Н.А. Вязовик - Программирование на Java
- Название:Программирование на Java
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Н.А. Вязовик - Программирование на Java краткое содержание
Программирование на Java - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Рассмотрим такое свойство живого организма, как способность питаться насекомыми. Очевидно, что это свойство нельзя приписать всей группе птиц, или млекопитающих, а тем более растений. Но существуют представители каждой из названных групп, которые этим свойством обладают, – для растений это росянка, для птиц, например, ласточки, а для млекопитающих – муравьеды. Причем, очевидно, "реализовано" это свойство у каждого вида совсем по-разному.
Можно было бы объявить соответствующий метод (скажем, consumeInsect(Insect) ) у каждого представителя независимо. Но если задача состоит в моделировании, например, зоопарка, то однотипную процедуру – кормление насекомыми – пришлось бы описывать для каждого вида отдельно, что существенно осложнило бы код, причем без какой-либо пользы.
Java предлагает другое решение. Объявляется интерфейс InsectConsumer:
public interface InsectConsumer {
void consumeInsect(Insect i);
}
Его реализуют все подходящие животные и растения:
// росянка расширяет класс растение
public class Sundew extends
Plant implements InsectConsumer {
public void consumeInsect(Insect i) {
...
}
}
// ласточка расширяет класс птица
public class Swallow extends
Bird implements InsectConsumer {
public void consumeInsect(Insect i) {
...
}
}
// муравьед расширяет класс млекопитающее
public class AntEater extends
Mammal implements InsectConsumer {
public void consumeInsect(Insect i) {
...
}
}
В результате в классе, моделирующем служащего зоопарка, можно объявить соответствующий метод:
// служащий, отвечающий за кормление,
// расширяет класс служащий
class FeedWorker extends Worker {
// с помощью этого метода можно накормить
// и росянку, и ласточку, и муравьеда
public void feedOnInsects(InsectConsumer
consumer) {
... consumer.consumeInsect(insect);
...
}
}
В результате удалось свести работу с одним свойством трех разнородных классов в одно место, сделать код более универсальным. Обратите внимание, что при добавлении еще одного насекомоядного такая модель зоопарка не потребует никаких изменений, чтобы обслуживать новый вид, в отличие от первоначального громоздкого решения. Благодаря введению интерфейса удалось отделить классы, реализующие его (живые организмы) и использующие его (служащий зоопарка). После любых изменений этих классов при условии сохранения интерфейса их взаимодействие не нарушится.
Данный пример иллюстрирует, как интерфейсы предоставляют альтернативный, более строгий и гибкий подход вместо множественного наследования.
Полиморфизм
Ранее были рассмотрены правила объявления классов с учетом их наследования. В этой лекции было введено понятие переопределенного метода. Однако полиморфизм требует более глубокого изучения. При объявлении одноименных полей или методов с совпадающими сигнатурами происходит перекрытие элементов из родительского и наследующего класса. Рассмотрим, как функционируют классы и объекты в таких ситуациях.
Поля
Начнем с полей, которые могут быть статическими или динамическими. Рассмотрим пример:
class Parent {
int a=2;
}
class Child extends Parent {
int a=3;
}
Прежде всего, нужно сказать, что такое объявление корректно. Наследники могут объявлять поля с любыми именами, даже совпадающими с родительскими. Затем, необходимо понять, как два одноименных поля будут сосуществовать. Действительно, объекты класса Child будут содержать сразу две переменных, а поскольку они могут отличаться не только значением, но и типом (ведь это два независимых поля), именно компилятор будет определять, какое из значений использовать. Компилятор может опираться только на тип ссылки, с помощью которой происходит обращение к полю:
Child c = new Child();
System.out.println(c.a);
Parent p = c;
System.out.println(p.a);
Обе ссылки указывают на один и тот же объект, порожденный от класса Child, но одна из них имеет такой же тип, а другая – Parent. Отсюда следуют и результаты:
3
2
Объявление поля в классе-наследнике "скрыло" родительское поле. Данное объявление так и называется – "скрывающим" (hiding). Это особый случай перекрытия областей видимости, отличный от "затеняющего" (shadowing) и "заслоняющего" (obscuring) объявлений. Тем не менее, родительское поле продолжает существовать. К нему можно обратиться и явно:
class Child extends Parent {
int a=3;
// скрывающее объявление
int b=((Parent)this).a;
// более громоздкое объявление
int c=super.a;
// более простое
}
Переменные b и c получат значение, хранящееся в родительском поле a. Хотя выражение с super более простое, оно не позволит обратиться на два уровня вверх по дереву наследования. А ведь вполне возможно, что в родительском классе это поле также было скрывающим и в родителе родителя хранится еще одно значение. К нему можно обратиться явным приведением, как это делается для b.
Рассмотрим следующий пример:
class Parent {
int x=0;
public void printX() {
System.out.println(x);
}
}
class Child extends Parent {
int x=-1;
}
Каков будет результат следующих строк?
new Child().printX();
Значение какого поля будет распечатано? Метод вызывается с помощью ссылки типа Child, но это не сыграет никакой роли. Вызывается метод, определенный в классе Parent, и компилятор, конечно, расценил обращение к полю x в этом методе именно как к полю класса Parent. Поэтому результатом будет 0.
Перейдем к статическим полям. На самом деле, для них проблем и конфликтов, связанных с полиморфизмом, не существует.
Рассмотрим пример:
class Parent {
static int a=2;
}
class Child extends Parent {
static int a=3;
}
Каков будет результат следующих строк?
Child c = new Child();
System.out.println(c.a);
Parent p = c;
System.out.println(p.a);
Нужно вспомнить, как компилятор обрабатывает обращения к статическим полям через ссылочные значения. Неважно, на какой объект указывает ссылка. Более того, она может быть даже равна null. Все определяется типом ссылки.
Поэтому рассматриваемый пример эквивалентен:
System.out.println(Child.a)
System.out.println(Parent.a)
А его результат сомнений уже не вызывает:
3
2
Можно привести следующее пояснение. Статическое поле принадлежит классу, а не объекту. В результате появление классов-наследников со скрывающими (hiding) объявлениями никак не сказывается на работе с исходным полем. Компилятор всегда может определить, через ссылку какого типа происходит обращение к нему.
Обратите внимание на следующий пример:
class Parent {
static int a;
}
class Child extends Parent {
}
Каков будет результат следующих строк?
Child.a=10;
Parent.a=5;
System.out.println(Child.a);
В этом примере поле a не было скрыто и передалось по наследству классу Child. Однако результат показывает, что это все же одно поле:
5
Несмотря на то, что к полю класса идут обращения через разные классы, переменная всего одна.
Итак, наследники могут объявлять поля с именами, совпадающими с родительскими полями. Такие объявления называют скрывающими. При этом объекты будут содержать оба значения, а компилятор будет каждый раз определять, с каким из них надо работать.
Читать дальшеИнтервал:
Закладка: