17

17


Большинство языков программирования (и в особенности С) требовали ис¬пользования уникальных имен для всех функций. Иначе говоря, программа не могла содержать функцию print() для распечатки целых чисел и одноимен¬ную функцию для вывода вещественных чисел — каждая функция должна была иметь уникальное имя.



В Java (и в С++) также существует другой фактор, который заставляет ис¬пользовать перегрузку имен методов: наличие конструкторов. Так как имя кон¬структора предопределено именем класса, оно может быть только единствен¬ным. Но что, если вы захотите создавать объекты разными способами? Допус¬тим, вы создаете класс с двумя вариантами инициализации: либо стандартно, либо на основании из некоторого файла. В этом случае необходимость двух конструкторов очевидна: один из них не имеет аргументов (конструктор по умолчаниюх, также называемый конструктором без аргументов (no-arg)), а другой получает в качестве аргумента строку с именем файла. Оба они явля¬ются полноценными конструкторами, и поэтому должны называться одинако¬во — именем класса. Здесь перегрузка методов (overloading) однозначно необхо¬дима, чтобы мы могли использовать методы с одинаковыми именами, но с раз¬ными аргументами . И хотя перегрузка методов обязательна только для конст¬рукторов, она удобна в принципе и может быть применена к любому методу.



Следующая программа показывает пример перегрузки как конструктора, так и обычного метода:



//: initialization/Overloading.java // Демонстрация перегрузки конструкторов наряду // с перегрузкой обычных методов, import static net.mindview util Print *;



class Tree {



int height:



Tree О {



print("Сажаем росток"): height = 0;



}



Tree(int initialHeight) {



height = i niti alHeight: print("Создание нового дерева высотой " + height + " м."):



}



void infoO {



print("Дерево высотой " + height + " м."):



}



void info(String s) {



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



print(s + ": Дерево высотой " + height + " м.");



}



}



public class Overloading {



public static void main(String[] args) { for(int i = 0; i < 5; i++) { Tree t = new Tree(i); t.infoO:



t.info("Перегруженный метод");



}



// Перегруженный конструктор: new TreeO;



}



} /* Output:



Создание нового дерева высотой 0 м.



Дерево высотой 0 м.



Перегруженный метод: Дерево высотой 0 м.



Создание нового дерева высотой 1 м.



Дерево высотой 1 м.



Перегруженный метод: Дерево высотой 1 м.



Создание нового дерева высотой 2 м.



Дерево высотой 2 м.



Перегруженный метод: Дерево высотой 2 м.



Создание нового дерева высотой 3 м.



Дерево высотой 3 м.



Перегруженный метод: Дерево высотой 3 м.



Создание нового дерева высотой 4 м.



Дерево высотой 4 м.



Перегруженный метод: Дерево высотой 4 м.



Сажаем росток



*///:-



Объект Tree (дерево) может быть создан или в форме ростка (без аргумен¬тов), или в виде «взрослого растения» с некоторой высотой. Для этого в классе определяются два конструктора; один используется по умолчанию, а другой по¬лучает аргумент с высотой дерева.



Возможно, вы захотите вызывать метод info() несколькими способами. На¬пример, вызов с аргументом-строкой info(String) используется при необходимо¬сти вывода дополнительной информации, а вызов без аргументов info() — когда дополнений к сообщению метода не требуется. Было бы странно давать два раз¬ных имени методам, когда их схожесть столь очевидна. К счастью, перегрузка методов позволяет использовать одно и то же имя для обоих методов.



Различение перегруженных методов



Если у методов одинаковые имена, как Java узнает, какой именно из них вызы¬вается? Ответ прост: каждый перегруженный метод должен иметь уникальный список типов аргументов.



Если немного подумать, такой подход оказывается вполне логичным. Как еще различить два одноименных метода, если не по типу аргументов?



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



// initialization/OverloadingOrder.java // Перегрузка, основанная на порядке // следования аргументов import static net.mindview util Print.*;



public class OverloadingOrder {



static void f(String s, int i) {



print("String- " + s + ". int: " + i).



}



static void f(int i. String s) {



printCint. " + i + String: " + s):



}



public static void main(String[] args) { f("Сначала строка", 11); f(99. "Сначала число").



}



} /* Output



String Сначала строка, int: 11 int 99. String. Сначала число *///.-



Два метода f() имеют одинаковые аргументы с разным порядком следования, и это различие позволяет идентифицировать метод.



Перегрузка с примитивами



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



//: ini ti alizati on/Pri mi ti veOverloadi ng.java // Повышение примитивных типов и перегрузка, import static net mindview.util.Print.*;



public class PrimitiveOverloading {



void fl(char x) { printnb("fl(char)"); }'



void fKbyte x) { printnbCf l(byte)"). }



void fKshort x) { printnb("fl(short)"); }



void fl(int x) { printnbCfKint)"): }



void fKlong x) { printnD("fl(long)"); }



void fl(float x) { printnb("fl(float)"); }



void f1(double x) { printnb("fl(double)"); }



void f2(byte x) { printnb("f2(byte)"); }



void f2(short x) { printnb("f2(short)"'); }



void f2(int x) { printnb("f2(int)"); }



void f2(long x) { printnb("f2(long)"); }



void f2(float x) { printnb("f2(float)"); }



void f2(double x) { printnb("f2(double)"); }



void f3(short x) { printnb("f3(short)"); }



void f3(int x) { printnb("f3(int)")} void f3(long x) { printnb("f3(long)M); } void f3(float x) { printnb("f3(float)"); } void f3(double x) { printnb("f3(double)"); }



void f4(int x) { printnb("f4(int)"); } void f4(long x) { printnb("f4(long)"); } void f4(float x) { printnb("f4(float)"); } void f4(double x) { printnb("f4(double)"); }



void f5(long x) { printnb("f5(long)"); } void f5(float x) { printnb("f5(float)"); } void f5(double x) { printnb("f5(double)"); }



void f6(float x) { printnb("f6(float)"); } void f6(double x) { printnb("f6(double)"); }



void f7(double x) { printnb("f7(double)"); }



void testConstValО {



printnb("5: ");



fl(5);f2(5);f3(5);f4(5);f5(5).f6(5);f7(5);print();



}



void testCharO {



char x = 'x'; printnbC'char: ");



fl(x) ;f2(x) ;f3(x) ;f4(x) ;f5(x) ;f6(x) ;f7(x); print ();



}



void testByteO {



byte x = 0;



System.out.println("параметр типа byte:"); fl(x):f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);



}



void testShortO {



short x = 0; printnb("short: ");



fl(x):f2(x):f3(x):f4(x);f5(x):f6(x);f7(x);print():



}



void testlntO {



int x = 0: printnbC'int: "):



fl(x) ;f2(x) ;f3(x) :f4(x) :f5(x) ;f6(x) ;f7(x); print ():



}



void testLongO {



long x = 0; printnbC'long:");



fl(x):f2(x):f3(x):f4(x):f5(x):f6(x):f7(x);print();



}



void testFloatO {



float x = 0:



System.out.pri nt1n("f1 oat:");



fl(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);print();



}



void testDoubleO {



double x = 0: printnb("double:"):



fl(x) ;f2(x) ;f3(x) ;f4(x) ;f5(x) ;f6(x) ;f7(x) ;print();



}



public static void main(String[] args) { PrimitiveOverloading p =



/new PrimitiveOverloadingO; p.testConstValО. p.testCharO; p.testByteО; p testShortO; p.testlntO; p.testLongO; p testFloatO; p.testDoubleO;



}



} /* Output:



5: fl(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) char: fl(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) byte: fl(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double) short: fl(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double) int: fl(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double) long: fl(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double) float: fl(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(double) double- fl(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double) *///:-



Если вы рассмотрите результат работы программы, то увидите, что констан¬та 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) {

Report Page