Java: руководство для начинающих (ЛП) - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
}Выполнение этого фрагмента кода дает следующий результат:
Inside f (int) : 10Inside f(double): 10.1Inside f (int): 99Inside f(int): 10Inside f(double): 11.5В данном примере определены только два варианта метода f (): один принимает параметр типа int, а второй — параметр типа double. Но передать методу f () можно также значение типа byte, short или float. Значения типа byte и short исполняющая система Java автоматически преобразует в тип int. В результате будет вызван вариант метода f (int). А если параметр имеет значение типа float, то оно преобразуется в тип double и далее вызывается вариант метода f (double).Важно понимать, что автоматическое преобразование типов выполняется лишь в отсутствие прямого соответствия типов параметра и аргумента. В качестве примера ниже представлена другая версия предыдущей программы, в которой добавлен вариант метода f() с параметром типа byte.
// Добавление варианта метода f(byte).class 0verload2 { void f(byte x) { System.out.println("Inside f(byte): " + x) ; }void f(int x) { System.out.println("Inside f(int) : " + x);}void f(double x) { System.out.printlnpinside f(double): " + x);}
}
class TypeConv { public static void main(String args[]) { 0verload2 ob = new 0verload2(); int i = 10; double d = 10.1; byte b = 99; short s = 10; float f = 11.5F; ob.f(i); // Вызов метода ob.f(int) ob.f(d); // Вызов метода ob.f(double) ob.f(b); // Вызов метода ob.f(byte) без преобразования типов ob.f(s) ; // Вызов метода ob.f (int) с преобразованием (типов ob.f(f) ; // Вызов метода ob.f(double) с преобразованием типов}
}Выполнение этой версии программы дает следующий результат:
Inside f(int): 10Inside f(double): 10.1Inside f(byte): 99Inside f(int): 10Inside f(double): 11.5В данной версии программы используется вариант метода f () с параметром типа byte. Так, если при вызове метода f () ему передается значение типа byte, вызывается вариант метода f (byte) и автоматическое приведение к типу int не производится.Перегрузка методов представляет собой механизм воплощения полиморфизма, т.е. способ реализации в Java принципа “один интерфейс — множество методов”. Для того чтобы стёбю понятнее, как и для чего это делается, необходимо принять во внимание следующее соображение: в языках программирования, не поддерживающих перегрузку методов, каждый метод должен иметь уникальное имя. Но в ряде случаев требуется выполнять одну и ту же последовательность операций над разными типами данных. В качестве примера рассмотрим функцию, определяющую абсолютное значение. В языках, не поддерживающих перегрузку методов, приходится создавать три или более варианта данной функции с именами, отличающимися хотя бы одним символом. Например, в языке С функция abs () возвращает абсолютное значение числа типа int, функция labs () — абсолютное значение числа типа long, а функция f abs () применяется к значению с плавающей точкой. А поскольку в С не поддерживается перегрузка, то каждая из функций должна иметь свое собственное имя, несмотря на то, что все они выполняют одинаковые действия. Это приводит к неоправданному усложнению процесса написания программ. Разработчику приходится не только представлять себе действия, выполняемые функциями, но и помнить все три их имени. Такая ситуация не возникает в Java, потому что все методы, вычисляющие абсолютное значение, имеют одно и то же имя. В стандартной библиотеке Java для вычисления абсолютного значения предусмотрен метод abs (). Его перегрузка осуществляется в классе Math для обработки значений всех числовых типов. Решение о том, какой именно вариант метода abs () должен быть вызван, исполняющая система Java принимает, исходя из типа аргумента.Главная ценность перегрузки заключается в том, что она обеспечивает доступ к связанным вместе методам по общему имени. Следовательно, имя abs обозначает общее выполняемое действие, а компилятор сам выбирает конкретный вариант метода по обстоятельствам. Благодаря полиморфизму несколько имен сводятся к одному. Несмотря на всю простоту рассматриваемого здесь примера, продемонстрированный в нем принцип полиморфизма можно расширить, чтобы выяснить, каким образом перегрузка помогает справляться с более сложными ситуациями в программировании.Когда метод перегружается, каждый его вариант может выполнять какое угодно действие. Для установления взаимосвязи между перегружаемыми методами не существует какого-то твердого правила, но с точки зрения правильного стиля программирования перегрузка методов подразумевает подобную взаимосвязь. Следовательно, использовать одно и то же имя для несвязанных друг с другом методов не следует, хотя это и возможно. Например, имя sqr можно было бы выбрать для методов, возвращающих квадрат и квадратный корень числа с плавающей точкой. Но ведь это принципиально разные операции. Такое применение перегрузки методов противоречит ее первоначальному назначению. На практике перегружать следует только тесно связанные операции.## Перегрузка конструкторовКак и методы, конструкторы также могут перегружаться. Это дает возможность конструировать объекты самыми разными способами. В качестве примера рассмотрим следующую программу:
// Демонстрация перегрузки конструкторов,class MyClass { int х;// Конструкторы перегружаются разными способами.MyClass() { System.out.println("Inside MyClass()."); x = 0 ;}MyClass(int i) { System.out.println("Inside MyClass(int) . ") ; x = i;}MyClass(double d) { System.out.println("Inside MyClass(double)."); x = (int) d;}MyClass(int i, int j) { System.out.println("Inside MyClass(int, int)."); x = i * j;}
}
class OverloadConsDemo { public static void main(String args[]) { MyClass tl = new MyClass(); MyClass t2 = new MyClass(88); MyClass t3 = new MyClass(17.23); MyClass t4 = new MyClass(2, 4); System.out.println("tl.x: " + tl.x); System.out.println("t2.x: " + t2.x); System.out.println("t3.x: " + t3.x); System.out.println("t4.x: " + t4.x);}
}В результате выполнения этой программы получается следующий результат:
Inside MyClass().Inside MyClass(int).Inside MyClass(double).Inside MyClass(int, int).tl.x: 0t2.x: 88t3.x: 17t4.x: 8В данном примере конструктор MyClass () перегружается четырежды. Во всех вариантах этого конструктора объект типа MyClass строится по-разному. Конкретный вариант конструктора выбирается из тех параметров, которые указываются при выполнении оператора new. Перегружая конструктор класса, вы предоставляете пользователю созданного вами класса свободу в выборе способа конструирования объекта.Перегрузка конструкторов чаще всего производится для того, чтобы дать возможность инициализировать один объект на основании другого объекта. Рассмотрим в качестве примера следующую программу, в которой класс Summation используется для вычисления суммы двух целочисленных значений.
// Инициализация одного объекта посредством другого,class Summation { int sum;// построить объект из целочисленного значенияSummation(int num) { sum = 0; for(int i=l; i <= num; i++) sum += i;}// Построение одного объекта иэ другого.Summation(Summation ob) { sum = ob.sum;}
}
class SumDemo { public static void main(String args[]) { Summation si = new Summation(5); Summation s2 = new Summation(si); System.out.println("si.sum: " + si.sum); System.out.println("s2.sum: " + s2.sum);}
}Выполнение этой программы дает следующий результат:
si.sum: 15s2.sum: 15Как следует из приведенного выше примера, использование одного объекта при инициализации другого нередко оказывается вполне оправданным. В данном случае при конструировании объекта s2 нет необходимости вычислять сумму. Даже если подобная инициализация не повышает быстродействие программы, зачастую удобно иметь конструктор, создающий копию объекта.**Пример для опробования 6.2.**Перегрузка конструктора класса QueueВ этом проекте предстоит усовершенствовать класс Queue, добавив в него два дополнительных конструктора. В первом из них новая очередь будет конструироваться на основании уже существующей, а во втором — присваиваться начальные значения элементам очереди при ее конструировании. Как станет ясно в дальнейшем, добавление этих конструкторов сделает класс Queue более удобным для использования.Последовательность действий1. Создайте новый файл QDemo2 . j ava и скопируйте в него код класса Queue, созданный в примере для опробования 6.1.2. Добавьте сначала в этот класс приведенный ниже конструктор, который будет строить одну очередь на основании другой.// Конструктор, строящий один объект типа Queue на основании другого.Queue(Queue ob) { putloc = ob.putloc; getloc = ob.getloc; q = new char[ob.q.length]; // копировать элементы очереди for(int i=getloc+l; i <= putloc; i++) q[i] = ob.q[i];}```Внимательно проанализируем этот конструктор. Сначала переменные putloc и getloc инициализируются в нем значениями, содержащимися в объекте ob, который передается ему в качестве параметра. Затем в нем организуется новый массив для хранения элементов очереди, которые далее копируются из объекта ob в этот массив. Вновь созданная копия очереди будет идентична оригиналу, хотя они и являются совершенно отдельными объектами.