Найти тему

Условно гласная

Если вы рассмотрите результат работы программы, то увидите, что констан¬та 5 трактуется как int, поэтому если есть перегруженный метод, принимающий аргумент типа int, то он и используется. Во всех остальных случаях, если име¬ется тип данных, «меньший», чем требуется для существующего метода, то этот тип данных повышается соответственным образом. Только тип char ве¬дет себя несколько иначе по той причине, что, если метода с параметром char нет, этот тип приводится сразу к типу int, а не к промежуточным типам byte или short.

Что же произойдет, если ваш аргумент «больше», чем аргумент, требующий¬ся в перегруженном методе? Ответ можно найти в модификации рассмотрен¬ной программы:

//:с04:Demotion.java

//Понижение примитивов и перегрузка.

import com.bruceeckel.simpletest.*;

public class Demotion {

static Test monitor = new TestO;

void fl(char x) { System.out.println("fl(char)"); }

void fKbyte x) { System out.println("fl(byte)"), }

void f 1(short x) { System.out.printlnC'fKshort)"); }

void fl(int x) { System.out.printlnC'fKint)"); }

void fKlong x) { System.out.printlnC'f 1(long)"); }

void fKfloat x) { System.out.println("fl(float)"); }

void f 1(double x) { System.out printlnC'fKdouble)"); }

void f2(char x) { System.out.println("f2(char)"); } void f2(byte x) { System.out.println("f2(byte)"); } void f2(short x) { System.out.println("f2(short)"). } void f2(int x) { System.out println("f2(int)"): } void f2(long x)?{ System.out.println("f2( 1 ong)"); }

void f2(float x) { System.out.println("f2(float)"); } продолжение&

124Глава 5 • Инициализация и завершение

System out println("f3(char)"), System out.pri nt1n("f3(byte)"); { System.out.pri ntln("f3(short)") System out.println("f3(int)"), } System.out.pri nt1n("f3(1ong)");

System.out.println("f4(char)"); System.out.println("f4(byte)M). { System.out println("f4(short)") System.out.println("f4(int)"); }

System.out println("f5(char)"); System out println("f5(byte)"); { System.out.pri ntln("f5(short)")

void f3(charх) void f3(byte х) void f3(short x) void f3(int x) { void f3(long x)

void f4(char x) void f4(byte x) void f4(short x) void f4(int x) {

void f5(char x) void f5(byte x) void f5(short x)

void f6(char x) void f6(byte x)

void f7(char x)

void testDouble(

System out.pri nt1n("f6(char)"). System.out.pri ntln("f6(byte)"),

System.out.println("f7(char)"), }

{

double x = 0;

System.out printlnC'napaMeipтипа double:"); fl(x);f2((float)x);f3((long)x).f4((int)x), f5((short)x);f6((byte)x);f7((char)x);

}

public static void main(String[] args) { Demotion p = new DemotionO; p.testDoubleO; monitor.expect(new StringC] {

"параметр типа double: "fl(double)", "f2(float)", "f3(long)", "f4(int)\ "f5(short)", "f6(byte)", "f7(char)M

} ///:-

Здесь методы требуют сужения типов данных. Если ваш аргумент «шире», необходимо явно привести его к нужному типу. В противном случае компиля¬тор выведет сообщениеоб ошибке.

Перегрузка по возвращаемым значениям

Вполне логично спросить, почему при перегрузке используются только имена классов и списки аргументов? Почему не идентифицировать методы по их воз¬вращаемым значениям? Следующие два метода имеют одинаковые имена и ар¬гументы, но их легко отличить друг от друга:

void f() {} int f() {}

Такой подход прекрасно сработает в ситуации, в которой компилятор может однозначно выбрать нужную версию метода, например: int х = f(). Однако воз¬вращаемое значение при вызове метода может быть проигнорировано; это часто называется вызовом метода для получения побочного эффекта, так как метод вызывается не для естественного результата, а для каких-то других целей. До¬пустим, метод вызывается следующим способом:

f():

Как здесь Java определит, какая из версий метода f() должна выполняться? И поймет ли читатель программы, что происходит при этом вызове? Именно из-за подобных проблем перегруженные методы не разрешается различать по возвращаемым значениям.

Конструкторы по умолчанию

Как упоминалось ранее, конструктором по умолчанию называется конструктор без аргументов, применяемый для создания «типового» объекта. Если создан¬ный вами классне имеет конструктора, компилятор автоматически добавит конструктор по умолчанию. Например:

//• initialization/DefaultConstructor.java class Bird {}

public class DefaultConstructor {

public static void main(String[] args) {

Bird b = new BirdO. //по умолчанию!

}

} ///-Строка

new BirdO;

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

//: initi alizati on/NoSynthesi s.java

class Bird2 {

Bird2(int i) {} Bird2(double d) {}

}

public class NoSynthesis {

public static void main(String[] args) {

//! Bird2 b = new Bird2(); //Нет конструктора по умолчанию! Bird2 Ь2 = new Bird2(l); Bird2 ЬЗ = new Bird2(1.0);

}

Теперь при попытке выполнения new Bird2() компилятор заявит, что не мо¬жет найти конструктор, подходящий по описанию. Получается так: если опре¬деления конструкторов отсутствуют, компилятор скажет: «Хотя бы один конст¬руктор необходим, позвольте создать его за вас». Если же вы записываете конструктор явно, компилятор говорит: «Вынаписали конструктор, а следова¬тельно, знаете, что вам нужно; й если вы создали конструктор по умолчанию, значит, он вам и не нужен».

Ключевое слово this

Если у вас есть два объекта одинакового типа с именами а и Ь, вы, возможно, за¬интересуетесь, каким образом производится вызов метода peel() для обоих объ¬ектов:

//: initialization/BananaPeel.java

class Banana { voi'd peel (int i ){/*...*/} }

public class BananaPeel {

public static void main(String[] args) {

Banana a = new BananaO, b = new BananaO;

a. peel(l);

b. peel(2);

}

} ///:-

Если существует только один метод с именем peel(), как этот метод узнает, для какого объекта он вызывается — а или Ь?

Чтобы программа могла записываться в объектно-ориентированном стиле, основанном на «отправке сообщений объектам», компилятор выполняет для вас некоторую тайную работу. При вызове метода peel() передается скрытый первый аргумент — не что иное, как ссылка на используемый объект. Таким об¬разом, вызовы указанного метода на самомделе можно представитьткак:

Banana.рееКаЛ);

Banana.peel(b,2);

Передача дополнительного аргумента относится к внутреннему синтаксису. При попытке явно воспользоваться ею компилятор выдает сообщение об ошиб¬ке, но вы примерно представляете суть происходящего.

Предположим, во время выполнения метода вы хотели бы получить ссылку на текущий объект. Так как эта ссылка передается компилятором скрытно, идентификатора для неене существует. Но для решения этой задачи существу¬ет ключевое слово — this. Юночевое слово this может использоваться только внутри не-статического метода и предоставляет ссылку на объект, для которого был вызван метод. Обращаться с ней можно точно так же, как и с любой другой ссылкой на объект. Помните, что при вызове метода вашего класса из другого метода этого класса this вам не нужно; просто укажите имя метода. Текущая ссылка this будет автоматически использована в другом методе. Таким образом, продолжая сказанное:

//: initialization/Apricot.java public class Apricot {

void pickO { /* .. */ } void pit() { pickO; /*..*/} } ///:-

Внутри метода pit() можно использовать запись this.pick(), но в этом нет не¬обходимости . Компилятор сделает это автоматически. Ключевое слово this употребляется только в особых случаях, когда вам необходимо явно сослаться на текущий объект. Например, оно часто применяется для возврата ссылки на текущий объект в команде return:

//: initialization/Leaf.java // Simple use of the "this" keyword.

public class Leaf { int i = 0; Leaf incrementО { i++;

return this;

}

void printO {

System, out. printlnC'i = " + i).

}

public static void main(String[] args) { Leaf x = new LeafO;

x.i ncrement().i ncrement() i ncrement().pri ntО;

}

} /* Output: i = 3 *///:-

Так как метод increment() возвращает ссылку на текущий объект посредст¬вом ключевого слова this, над одним и тем же объектом легко можно провести множество операций.

Ключевое слово this также может пригодиться для передачи текущего объек¬та другому методу:

//. initialization/PassingThis java

class Person {

public void eat(Apple apple) {

Apple peeled = apple.getPeeledО; System.out.println("Yummy");

}

}

class Peeler {

static Apple peel(Apple apple) {

продолжение&

// ..Снимаем кожуру

return apple; //Очищенное яблоко

}

}

class Apple {

Apple getPeeledO { return Peeler.peel(this); }

}

public class PassingThis {

public static void main(String[] args) { new PersonО eat (new AppleO);

}

} /* Output: Yummy *///.-

Класс Apple вызывает Peeler.peel() — вспомогательный метод, который по ка¬кой-то причине должен быть оформлен как внешний по отношению к Apple (мо¬жет быть, он должен обслуживать несколько разных классов, и вы хотите избе¬жать дублирования кода). Для передачи текущего объекта внешнему методу используется ключевое слово this.

Вызов конструкторов из конструкторов

Если вы пишете для класса несколько конструкторов, иногда бывает удобно вызвать один конструктор из другого, чтобы избежать дублирования кода. Та¬кая операция проводится с использованием ключевого слова this.

Обычно при употреблении this подразумевается «этот объект» или «теку¬щий объект», и само слово является ссылкой на текущий объект. В конструкто¬ре ключевое слово this имеет другой смысл: при использовании его со списком аргументов вызывается конструктор, соответствующий данному списку. Таким образом, появляется возможность прямого вызова других конструкторов:

// initialization/Flower.java // Calling constructors with "this" import static net.mindview.util.Print.*;

public class Flower { int petal Count = 0; String s = "initial value"; Flower(int petals) {

petal Count = petals;

print("Конструктор с параметром int, petalCount= " + petal Count),

}

Flower(String ss) {

print("Конструктор с параметром String, s = " + ss); s = ss;

}

Flower(String s, int petals) { this(petals),

//! this(s); //Вызов другого конструктора запрещен! this.s = s; // Другое использование "this" print("Аргументы String и int");

}

FlowerО { thisC'hi". 47).

printC'KOHCTpyKTopпо умолчанию (без аргументов)"),

}

void printPetalCountO { //! this(11), //Разрешается только в конструкторах! print("petal Count = " + petal Count + " s = "+ s);

}

public static void main(String[] args) { Flower x = new Flower(); x printPetalCountO,

}

} /* Output-

Конструктор с параметром int, petalCount= 47 Аргументы String и int Конструктор по умолчанию (без аргументов) petal Count = 47 s = hi */// ~

Конструктор Flower(String s, int petals) показывает, что при вызове одного конструктора через this вызывать второй запрещается. Вдобавок вызов другого конструктора должен быть первой выполняемой операцией, иначе компилятор выдаст сообщение об ошибке.

Пример демонстрирует еще один способ использования this. Так как имена аргумента s и поля данных класса s совпадают, возникает неоднозначность. Раз¬решить это затруднение можно при помощи конструкции this.s, однозначно оп¬ределяющей поле данных класса. Вы еще не раз встретите такой подход в раз¬личных Java-nporpaMMax, да и в этой книге он практикуется довольно часто.

Метод printPetalCountO показывает, что компилятор не разрешает вызывать конструктор из обычного метода; это разрешено только в конструкторах.

Значение ключевого слова static

Ключевое слово this поможет лучше понять, что же фактически означает объяв¬ление статического (static) метода. У таких методов не существует ссылки this. Вы не в состояниивызывать нестатические методы из статических  (хотя об¬ратное позволено), и статические методы можно вызывать для имени класса, без каких-либо объектов. Статические методы отчасти напоминают глобальные функции языка С, но с некоторыми исключениями: глобальные функции не разрешены в Java, и создание статического метода внутри класса дает ему право на доступ к другим статическим методам и полям.

Некоторые люди утверждают, что статические методы со своей семантикой глобальной функции противоречат объектно-ориентированной парадигме; в слу¬чае использования статического метода вы не посылаете сообщение объекту, поскольку отсутствует ссылка this. Возможно, что это справедливый упрек, и если вы обнаружите, что используете слишком много статических методов, то стоит пересмотреть вашу стратегию разработки программ. Однако ключевое слово static полезно на практике, и в некоторых ситуациях они определенно не¬обходимы. Споры же о «чистоте ООП» лучше оставить теоретикам.

Очистка: финализация и сборка мусора