Чтение онлайн

на главную - закладки

Жанры

Программирование на Java

Вязовик Н.А.

Шрифт:

Поля

Начнем с полей, которые могут быть статическими или динамическими. Рассмотрим пример:

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

Несмотря на то, что к полю класса идут обращения через разные классы, переменная всего одна.

Итак, наследники могут объявлять поля с именами, совпадающими с родительскими полями. Такие объявления называют скрывающими. При этом объекты будут содержать оба значения, а компилятор будет каждый раз определять, с каким из них надо работать.

Методы

Рассмотрим случай переопределения (overriding) методов:

class Parent {

public int getValue {

return 0;

}

}

class Child extends Parent {

public int getValue {

return 1;

}

}

И строки, демонстрирующие работу с этими методами:

Child c = new Child;

System.out.println(c.getValue);

Parent p = c;

System.out.println(p.getValue);

Результатом будет:

1

1

Можно видеть, что родительский метод полностью перекрыт, значение 0 никак нельзя получить через ссылку, указывающую на объект класса Child. В этом ключевая особенность полиморфизма – наследники могут изменять родительское поведение, даже если обращение к ним производится по ссылке родительского типа. Напомним, что, хотя старый метод снаружи уже недоступен, внутри класса-наследника к нему все же можно обратиться с помощью super.

Рассмотрим более сложный пример:

class Parent {

public int getValue {

return 0;

}

public void print {

System.out.println(getValue);

}

}

class Child extends Parent {

public int getValue {

return 1;

}

}

Что появится на консоли после выполнения следующих строк?

Parent p = new Child;

p.print;

С помощью ссылки типа Parent вызывается метод print, объявленный в классе Parent. Из этого метода делается обращение к getValue, которое в классе Parent возвращает 0. Но компилятор уже не может предсказать, к динамическому методу какого класса произойдет обращение во время работы программы. Это определяет виртуальная машина на основе объекта, на который указывает ссылка. И раз этот объект порожден от Child, то существует лишь один метод getValue.

Поделиться:
Популярные книги

Ловите конский топот. Том 1. Исхода нет, есть только выходы...

Звягинцев Василий Дмитриевич
15. Одиссей покидает Итаку
Фантастика:
альтернативная история
8.24
рейтинг книги
Ловите конский топот. Том 1. Исхода нет, есть только выходы...

Играть... в тебя

Зайцева Мария
3. Звериные повадки Симоновых
Любовные романы:
современные любовные романы
5.25
рейтинг книги
Играть... в тебя

Сексуальная жизнь мужчины. Книга 1

Еникеева Диля Дэрдовна
Дом и Семья:
образовательная литература
здоровье и красота
7.00
рейтинг книги
Сексуальная жизнь мужчины. Книга 1

Зарево горящих деревень

Инин Дмитрий
4. Негодяй Аллара
Фантастика:
фэнтези
рпг
5.00
рейтинг книги
Зарево горящих деревень

Рыцарь Империи

Буревой Андрей
3. Одержимый
Фантастика:
фэнтези
9.39
рейтинг книги
Рыцарь Империи

Больше чем Герой! Том 9

Довыдовский Кирилл Сергеевич
9. Путешествие Героя
Фантастика:
героическая фантастика
юмористическое фэнтези
городское фэнтези
рпг
5.00
рейтинг книги
Больше чем Герой! Том 9

"Фантастика 2023-121". Компиляция. Книги 1-25

Башибузук Александр
Фантастика 2023. Компиляция
Фантастика:
фэнтези
боевая фантастика
5.00
рейтинг книги
Фантастика 2023-121. Компиляция. Книги 1-25

Люсьен Левен (Красное и белое)

Стендаль Фредерик
Проза:
классическая проза
7.00
рейтинг книги
Люсьен Левен (Красное и белое)

Бояръ-Аниме. Газлайтер. Том 35

Володин Григорий Григорьевич
35. История Телепата
Фантастика:
аниме
боевая фантастика
фэнтези
6.40
рейтинг книги
Бояръ-Аниме. Газлайтер. Том 35

Миры Филипа Фармера. Том 4. Больше чем огонь. Мир одного дня

Фармер Филип Хосе
Фантастика:
научная фантастика
5.00
рейтинг книги
Миры Филипа Фармера. Том 4. Больше чем огонь. Мир одного дня

Гримуар темного лорда IV

Грехов Тимофей
4. Гримуар темного лорда
Фантастика:
фэнтези
боевая фантастика
попаданцы
аниме
6.00
рейтинг книги
Гримуар темного лорда IV

Первый Предтеча. Трилогия

Тарс Элиан
Первый Предтеча
Фантастика:
аниме
фэнтези
фантастика: прочее
попаданцы
5.00
рейтинг книги
Первый Предтеча. Трилогия

Спецоперации. Лубянка и Кремль 1930–1950 годы

Судоплатов Павел Анатольевич
Документальная литература:
биографии и мемуары
5.00
рейтинг книги
Спецоперации. Лубянка и Кремль 1930–1950 годы

Арканарский вор (Трилогия)

Шелонин Олег Александрович
Арканарский вор
Фантастика:
юмористическая фантастика
9.00
рейтинг книги
Арканарский вор (Трилогия)