Java: руководство для начинающих
Шрифт:
Как видите, в данном случае действия в методе change оказывают влияние на объект, используемый в качестве аргумента этого метода.
Не следует, однако, забывать, что когда объект передается методу по ссылке, сама ссылка на него передается по значению. Но поскольку передаваемое значение лишь указывает на объект, копия этого значения будет по-прежнему указывать на тот же самый объект в соответствующем аргументе. Возврат объектов
Метод может возвращать данные любого типа, включая и типы классов. Например, объект приведенного ниже класса ErrorMsg может быть использован для сообщения об ошибке. В этом классе имеется метод getErrorMsg , который возвращает объект типа String, описывающий ошибку. Объект типа String строится на основании кода ошибки, переданного методу. // Возврат объекта типа String, class ErrorMsg { String msgs[] = { "Output Error", "Input Error", "Disk Full", "Index Out-Of-Bounds" }; // возвратить объект типа String в виде сообщения об ошибке String getErrorMsg(int i) { if(i >=0 & i < msgs.length) return msgs[i]; else return "Invalid Error Code"; } } class ErrMsg { public static void main(String args[]) { ErrorMsg err = new ErrorMsg; System.out.println(err.getErrorMsg(2)); System.out.println(err.getErrorMsg(19)); } }
Выполнение
Разумеется, возвращать можно и объекты создаваемых классов. Например, приведенный ниже фрагмент кода представляет собой переработанную версию предыдущей программы, где создаются два класса формирования ошибок Err и Error Inf о. В классе Err, помимо кода ошибки, инкапсулируется символьная строка описания ошибки. А в классе Errorlnf о содержится метод getErrorlnf о , возвращающий объект типа Err. // Возврат объекта, определяемого разработчиком программы, class Err { String msg; // Сообщение об ошибке int severity; // Код, определяющий серьезность ошибки Err(String m, int s) { msg = m; severity = s; } } class Errorlnfo { String msgs[] = { "Output Error", "Input Error", "Disk Full", "Index Out-Of-Bounds" }; int howbad[] = { 3, 3, 2, 4 }; // Возврат объекта типа Err. Err getErrorlnfo(int i) { if(i >=0 & i < msgs.length) return new Err(msgs[i], howbad[i]); else return new Err("Invalid Error Code", 0) ; } } class Errlnfo { public static void main(String args[]) { Errorlnfo err = new Errorlnfo; Err e; e = err.getErrorlnfo (2); System.out.println(e.msg + " severity: " + e.severity); e = err.getErrorInfo.(19) ; System.out.println(e.msg + " severity: " + e.severity); } }
Disk Full severity: 2 Invalid Error Code severity: 0 При каждом вызове метода getErrorlnfo создается новый объект типа Err и ссылка на него возвращается вызывающему методу. Этот объект затем используется методом main для отображения кода серьезности ошибки и текстового сообщения. Объект, возвращенный методом, существует до тех пор, пока на него имеется хотя бы одна ссылка. Если ссылки на объект отсутствуют, он уничтожается системой “сборки мусора”. Поэтому при выполнении программы не возникает ситуация, когда объект разрушается лишь потому, что метод, в котором он был создан, завершился. ## Перегрузка методов методов В этом разделе речь пойдет об одном из самых интересных языковых средств Java — перегрузке методов. Несколько методов одного класса могут иметь одно и то же имя, отличаясь лишь набором параметров. Перегрузка методов является одним из способов реализации принципа полиморфизма в Java. Для того чтобы перегрузить метод, достаточно объявить его новый вариант, отличающийся от уже существующих, а все остальное сделает компилятор. Нужно лишь соблюсти одно условие: тип и/или число параметров в каждом из перегружаемых методов должны быть разными. Некоторые считают, что два метода могут отличаться лишь типом возвращаемого значения, но это заблуждение. Возвращаемый тип не предоставляет достаточных сведений для принятия решения о том, какой именно метод должен быть использован. Конечно, перегружаемые методы могут иметь разные возвращаемые типы, но при вызове метода выполняется лишь тот его вариант, в котором параметры соответствуют передаваемым аргументам. Ниже приведен простой пример программы, демонстрирующий перегрузку методов.
// Перегрузка методов, class Overload { // Первый вариант метода. void ovlDemo { System.out.println("No parameters"); } // перегрузить метод ovlDemo с одним параметром типа int. // Второй вариант метода. void ovlDemo(int а) { System.out.println("One parameter: " + a); } // перегрузить метод ovlDemo с двумя параметрами типа int. // Третий вариант метода. int ovlDemo(int a, int b) { System.out.println("Two parameters: " + a + " " + b) ; return a + b; } // перегрузить метод ovlDemo с двумя параметрами типа double. // Четвертый вариант метода. double ovlDemo(double a, double b) { System.out.println("Two double parameters: " + a + " "+ b); return a + b; }
}
class OverloadDemo { public static void main(String args[]) { Overload ob = new Overload; int resl; double resD; // вызвать все варианты метода ovlDemo ob.ovlDemo; System.out.println; ob.ovlDemo(2) ; System.out.println; resl = ob.ovlDemo(4, 6) ; System.out.println("Result of ob.ovlDemo(4, 6): " + resl); System.out.println; resD = ob.ovlDemo(1.1, 2.32); System.out.println("Result of ob.ovlDemo(1.1, 2.32): " + resD); }
} Как видите, метод ovlDemo перегружается четырежды. В первом его варианте параметры не предусмотрены, во втором — определен один целочисленный параметр, в третьем — два целочисленных параметра, в четвертом — два параметра типа double. Обратите внимание на то, что первые два варианта метода ovlDemo имеют тип void, а два другие возвращают значение. Как пояснялось ранее, тип возвращаемого значения не учитывается при перегрузке методов. Следовательно, попытка определить два варианта метода ovlDemo так, как показано ниже, приводит к ошибке.
// Возможен лишь один вариант метода ovlDemo (int). // Возвращаемое значение нельзя использовать // для различения перегружаемых методов. void ovlDemo(int а) { System.out.println("One parameter: " + a); } / Ошибка! Два варианта метода ovlDemo(int) не могут существовать, даже если типы возвращаемых ими значений отличаются. / int ovlDemo(int а) { System.out.println("One parameter: " + a); return a * a; } Как поясняется в комментариях к приведенному выше фрагменту кода, отличия возвращаемых типов недостаточно для перегрузки методов. Как следует из главы 2, в Java производится автоматическое приведение типов. Это приведение распространяется и на типы параметров перегружаемых методов. В качестве примера рассмотрим следующий фрагмент кода:
/ Автоматическое преобразование типов может оказывать влияние на выбор перегружаемого метода. / class 0verload2 { void f(int x) { System.out.println("Inside f(int): " + x) ; } void f(double x) { System.out.println("Inside f(double): " + x) ; }
}
class TypeConv { public static void main(String args[]) { overload2 ob = new 0verload2; int i = 10; double d = 10.1; byte b = 99; short s = 10; float f = 11.5F; ob.f(i); // Вызов метода оb.f(int) ob.f(d); // Вызов метода ob.f(double) ob.f(b); // Вызов метода oh.f(int) с преобразованием типов ob.f(s); // Вызов метода ob.f(int) с преобразованием типов ob.f(f); // Вызов метода ob.f(double) с преобразованием типов }
} Выполнение этого фрагмента кода дает следующий результат:
Inside f (int) : 10 Inside f(double): 10.1 Inside f (int): 99 Inside f(int): 10 Inside 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): 10 Inside f(double): 10.1 Inside f(byte): 99 Inside f(int): 10 Inside 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 можно было бы выбрать для методов, возвращающих квадрат и квадратный корень числа с плавающей точкой. Но ведь это принципиально разные операции. Такое применение перегрузки методов противоречит ее первоначальному назначению. На практике перегружать следует только тесно связанные операции. ## Перегрузка конструкторов Как и методы, конструкторы также могут перегружаться. Это дает возможность конструировать объекты самыми разными способами. В качестве примера рассмотрим следующую программу: