Глава 7 Классы
Red World

Глава 7 Классы

Базовым элементом объектно-ориентирован­ного программирования в языке Java являет­ся класс. В этой главе Вы научитесь создавать и расширять свои собственные классы, работать с экземплярами этих классов и начнете использовать мощь объектно-ориентированного подхода. Напомним, что классы в Java не обязательно должны содержать метод main. Единственное назначение этого метода — указать интерпретатору Java, откуда надо начинать выполнение программы. Для того, чтобы создать класс, достаточно иметь исходный файл, в котором будет присутствовать ключевое слово class, и вслед за ним — допустимый идентификатор и пара фигурных скобок для его тела.

class Point {

}

 

ЗАМЕЧАНИЕ

Имя исходного файла Java должно соответствовать имени хранящегося в нем класса. Регистр букв важен и в имени класса, и в имени файла.

 

Как вы помните из главы 2, класс — это шаблон для создания объекта. Класс определяет структуру объекта и его методы, образующие функциональный интерфейс. В процессе выполнения Java-программы система использует определения классов для создания представителей классов. Представители являются реальными объектами. Термины «представитель», «экземпляр» и «объект» взаимозаменяемы. Ниже приведена общая форма определения класса.

 

class имя_класса extends имя_суперкласса { type переменная1_объекта:

type переменная2_объекта:

type переменнаяN_объекта:

type имяметода1(список_параметров) { тело метода;

}

type имяметода2(список_параметров) { тело метода;

}

type имя методаМ(список_параметров) { тело метода;

}

}

 

Ключевое слово extends указывает на то, что «имя_класса» — это подкласс класса «имя_суперкласса». Во главе классовой иерархии Java стоит единственный ее встроенный класс — Object. Если вы хотите создать подкласс непосредственно этого класса, ключевое слово extends и следующее за ним имя суперкласса можно опустить — транслятор включит их в ваше определение автома­тически. Примером может служить класс Point, приведенный ранее.

 

Переменные представителей (instance variables)

Данные инкапсулируются в класс путем объявления переменных между открывающей и закрывающей фигурными скобками, выделяющи­ми в определении класса его тело. Эти переменные объявляются точно так же, как объявлялись локальные переменные в предыдущих примерах. Единст­венное отличие состоит в том, что их надо объявлять вне методов, в том числе вне метода main. Ниже приведен фрагмент кода, в котором объявлен класс Point с двумя переменными типа int.

class Point { int х, у;

}

В качестве типа для переменных объектов можно использовать как любой из простых типов, описанных в главе 4, так и классовые типы. Скоро мы добавим к приведенному выше классу метод main, чтобы его можно было запустить из командной строки и создать несколько объек­тов.

 

Оператор new

Оператор new создает экземпляр указанного класса и возвращает ссылку на вновь созданный объект. Ниже приведен пример создания и присваивание переменной р экземпляра класса Point.

Point р = new Point();

Вы можете создать несколько ссылок на один и тот же объект. Приведенная ниже программа создает два раз­личных объекта класса Point и в каждый из них заносит свои собст­венные значения. Оператор точка используется для доступа к переменным и методам объекта.

 

class TwoPoints {

public static void main(String args[]) {

Point p1 = new Point();

Point p2 = new Point();

p1.x = 10;

p1.y = 20;

р2.х = 42;

р2.у = 99;

System.out.println("x = " + p1.x + " у = " + p1.y);

System.out.println("x =  " + р2.х + " у = " + р2.у);

} }

 

В этом примере снова использовался класс Point, было создано два объекта этого класса, и их переменным х и у присвоены различные зна­чения. Таким образом мы продемонстрировали, что переменные различ­ных объектов независимы на самом деле. Ниже приведен результат, полученный при выполнении этой программы.

С:\> Java TwoPoints

х = 10 у = 20

х = 42 у = 99

 

замечание

Поскольку при запуске интерпретатора мы указали в командной строке не класс Point, а класс TwoPoints, метод main класса Point был полностью проигнорирован. Добавим в класс Point метод main и, тем самым, получим закончен­ную программу.

 

class Point { int х, у;

public static void main(String args[]) {

Point p = new Point();

р.х = 10;

p.у = 20;

System.out.println("x =  " + р.х + " у = " + p.y);

} }

 

Объявление методов

Методы - это подпрограммы, присоединенные к кон­кретным определениям классов. Они описываются внутри определения класса на том же уровне, что и переменные объектов. При объявлении метода задаются тип возвращаемого им результата и список параметров. Общая форма объявления метода такова:

тип имя_метода (список формальных параметров) {

тело метода:

}

Тип результата, который должен возвращать метод может быть любым, в том числе и типом void - в тех случаях, когда возвращать результат не требуется. Список формальных параметров - это последова­тельность пар тип-идентификатор, разделенных запятыми. Если у метода параметры отсутствуют, то после имени метода должны стоять пустые круглые скобки.

class Point { int х, у;

void init(int a, int b) {

х = а;

У = b;

} }

 

Вызов метода

В Java отсутствует возможность передачи параметров по ссылке на  примитивный тип. В Java все пара­метры примитивных типов передаются по значению, а это означает, что у метода нет доступа к исходной переменной, использованной в качестве параметра. Заметим, что все объекты передаются по ссылке, можно изменять содержимое того объекта, на который ссыла­ется данная переменная. В главе 12 Вы узнаете, как предать переменные примитивных типов по ссылке (через обрамляющие классы-оболочки).

 

Скрытие переменных представителей

В языке Java не допускается использование в одной или во вложен­ных областях видимости двух локальных переменных с одинаковыми именами. Интересно отметить, что при этом не запрещается объявлять формальные параметры методов, чьи имена совпадают с именами переменных представителей. Давайте рассмотрим в качестве примера иную версию метода init, в которой формальным пара­метрам даны имена х и у, а для доступа к одноименным переменным текущего объекта используется ссылка this.

class Point { int х, у;

void init(int х, int у) {

this.x = х;

this.у = у } }

class TwoPointsInit {

public static void main(String args[]) {

Point p1 = new Point();

Point p2 = new Point();

p1.init(10,20);

p2.init(42,99);

System.out.println("x = " + p1.x + " у = •• + p-l.y);

System.out.printlnC'x = " + p2.x + " у = •• + p2.y);

} }

 

Конструкторы

Инициализировать все переменные класса всякий раз, когда создается его очередной представитель — довольно утомительное дело даже в том случае, когда в классе имеются функции, подобные методу init. Для этого в Java предусмотрены специальные методы, называемые конструкторами. Конструктор — это метод класса, который инициали­зирует новый объект после его создания. Имя конструктора всегда со­впадает с именем класса, в котором он расположен (также, как и в C++). У конструкторов нет типа возвращаемого результата - никакого, даже void. Заменим метод init из предыду­щего примера конструктором.

 

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

} }

class PointCreate {

public static void main(String args[]) {

Point p = new Point(10,20);

System.out.println("x = " + p.x + " у = " + p.у);

} }

 

Программисты на Pascal (Delphi) для обозначения конструктора используют ключевое слово constructor.

 

Совмещение методов

Язык Java позволяет создавать несколько методов с одинаковыми именами, но с разными списками параметров. Такая техника называется совмещением методов (method overloading). В качестве примера при­ведена версия класса Point, в которой совмещение методов использовано для определения альтернативного конструктора, который инициализиру­ет координаты х и у значениями по умолчанию (-1).

 

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

}

Point() {

х = -1;

у = -1;

} }

class PointCreateAlt {

public static void main(String args[]) {

Point p = new Point();

System.out.println("x = " + p.x + " у = " + p.y);

} }

 

В этом примере объект класса Point создается не при вызове первого конструктора, как это было раньше, а с помощью второго конструктора без параметров. Вот результат работы этой программы:

С:\> java PointCreateAlt

х = -1 у = -1

 

ЗАМЕЧАНИЕ

Решение о том, какой конструктор нужно вызвать в том или ином случае, принимается в соответствии с количеством и типом параметров, указанных в операторе new. Недопустимо объявлять в классе методы с одинаковыми именами и сигнатурами. В сигнатуре метода не учитываются имена формальных параметров учитываются лишь их типы и количество.

 

this в конструкторах

Очередной вариант класса Point показывает, как,       используя this и со­вмещение методов, можно строить одни конструкторы на основе других.

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

}

Point() {

this(-1, -1);

} }

В этом примере второй конструктор для завершения инициализации объекта обращается к первому конструктору.

 

Методы, использующие совмещение имен, не обязательно должны быть конструкторами. В следующем примере в класс Point добавлены два метода distance. Функция distance возвращает расстояние между двумя точками. Одному из совмещенных методов в качестве параметров передаются координаты точки х и у, другому же эта информация пере­дается в виде параметра-объекта Point.

 

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this. y = y;

}

double distance(int х, int у) {

int dx = this.x - х;

int dy = this.у - у;

return Math.sqrt(dx*dx + dy*dy);

}

double distance(Point p) {

return distance(p.x, p.y);

} }

class PointDist {

public static void main(String args[]) {

Point p1 = new Point(0, 0);

Point p2 = new Point(30, 40);

System.out.println("p1 = " + pi.x + ", " + p1.y);

System.out.println("p2 = " + p2.x + ", " + p2.y);

System.out.println("p1.distance(p2) = " + p1.distance(p2));

System.out.println("p1.distance(60, 80) = " + p1.distance(60, 80));

} }

 

Обратите внимание на то как во второй фороме метода distance для получения результата вызывается его первая форма. Ниже приведен результат работы этой программы:

С:\> java PointDist

р1 = 0, 0

р2 = 30, 40

р1.distance(p2) = 50.0

p1.distance(60, 80) = 100.0

 

Наследование

Вторым фундаментальным свойством объектно-ориентированного под­хода является наследование (первый – инкапсуляция). Классы-потомки имеют возможность не только создавать свои собственные переменные и  методы, но и наследовать переменные и методы классов-предков. Классы-потомки принято называть подклассами. Непосредственного предка данного класса называют его суперклассом. В очередном примере показано, как расширить класс Point таким образом, чтобы включить в него третью координату z.

class Point3D extends Point { int z;

Point3D(int x, int y, int z) {

this.x = x;

this.у = у;

this.z = z; }

Point3D() {

this(-1,-1,-1);

} }

 

В этом примере ключевое слово extends используется для того, чтобы сообщить транслятору о намерении создать подкласс класса Point. Как видите, в этом классе не понадобилось объявлять переменные х и у, по­скольку Point3D унаследовал их от своего суперкласса Point.

 

ВНИМАНИЕ

Вероятно, программисты, знакомые с C++, очевидно ожидают, что сей­час мы начнем обсуждать концепцию множественного наследования. Под множественным наследованием понимается создание класса, имеющего несколько суперклассов. Однако в языке Java ради обеспечения высокой производительности и большей ясности исходного кода множественное наследование реализовано не было. В большинстве случаев, когда требуется множественное наследование, проблему можно решить с помощью имеющегося в Java механизма интерфейсов, описанного в следующей  главе. 

 

super

В примере с классом Point3D частично повторялся код, уже имев­шийся в суперклассе. Вспомните, как во втором конструк­торе мы использовали this для вызова первого конструктора того же класса. Аналогичным образом ключевое слово super позволяет обратить­ся непосредственно к конструктору суперкласса (в Delphi / С++ для этого используется ключевое слово inherited).

 

class Point3D extends Point { int z;

Point3D(int x, int у, int z) {

super(x, y);     // Здесь мы вызываем конструктор суперкласса this.z=z;

public static void main(String       args[]) {

Point3D p = new Point3D(10, 20, 30);

System.out.println( " x = " +  p.x + " y = " + p.y +

                    " z = " + p.z);

} }

 

Вот результат работы этой программы:

С:\> java Point3D

x = 10 у = 20 z = 30

 

Замещение методов

Новый подкласс Point3D класса Point наследует реализацию метода distance своего суперкласса (пример PointDist.java). Проблема заключается в том, что в классе Point уже определена версия метода distance(mt х, int у), которая возвращает обычное расстояние между точ­ками на плоскости. Мы должны заместить (override) это определение метода новым, пригодным для случая трехмерного пространства. В сле­дующем примере проиллюстрировано и совмещение (overloading), и за­мещение (overriding) метода distance.

 

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

}

double distance(int х, int у) {

int dx = this.x - х;

int dy = this.у - у:

return Math,sqrt(dx*dx + dy*dy);

}

double distance(Point p) {

return distance(p.х, p.y);

}

}

class  Point3D extends Point { int z;

Point3D(int х, int y, int z)   {

super(x, y);

this.z = z;

(

double distance(int х, int y,  int z) {

int dx = this.x - х;

int dy = this.y - y;

int dz = this.z -  z;

return Math.sqrt(dx*dx + dy*dy + dz*dz);

}

double distance(Point3D other) {

return distance(other.х, other.y, other.z);

}

double distance(int х, int y)  {

double dx = (this.x / z) - х;

double dy = (this.у / z) - y;

return Math.sqrt(dx*dx + dy*dy);

}

}

class Point3DDist {

public static void main(String args[]) {

Point3D p1 = new Point3D(30, 40, 10);

Point3D p2 = new Point3D(0, 0, 0);

Point p = new Point(4, 6);

System.out.println("p1 = " + p1.x +  ", " + p1.y + ", " + p1.z);

System.out.println("p2 = " + p2.x +  ", " + p2.y + ", " + p2.z);

System.out.println("p = " + p.x + ", " + p.y);

System.out.println("p1.distance(p2) = " + p1.distance(p2));

System.out.println("p1.distance(4, 6) = " + p1.distance(4, 6));

System.out.println("p1.distance(p) = " + p1.distance(p));

} }

 

Ниже приводится результат работы этой программы:

С:\> Java Point3DDist

p1 = 30, 40, 10

р2 = 0, 0, 0 

р = 4, 6

p1.distance(p2) = 50.9902

p1.distance(4, 6) = 2.23607

p1.distance(p) = 2.23607

 

Обратите внимание — мы получили ожидаемое расстояние между трехмерными точками и между парой двумерных точек. В примере используется механизм, который называется динамическим назначением методов (dynamic method dispatch).

 

Динамическое назначение методов

Давайте в качестве примера рассмотрим два класса, у которых имеют простое родство подкласс / суперкласс, причем единственный метод суперкласса замещен в подклассе.                    

 

class A { void callme() {

System.out.println("Inside A's callrne method");

class В extends A { void callme() {

System.out.println("Inside B's callme method");

} }

class Dispatch {

public static void main(String args[]) {

A a = new B();

a.callme();

} }

 

Обратите внимание — внутри метода main мы объявили переменную а класса А, а проинициализировали ее ссылкой на объект класса В. В следующей строке мы вызвали метод callme. При этом транслятор про­верил наличие метода callme у класса А, а исполняющая система, уви­дев, что на самом деле в переменной хранится представитель класса В, вызвала не метод класса А, а callme класса В. Ниже приведен результат работы этой программы:

С:\> Java Dispatch

Inside B's calime method

 

СОВЕТ

Программистам Delphi / C++ следует отметить, что все Java по умолчанию являются виртуальными функциями (ключевое слово virtual).

 

Рассмотренная форма динамического полиморфизма времени выполнения представляет собой один из наиболее мощных механизмов объектно-ориентированного программирования, позволяющих писать надеж­ный, многократно используемый код.

 

final

Все методы и переменные объектов могут быть замещены по умолча­нию. Если же вы хотите объявить, что подклассы не имеют права за­мещать какие-либо переменные и методы вашего класса, вам нужно объ­явить их как finalDelphi / C++ не писать слово virtual).

final int FILE_NEW = 1;

По общепринятому соглашению при выборе имен переменных типа final — используются только символы верхнего регистра (т.е. используются как аналог препроцерных констант C++). Использование final-методов порой приводит к выигрышу в скорости выполнения кода — поскольку они не могут быть замещены, транслятору ничто не мешает заменять их вызовы встроенным (in-line) кодом (байт-код копируется непосредственно в код вызывающего метода).

 

finalize

В Java существует возможность объявлять методы с именем finalize. Методы finalize аналогичны деструкторам в C++ (ключевой знак ~) и Delphi (ключевое слово destructor). Исполняющая среда Java будет вызывать его каждый раз, когда сборщик мусора соберется уничтожить объект этого класса.

 

static

Иногда требуется создать метод, который можно было бы использо­вать вне контекста какого-либо объекта его класса. Так же, как в случае main, все, что требуется для создания такого метода — указать при его объ­явлении модификатор типа static. Статические методы могут непосред­ственно обращаться только к другим статическим методам, в них ни в каком виде не допускается использование ссылок this и super. Перемен­ные также могут иметь тип static, они подобны глобальным перемен­ным, то есть доступны из любого места кода. Внутри статических методов недопустимы ссылки на переменные представителей. Ниже приведен пример класса, у которого есть статические переменные, статический метод и статический блок инициализации.

 

class Static {

static int a = 3;

static int b;

static void method(int x) {

System.out.println("x = " + x);

System.out.println("a = " + a);

System.out.println("b = " + b);

}

static {

System.out.println("static block initialized");

b = a * 4;

}

public static void main(String args[]) {

method(42);

} }

 

Ниже приведен результат запуска этой программы.

С:\> java Static static block initialized

Х = 42

А = 3

B = 12

 

В следующем примере мы создали класс со статическим методом и несколькими статическими переменными. Второй класс может вызывать статический метод по имени и ссылаться на статические переменные непосредственно через имя класса.

 

class StaticClass {

static int a = 42;

static int b = 99;

static void callme() {

System.out.println("a = " + a);

} }

class StaticByName {

public static void main(String args[]) {

StaticClass.callme();

System.out.println("b = " + StaticClass.b);

} }

 

А вот и результат запуска этой программы:

С:\> Java StaticByName

а = 42 b = 99

 

abstract

Бывают ситуации, когда нужно определить класс, в котором задана структура какой-либо абстракции, но полная реализация всех методов от­сутствует. В таких случаях вы можете с помощью модификатора типа ab­stract объявить, что некоторые из методов обязательно должны быть заме­щены в подклассах. Любой класс, содержащий методы abstract, также должен быть объявлен, как abstract. Поскольку у таких классов отсутствует полная реализация, их представи­телей нельзя создавать с помощью оператора new. Кроме того, нельзя объ­являть абстрактными конструкторы и статические методы. Любой под­класс абстрактного класса либо обязан предоставить реализацию всех абстрактных методов своего суперкласса, либо сам должен быть объявлен абстрактным.

 

abstract class A {

abstract void callme();

void metoo() {

System.out.println("Inside A's metoo method");

} }

class B extends A {

void callme() {

System.out.println("Inside B's callme method");

} }

class Abstract {

public static void main(String args[]) {

A a = new B():

a.callme():

a.metoo():

} }

 

В нашем примере для вызова реализованного в под­классе класса А метода callme и реализованного в классе А метода metoo используется динамическое назначение методов, которое мы обсуждали раньше.

 

С:\> Java Abstract

Inside B's callrne method Inside A's metoo method

 

Классическое заключение

В этой главе вы научились создавать классы, конструкторы и методы. Вы осознали разницу между совмещением (overloading) и замещением (overriding) методов. Специальные переменные this и super помогут вам сослаться на текущий объект и на его суперкласс. В ходе эволюции языка Java стало ясно, что в язык нужно ввести еще несколько организационных механизмов - возможности более динамич­ного назначения методов и возможности более тонкого управления про­странством имен класса и уровнями доступа к переменным и методам объектов. Оба этих механизма - интерфейсы и пакеты, описаны в сле­дующей главе.

 




Сайт создан в системе uCoz