KnigaRead.com/

Н.А. Вязовик - Программирование на Java

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Н.А. Вязовик, "Программирование на Java" бесплатно, без регистрации.
Перейти на страницу:

public class Test {

public Test() {

System.out.println("Test()");

}

public Test(int x) {

this();

System.out.println("Test(int x)");

}

}


После выполнения выражения new Test(0) на консоли появится:


Test()

Test(int x)

В заключение рассмотрим применение модификаторов доступа для конструкторов. Может вызвать удивление возможность объявлять конструкторы как private. Ведь они нужны для генерации объектов, а к таким конструкторам ни у кого не будет доступа. Однако в ряде случаев модификатор private может быть полезен. Например:


* private -конструктор может содержать инициализирующие действия, а остальные конструкторы будут использовать его с помощью this, причем прямое обращение к этому конструктору по каким-то причинам нежелательно;

* запрет на создание объектов этого класса, например, невозможно создать экземпляр класса Math ;

* реализация специального шаблона проектирования из ООП Singleton, для работы которого требуется контролировать создание объектов, что невозможно в случае наличия не- private конструкторов.

Инициализаторы

Наконец, последней допустимой конструкцией в теле класса является объявление инициализаторов. Записываются объектные инициализаторы очень просто – внутри фигурных скобок.

public class Test {

private int x, y, z;

// инициализатор объекта {

x=3;

if (x>0)

y=4;

z=Math.max(x, y);

}

}


Инициализаторы не имеют имен, исполняются при создании объектов, не могут быть вызваны явно, не передаются по наследству (хотя, конечно, инициализаторы в родительском классе продолжают исполняться при создании объекта класса-наследника).

Было указано уже три вида инициализирующего кода в классах – конструкторы, инициализаторы переменных, а теперь добавились объектные инициализаторы. Необходимо разобраться, в какой последовательности что выполняется, в том числе при наследовании. При создании экземпляра класса вызванный конструктор выполняется следующим образом:


если первой строкой идет обращение к конструктору родительского класса (явное или добавленное компилятором по умолчанию), то этот конструктор исполняется;

в случае успешного исполнения вызываются все инициализаторы полей и объекта в том порядке, в каком они объявлены в теле класса;

если первой строкой идет обращение к другому конструктору этого же класса, то он вызывается. Повторное выполнение инициализаторов не производится.

Второй пункт имеет ряд важных следствий. Во-первых, из него следует, что в инициализаторах нельзя использовать переменные класса, если их объявление записано позже.

Во-вторых, теперь можно сформулировать наиболее гибкий подход к инициализации final -полей. Главное требование – чтобы такие поля были проинициализированы ровно один раз. Это можно обеспечить в следующих случаях:


если инициализировать поле при объявлении;

если инициализировать поле только один раз в инициализаторе объекта (он должен быть записан после объявления поля);

если инициализировать поле только один раз в каждом конструкторе, в первой строке которого стоит явное или неявное обращение к конструктору родителя. Конструктор, в первой строке которого стоит this, не может и не должен инициализировать final -поле, так как цепочка this -вызовов приведет к конструктору с super, в котором эта инициализация обязательно присутствует.

Для иллюстрации порядка исполнения инициализирующих конструкций рассмотрим следующий пример:


public class Test {


{

System.out.println("initializer");

}

int x, y=getY();

final int z; {

System.out.println("initializer2");

}

private int getY() {

System.out.println("getY() "+z);

return z;

}

public Test() {

System.out.println("Test()");

z=3;

}

public Test(int x) {

this();

System.out.println("Test(int)");

// z=4; - нельзя! final-поле уже

// было инициализировано

}

}


После выполнения выражения new Test() на консоли появится:


initializer

getY() 0

initializer2

Test()


Обратите внимание, что для инициализации поля y вызывается метод getY(), который возвращает значение final -поля z, которое еще не было инициализировано. Поэтому в итоге поле y получит значение по умолчанию 0, а затем поле z получит постоянное значение 3, которое никогда уже не изменится.

После выполнения выражения new Test(3) на консоли появится:


initializer

getY() 0

initializer2

Test()

Test(int)

Дополнительные свойства классов

Рассмотрим в этом разделе некоторые особенности работы с классами в Java. Обсуждение данного вопроса будет продолжено в специальной лекции, посвященной объектной модели в Java.

Метод main

Итак, виртуальная машина реализуется приложением операционной системы и запускается по обычным правилам. Программа, написанная на Java, является набором классов. Понятно, что требуется некая входная точка, с которой должно начинаться выполнение приложения.

Такой входной точкой, по аналогии с языками C/C++, является метод main(). Пример его объявления:


public static void main(String[] args) { }


Модификатор static в этой лекции не рассматривался и будет изучен позже. Он позволяет вызвать метод main(), не создавая объектов. Метод не возвращает никакого значения, хотя в C есть возможность указать код возврата из программы. В Java для этой цели существует метод System.exit(), который закрывает виртуальную машину и имеет аргумент типа int.

Аргументом метода main() является массив строк. Он заполняется дополнительными параметрами, которые были указаны при вызове метода.

package test.first;

public class Test {

public static void main(String[] args) {

for (int i=0; i<args.length; i++) {

System.out.print(args[i]+" ");

}

System.out.println();

}

}


Для вызова программы виртуальной машине передается в качестве параметра имя класса, у которого объявлен метод main(). Поскольку это имя класса, а не имя файла, то не должно указываться никакого расширения ( .class или .java ) и расположение класса записывается через точку (разделитель имен пакетов), а не с помощью файлового разделителя. Компилятору же, напротив, передается имя и путь к файлу.

Если приведенный выше модуль компиляции сохранен в файле Test.java, который лежит в каталоге testfirst, то вызов компилятора записывается следующим образом:


javac testfirstTest.java


А вызов виртуальной машины:


java test.first.Test


Чтобы проиллюстрировать работу с параметрами, изменим строку запуска приложения:


java test.first.Test Hello, World!


Результатом работы программы будет:


Hello, World!


Параметры методов

Для лучшего понимания работы с параметрами методов в Java необходимо рассмотреть несколько вопросов.

Как передаются аргументы в методы – по значению или по ссылке? С точки зрения программы вопрос формулируется, например, следующим образом. Пусть есть переменная и она в качестве аргумента передается в некоторый метод. Могут ли произойти какие-либо изменения с этой переменной после завершения работы метода?


int x=3;

process(x);

print(x);


Предположим, используемый метод объявлен следующим образом:


public void process(int x) {

x=5;

}


Какое значение появится на консоли после выполнения примера? Чтобы ответить на этот вопрос, необходимо вспомнить, как переменные разных типов хранят свои значения в Java.

Напомним, что примитивные переменные являются истинными хранилищами своих значений и изменение значения одной переменной никогда не скажется на значении другой. Параметр метода process(), хоть и имеет такое же имя x, на самом деле является полноценным хранилищем целочисленной величины. А потому присвоение ему значения 5 не скажется на внешних переменных. То есть результатом примера будет 3 и аргументы примитивного типа передаются в методы по значению. Единственный способ изменить такую переменную в результате работы метода – возвращать нужные величины из метода и использовать их при присвоении:


public int doubler(int x) {

return x+x;

}

public void test() {

int x=3;

x=doubler(x);

}


Перейдем к ссылочным типам.


public void process(Point p)

{

p.x=3;

}

public void test() {

Point p = new Point(1,2);

process(p);

print(p.x);

}


Ссылочная переменная хранит ссылку на объект, находящийся в памяти виртуальной машины. Поэтому аргумент метода process() будет иметь в качестве значения ту же самую ссылку и, стало быть, ссылаться на тот же самый объект. Изменения состояния объекта, осуществленные с помощью одной ссылки, всегда видны при обращении к этому объекту с помощью другой. Поэтому результатом примера будет значение 3. Объектные значения передаются в Java по ссылке. Однако если изменять не состояние объекта, а саму ссылку, то результат будет другим:


public void process(Point p)

{

p = new Point(4,5);

}

public void test() {

Point p = new Point(1,2);

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*