|
Базовым
элементом объектно-ориентированного
программирования в языке Java является
класс. В этой главе Вы
научитесь создавать и расширять свои
собственные классы, работать с
экземплярами этих классов и начнете
использовать мощь объектно-ориентированного
подхода. Напомним, что классы в Java не
обязательно должны содержать метод main.
Единственное назначение этого метода —
указать интерпретатору Java, откуда надо
начинать выполнение программы. Для того,
чтобы создать класс, достаточно иметь
исходный файл, в котором будет
присутствовать ключевое слово class, и вслед
за ним — допустимый идентификатор и пара
фигурных скобок для его тела.
class Point {
}
ЗАМЕЧАНИЕ
Имя
исходного файла Java должно соответствовать
имени хранящегося в нем класса. Регистр
букв важен и в имени класса, и в имени файла.
Как вы
помните класс — это шаблон для
создания объекта. Класс определяет
структуру объекта и его методы,
образующие функциональный интерфейс. В
процессе выполнения 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 х,
у;
}
В качестве типа
для переменных объектов можно
использовать как любой из простых типов, так и классовые типы. Скоро мы
добавим к приведенному выше классу метод
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 все параметры
примитивных типов передаются по
значению, а это означает, что у метода
нет доступа к исходной переменной,
использованной в качестве параметра. Заметим, что все объекты передаются
по ссылке, можно изменять содержимое того
объекта, на который ссылается данная
переменная.
Скрытие
переменных представителей
В языке 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
Все методы и
переменные объектов могут быть замещены
по умолчанию. Если же вы хотите объявить,
что подклассы не имеют права замещать
какие-либо переменные и методы вашего
класса, вам нужно объявить их как final
(в Delphi / 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
Бывают ситуации, когда нужно
определить класс, в котором задана
структура какой-либо абстракции, но полная
реализация всех методов отсутствует. В
таких случаях вы можете с помощью
модификатора типа abstract объявить, что
некоторые из методов обязательно должны
быть замещены в подклассах. Любой класс,
содержащий методы 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
стало ясно, что в язык нужно ввести еще
несколько организационных механизмов -
возможности более динамичного
назначения методов и возможности более
тонкого управления пространством имен
класса и уровнями доступа к переменным и
методам объектов.
|