
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ.
Наследование
Класс ( подкласс) может наследовать переменные и методы другого класса (суперкласса), используя ключевое слово extends. Подкласс имеет доступ ко всем открытым переменным и методам (кроме private) родительского класса, как будто они находятся в подклассе. В то же время подкласс может иметь методы с тем же именем, параметрами и возвращаемым значением, что и методы суперкласса. В этом случае подкласс переопределяет методы родительского класса. Это часть механизма ООП, который называется полиморфизмом. В следующем примере переопределяемый метод show() находится в двух классах Bird и Eagle. По принципу полиморфизма вызывается метод, наиболее близкий к текущему объекту.
/* пример # 1 : наследование класса и переопределение
метода : BirdSample.java */
class Bird {
private String name;
private float price;
public Bird(float p, String str) { //конструктор
name = str;
price = p;
}
public float getPrice(){
return price;
}
public g getName(){ Strin
return name;
}
void show(){
System.out.println("название: " + name
+ ", цена: " + price);
}
}
class extends Bird { Eagle
private boolean fly;
public Eagle(float p, String str, boolean f) {
super(p, str); //вызов конструктора суперкласса
fly = f;
}
void show(){
System.out.println("название:" + getName()
+ ", цена: " + getPrice() + ", полет:" + fly);
}
}
public class BirdSample {
public static void main(String[] args) {
Bird b1 = new Bird(0.85F, "Гусь");
Bird b2 = new Eagle(10.55F, "Белый Орел", true);
b1.show(); // вызов show() класса Bird
b2.show(); // вызов show() класса Eagle
}
}
Объект b1 создается при помощи вызова конструктора класса Bird, и, соответственно, при вызове метода show() вызывается версия метода из класса Bird. При создании объекта b2 ссылка типа Bird инициализируется объектом типа Eagle. При таком способе инициализации ссылка на суперкласс получает доступ к методам, переопределенным в подклассе. При объявлении совпадающих по сигнатуре полей в суперклассе и подклассах их значения не переопределяются и никак не пересекаются, то есть существуют в одном объекте независимо друг от друга. В этом случае задача извлечения требуемого значения определенного поля, принадлежащего классу в цепочке наследования, ложится на программиста.
/* пример # 2 : доступ к полям с одинаковыми именами
при наследовании : DemoAB.java */
class A {
int x = 1, y = 2;
public A() {
y = getX();
System.out.println("в классе A после вызова"
+ " getX() x=" + x + " y=" + y);
}
public int getX(){
System.out.println("в классе A");
return x;
}
}
class B extends A {
int x = 3, y = 4;
public B() {
System.out.println("в классе B x=" + x
+ " y=" + y);
}
public int getX(){
System.out.println("в классе B");
return x;
}
}
public class DemoAB {
public static void main (String[] args) {
A objA = new B();
B objB = new B();
System.out.println(objA.x);
System.out.println(objB.x);
}
}
В результате выполнения данного кода последовательно будет выведено:
в классе B
в классе A после вызова getX() x=1 y=0
в классе B x=3 y=4
в классе B
в классе A после вызова getX() x=1 y=0
в классе B x=3 y=4
x=1
x=3
В случае создания объекта objA инициализацией ссылки на класс А объектом класса В был получен доступ к полю х класса А. Во втором случае при создании объекта objB класса В был получен доступ к полю х класса В. Однако, воспользовавшись преобразованием типов вида: ((B)objA).x или ((A)objB).x, легко можно получить доступ к полю х из соответствующего класса. Одну из сторон полиморфизма методов иллюстрирует конструктор класса А в виде:
public A() {
y = getX();
}
Метод getX() содержится как в классе A, так и в классе В. При создании объекта класса В одним из способов:
A objA = new B();
B objB = new B();
в любом случае сначала вызывается конструктор класса А. Но так как создается объект класса В, то вызывается метод getX(), соответственно принадлежащий классу В, который в свою очередь оперирует полем х, еще не проинициализированным для класса В. В результате у получит значение х по умолчанию, т.е. нуль. Нельзя создать подкласс для класса, объявленного со спецификатором final:
// класс First не может быть суперклассом
final class First {/*код*/}
// сл класс н едующий евозможен
class Second extends First{/*код*/}
Использование super и this Ключевое слово super используется для вызова конструктора суперкласса и для доступа к члену суперкласса. Например:
super(список_параметров); /* вызов конструктора суперкласса с передачей параметров или без нее*/
super.i = n; /* обращение к атрибуту суперкласса */
super.methodName(); // вызов метода суперкласса
Вторая форма super подобна ссылке this на экземпляр класса. Третья форма специфична для Java и обеспечивает вызов переопределенного метода, причем если в суперклассе этот метод не определен, то будет осуществляться поиск по цепочке наследования до тех пор, пока метод не будет найден. Каждый экземпляр класса имеет неявную ссылку this на себя, которая передается также и методам. После этого можно, например, вместо артибута price писать this.price, хотя и не обязательно. Следующий код показывает, как, используя this, можно строить одни конструкторы на основе других.
// пример # 3 : this в конструкторе : Locate3D.java
class a D { Loc te3
private int x, y, z;
public Locate3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
public cate3D() { Lo
this(-1, -1, -1);
}
}
В этом классе второй конструктор для завершения инициализации объекта обращается к первому конструктору. Такая конструкция применяется в случае, когда в классе имеется несколько конструкторов и требуется добавить конструктор по умолчанию. Ссылка this используется в методе для уточнения того, о каких именно переменных x и y идет речь в каждом отдельном случае, а конкретно для доступа к переменной класса из метода, если в методе есть локальная переменная с тем же именем. Инструкция this() должна быть единственной в вызывающем конструкторе и быть первой по счету выполняемой операцией.
Переопределение методов и полиморфизм
Способность Java делать выбор метода, исходя из типа времени выполнения, называется динамическим полиморфизмом. Поиск метода происходит сначала в данном классе, затем в суперклассе, пока метод не будет найден или не достигнут Object – суперкласс для всех классов.
Отметим, что статические методы могут быть переопределены в подклассе, но не могут быть полиморфными, так как их вызов не затрагивает объекты.
Если два метода с одинаковыми именами и возвращаемыми значениями находятся в одном классе, то списки их параметров должны отличаться. Такие методы являются перегружаемыми (overloading). Если метод подкласса совпадает с методом суперкласса (порождающего класса), то метод подкласса переопределяет (overriding) метод суперкласса. Все методы Java являются виртуальными (ключевое слово virtual, как в C++, не используется). Переопределение методов является основой концепции динамического связывания, реализующей полиморфизм. Когда переопределенный метод вызывается через ссылку суперкласса, Java определяет, какую версию метода вызвать, основываясь на типе объекта, на который имеется ссылка. Таким образом, тип объекта определяет версию метода на этапе выполнения. В следующем примере рассматривается реализация полиморфизма на основе динамического связывания. Так как суперкласс содержит методы, переопределенные подклассами, то объект суперкласса будет вызывать методы различных подклассов, в зависимости от того, на объект какого подкласса у него имеется ссылка.
/* пример # 4 : динамическое связывание методов :
DynDispatch.java */
class A {
int i, j;
public A(int a, int b) {
i = a;
j = b;
}
void show() { // вывод i и j
System.out.println("i и j: " + i + " " + j);
}
}
class extends A { B
; int k
public int a, int b, int c) { B(
super(a, b);
k = c;
}
void show() {
/* вывод k: переопределенный метод show() из A */
super.show(); // вывод значений из A
System.out.println("k: " + k);
}
}
class C extends B {
int m;
public int a, int b, int c, int d) { C(
super(a, b, c);
m = d;
}
void show() {
/* вывод m: переопределенный метод show() из B */
super.show(); //вывод значений из B
// show();
/*нельзя!!! метод будет вызывать сам себя, что приве-
дет к ошибке во время выполнения */
System.out.println("m: " + m);
}
}
public class DynDispatch {
public static void main(String[] args) {
A Aob;
B Bob = new B(1, 2, 3);
C Cob = new C(5, 6, 7, 8);
Aob = Bob; // установка ссылки на Bob
Aob.show(); // вызов show() из B
System.out.println();
Aob = Cob; // установка ссылки на Cob
Aob.show(); // вызов show() из C
}
}
Результат:
i и j: 1 2
k: 3
i и j : 5 6
k:7
m: 8
Следует помнить, что при вызове show() обращение super всегда происходит к ближайшему суперклассу.