Иллюстрированный самоучитель по 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


Содержание раздела