Вызов метода
В 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