- Для чего нужно это наследование?
- Как использовать наследование?
- Доступность членов классов-предков в классах-наследниках
- Полезные ссылки
Внимание оговорки:
- 16:51 Доступен метод предка, а не потомка.
- 22:30 вместо слова "метод" подразумевается слово "класс".
В этом уроке Вы добавляете наследование в java в копилку своих знаний.
Код, примеры из которого приведены здесь, можно взять вот в этом репозитории.
Наследование — это механизм, который позволяет создавать классы на основе других классов. Это дает возможность расширять свойства наследуемого класса, и сохранять работоспособность ранее написанного кода.
- Все классы в мире java, косвенно или прямо, являются наследниками класса
Object
. - Наследоваться можно только от одного класса.
Для того чтобы некий класс стал наследником другого класса, в его объявлении применяется ключевое слово extends
и имя наследуемого класса. Вот как это выглядит в коде:
class Primate extends Animal {
// ....
}
Но мы не можем написать так: class Primate extends Animal, SomeClass, Foo {
. Наследоваться можно только от одного класса.
Член класса — это либо метод, либо поле класса.
Для чего нужно это наследование?
Наследование, как было сказано в видео, необходимо для того, чтобы не дублировать ранее написанный код, а использовать его же, но с новыми возможностями.
Для разнообразия приведу пример не на ветвлении класса Game
, а на упрощенной классификации животных.
Начнем с класса Animal
:
package com.critters;
public class Animal {
boolean isAlive;
int age;
// Геттеры и сеттеры упущены для экономии места в этом тексте. )
}
Не зависимо от типа, животное может быть живым или мертвым и иметь некий возраст. Пока этих полей хватит.
Далее создадим класс приматов:
package com.critters;
public class Primate extends Animal {
int numberOfLimbs;
int height;
int iq;
Sex sex;
// Геттеры и сеттеры упущены для экономии места в этом тексте. )
public enum Sex { // Модуль 5. Урок 5. Перечисления в Java.
male, female
}
}
Класс приматов будет немного сложнее. Но любой примат точно может быть живым или мертвым и иметь некий возраст. Поэтому он является наследником класса Animal
.
Далее создадим класс обезьян:
package com.critters;
public class Monkey extends Primate {
public Diet diet;
public Diet getDiet() {
return diet;
}
public void setDiet(Diet diet) {
this.diet = diet;
}
public enum Diet { // Модуль 5. Урок 5. Перечисления в Java.
omnivorous, carnivore, herbivore
}
}
Будем классифицировать обезьян по диете и на этом успокоимся. Но каждая обезьяна точно должна включать в себя все параметры примата. Поэтому класс Monkey
наследуется от класса Primate
. Получается, что в классе Monkey
мы уже не дублируем шесть полей и кучу методов к ним, а переиспользуем уже написанное!
В упомянутом выше репозитории Вы еще найдете класс HomoSapiens
как пример ветвления наследников.
Как использовать наследование?
Сразу же начнем с примера использования объекта класса Monkey
. Вертеть ее будем в классе CrittersUsage
.
package com;
import com.critters.Animal;
import com.critters.Monkey;
import com.critters.Primate;
public class CrittersUsage {
public static void main(String[] args) {
Object o = new Object();
Monkey monkey = new Monkey();
// Наполним обезьянку.
monkey.setDiet(Monkey.Diet.herbivore);
// Поля от предка Primate
monkey.setNumberOfLimbs(5); // иногда хвост считается конечностью
monkey.setHeightCM(110);
monkey.setIq(75);
monkey.setSex(Primate.Sex.male);
// Поля от предка Animal
monkey.setAge(15);
monkey.setAlive(true); // Оживили.
// Monkey доступны методы класса Object. В данном случае — toString()
System.out.println(monkey.toString());
printAnimalAge(monkey); // 15
printAnimalAliveStatus(monkey); // true
}
// Оба метода будут работать с любыми наследниками класса Animal
private static void printAnimalAge(Animal someAnimal) {
System.out.println(someAnimal.getAge());
}
private static void printAnimalAliveStatus(Animal someAnimal) {
System.out.println(someAnimal.isAlive());
}
}
Как видно из кода — объекту класса Monkey
доступны все члены всех классов-предков. Даже Object.toString()
. Реализован Object.toString()
слишком обобщенно, поэтому данный метод просто возвращает системное имя объекта, у которого он был вызван. В моем случае это было com.critters.Monkey@1b6d3586
.
Доступность членов классов-предков в классах-наследниках
Обратите внимание на то, что ссылки в аргументах методов printAnimalAge
& printAnimalAliveStatus
имеют тип Animal
. Это хоть и позволяет этим методам принимать на вход объекты-наследники от класса Animal
, но это же ограничивает доступность членов объекта для данных методов.
Тип ссылки ограничивает видимость членов объекта по себе. То есть, несмотря на то, что мы знаем, что в ссылках someAnimal
на самом деле представлен объект класса Monkey
, но мы сможем использовать только члены класса (типа) Animal
: isAlive(), setAlive(...), getAge(), setAge(...)
, в выше упомянутых методах.
А поля класса Animal
видны только в рамках пакета critters
, потому что они не имеют модификатора доступа.
Это означает, что видимость членов классов в наследовании работает только на верх. Наследники наследуют все свойства предков, а предкам недоступны свойства наследников.
super
— ключевое слово, позволяющее наследнику получить доступ к членам класса-предка, которые перекрыты такими же членами класса-наследника. Но наследник:
- не имеет доступа к
private
членам класса-предка; - может иметь доступ к членам, которые без модификатора доступа, если лежит с предком в одном пакете;
- может иметь доступ к членам класса-предка, которые с модификатором
protected
, не зависимо от расположения в пакетах.
Естественно, что public
члены любого класса светятся во весь мир.
Рассмотрим на примерах.
Для начала, добавим:
- в класс
Animal
строкуprotected final String hiddenMember = "Animal's hidden string.";
. - в класс
Primate
строкиprotected final String hiddenMember = "Primate's hidden first string.";
иprivate final String secondHiddenMember = "Primate's hidden second string.";
. - в класс
Monkey
строкуprotected final String hiddenMember = "Monkeys's hidden string.";
. И Добавим еще сюда методaccessToHiddenMembers()
.
Теперь класс Monkey
будет выглядеть так:
package com.critters;
public class Monkey extends Primate {
Diet diet;
protected final String hiddenMember = "Monkeys's hidden string.";
public Diet getDiet() {
return diet;
}
public void setDiet(Diet diet) {
this.diet = diet;
}
public enum Diet { // Модуль 5. Урок 5. Перечисления в Java.
omnivorous, carnivore, herbivore
}
public void accessToHiddenMembers() {
System.out.println(hiddenMember); // "Monkeys's hidden string."
System.out.println(super.hiddenMember); // "Primate's hidden first string."
// System.out.println(super.secondHiddenMember); // private member
// System.out.println(super.super.hiddenMember); // Так тоже нельзя.
}
}
Обратите внимание, что при помощи слова super
мы получили доступ к полю hiddenMember
класса Primate
. Хотя мы его перекрыли таким же полем в классе Monkey
.
Но мы не можем получить доступ к скрытому члену класса-предка-предка Animal
. Хотя можем пройтись по всем другим членам классов-предков вплоть до Object
. Перекрывая член класса-предка — теряем доступ к этому члену во всех классах-предках, кроме ближайшего.
Полезные ссылки
- Про наследование от Oracle
- еще вариант объяснение темы про наследование (RU)
Еще один хороший источник с табличками (EN)
Документация по ключевому слову super