Иллюстрированный самоучитель по Java
Как
описать класс и подкласс
Итак, описание класса начинается
со слова class, после которого записывается имя класса. Соглашения "Code
Conventions" рекомендуют начинать имя класса с заглавной буквы.
Перед словом
class
можно записать модификаторы класса (class modifiers). Это одно из слов
public, abstract, final, strictfp
. Перед именем вложенного класса можно
поставить, кроме того, модификаторы
protected, private, static
.
Модификаторы мы будем вводить по мере изучения языка.
Тело класса, в котором в любом порядке
перечисляются поля, методы, вложенные классы и интерфейсы, заключается в фигурные
скобки.
При описании поля указывается его
тип, затем, через пробел, имя и, может быть, начальное значение после знака
равенства, которое можно записать константным выражением. Все это уже описано
в
главе 1.
Описание поля может начинаться с
одного или нескольких необязательных модификаторов
public, protected,
private, static, final, transient, volatile
. Если надо поставить несколько
модификаторов, то перечислять их JLS рекомендует в указанном порядке, поскольку
некоторые компиляторы требуют определенного порядка записи модификаторов. С
модификаторами мы будем знакомиться по мере необходимости.
При описании метода указывается
тип возвращаемого им значения или слово
void
, затем, через
пробел, имя метода, потом, в скобках, список параметров. После этого в фигурных
скобках расписывается выполняемый метод.
Описание метода может начинаться
с модификаторов
public, protected, private, abstract, static,
final, synchronized, native, strictfp
. Мы будем вводить их по необходимости.
В списке параметров через запятую
перечисляются тип и имя каждого параметра. Перед типом какого-либо параметра
может стоять модификатор
final
. Такой параметр нельзя
изменять внутри метода. Список параметров может отсутствовать, но скобки сохраняются.
Перед началом работы метода для
каждого параметра выделяется ячейка оперативной памяти, в которую копируется
значение параметра, заданное при обращении к методу. Такой способ называется
передачей параметров
по значению.
В листинге 2.1 показано, как можно
оформить метод деления пополам для нахождения корня нелинейного уравнения из
листинга 1.5.
Листинг 2.1.
Нахождение корня нелинейного уравнения методом бисекцйи
class
Bisection2{
private static
double final EPS = le-8; // Константа
private double
a = 0.0, b = 1.5, root; // Закрытые поля
public double
getRoot(}{return root;} // Метод доступа
private double
f(double x)
{
return x*x*x
— 3*x*x + 3; // Или что-то другое
}
private void
bisect(){ // Параметров нет —
// метод работает с полями экземпляра
double у = 0.0;
// Локальная переменная — не поле
do{
root = 0.5 *(а
+ b); у = f(root);
if (Math.abs(y)
< EPS) break;
// Корень найден.
Выходим из цикла
// Если на концах
отрезка [a; root]
// функция имеет
разные знаки:
if (f(а) * у
< 0.0} b = root;
// значит, корень здесь
// Переносим точку b в точку root
//В противном случае:
else a = root;
// переносим точку а в точку root
// Продолжаем, пока [а; Ь] не станет мал
} while(Math.abs(b-a)
>= EPS);
}
public static
void main(String[] args){
Bisection2 b2
= new Bisection2();
b2.bisect();
System.out.println("x
= " +
b2.getRoot()
+ // Обращаемся к корню через метод доступа
", f() =
" +b2.f(b2.getRoot()));
}
}
В описании метода
f()
сохранен старый, процедурный стиль: метод получает аргумент, обрабатывает
его и возвращает результат. Описание метода
bisect
о
выполнено в духе ООП: метод активен, он сам обращается к полям экземпляра
b2
и сам заносит результат в нужное поле. Метод
bisect
()
— это внутренний механизм класса Bisection2, поэтому он закрыт (private).
Имя метода, число и типы параметров
образуют
сигнатуру
(signature) метода. Компилятор различает методы не
по их именам, а по сигнатурам. Это позволяет записывать разные методы с одинаковыми
именами, различающиеся числом и/или типами параметров.
Замечание
Тип возвращаемого значения не
входит в сигнатуру метода, значит, методы не могут различаться только типом
результата их работы.
Например, в классе
Automobile
мы записали метод
moveTo(int x, int у)
,
обозначив пункт назначения его географическими координатами. Можно определить
еще метод
moveTo (string destination)
для указания географического
названия пункта назначения и обращаться к нему так:
oka.moveTo("Москва")
;
Такое дублирование методов называется
перегрузкой
(overloading). Перегрузка методов очень удобна в использовании.
Вспомните, в главе 1 мы выводили данные любого типа на экран методом
printin()
не заботясь о том, данные какого именно типа мы выводим. На самом деле мы использовали
разные методы t одним и тем же именем
printin
, даже не
задумываясь об этом. Конечно, все эти методы надо тщательно спланировать и заранее
описать в классе. Это и сделано в классе Printstream, где представлено около
двадцати методов
print()
и
println()
.
Если же записать метод с тем же
именем в подклассе, например:
class
Truck extends Automobile{
void moveTo(int
x, int y){
//
Какие-то действия
}
//
Что-то еще
}
то он перекроет метод суперкласса.
Определив экземпляр класса
Truck
, например:
Truck
gazel = new Truck();
и записав
gazei.moveTo(25,
150)
, мы обратимся к методу класса
Truck
. Произойдет
переопределение
(overriding) метода.
При переопределении права доступа
к методу можно только расширить. Открытый метод
public
должен остаться открытым, защищенный
protected
может
стать открытым.
Можно ли внутри подкласса обратиться
к методу суперкласса? Да, можно, если уточнить имя метода, словом
super
,
например,
super.moveTo(30, 40)
. Можно уточнить и имя
метода, записанного в этом же классе, словом
this
, например,
this.moveTo (50, 70)
, но в данном случае это уже излишне.
Таким же образом можно уточнять и совпадающие имена полей, а не только методов.
Данные уточнения подобны тому, как
мы говорим про себя "я", а не "Иван Петрович", и говорим
"отец", а не "Петр Сидорович".
Переопределение методов приводит
к интересным результатам. В классе
Pet
мы описали метод
voice()
. Переопределим его в подклассах и используем
в классе
chorus
, как показано в листинге 2.2.
Листинг 2.2.
Пример полиморфного метода
abstract
class Pet{
abstract
void voice();
}
class Dog extends
Pet{
int
k = 10;
void
voice(){
System.out.printin("Gav-gav!");
}
}
class Cat extends
Pet{
void
voice () {
System.out.printin("Miaou!");
}
}
class Cow extends
Pet{
void
voice(){
System.out.printin("Mu-u-u!");
}
}
public class Chorus(
public
static void main(String[] args){
Pet[] singer = new Pet[3];
singer[0] = new Dog();
singer[1] = new Cat();
singer[2] = new Cow();
for (int i = 0; i < singer.length; i++)
singer[i].voice();
}
}
На рис. 2.1 показан вывод этой программы.
Животные поют своими голосами!
Все дело здесь в определении поля
singer[].
Хотя массив ссылок
singer []
имеет тип
Pet
, каждый его элемент ссылается на объект
своего типа
Dog, Cat, cow
. При выполнении программы вызывается
метод конкретного объекта, а не метод класса, которым определялось имя ссылки.
Так в Java реализуется полиморфизм.
Знатокам C++
В языке Java все методы являются
виртуальными функциями.
Внимательный читатель заметил в
описании класса
Pet
новое слово
abstract
.
Класс
Pet
и метод
voice()
являются
абстрактными.
Рис. 2.1.
Результат
выполнения программы
Chorus