Программное обеспечение

Java шпаргалка. Часть 3. Наследование

Шпаргалка по наследованию в языке Java.

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

Рефлексия — позволяет исследовать свойства классов в ходе выполнения программы.

Определение подкласса

[java]
class Villa extends Building
{
Дополнительные методы и поля
}
[/java]

Если мы в классе Villa хотим определить новый метод getArea(), который будет также учитывать площадь участка вокруг виллы, то:

[java]
public double getArea()
{
double baseArea = super.getArea();//вызов метода getaArea() из суперкласса.
return baseArea + outsideArea;
}
[/java]

super не означает ссылку на объект. По нему нельзя присвоить значение другой объектной переменной. Это слово всего лишь означает, что нужно вызвать метод из суперкласса.

[java]
public Villa(String s, double a)
{
super(s,a)
outsideArea=0;
}
[/java]

Если конструктор подкласса не вызывает явно ни одного из конструкторов суперкласса, то из этого суперкласса автоматически вызывается конструктор без аргументов. Если же в суперклассе отсутствует конструктор без аргументов, а конструктор подкласса не вызывает явно другой конструктор из суперкласса, то компилятор Java выдаст сообщение об ошибке.

Мы можем создать массив такого типа:

[java]
Building[] quarter = new Building[3];
[/java]

Данный массив может также хранить объекты класса Village.

[java]
for (Building b : quarter)
System.out.println(b.getArea());
[/java]

Причем для объекта Village вызовется соответствующий метод getArea() из класса-наследника, а не супер-класса.

Способность переменной (в нашем случае b) ссылаться на объекты, имеющие разные фактические типы, называется полиморфизмом. Автоматический выбор нужного метода во время выполнения программы называется динамическим связыванием.

Полиморфизм

Для того, чтобы определить, стоит ли в конкретной ситуации применять наследование или нет существует простое правило. Если между объектами существует отношение «является», то каждый объект подкласса является объектом суперкласса.

В Java объектные переменные являются полиморфными. Переменная типа Building может ссылаться как на объект класса Building, так и на объект любого подкласса, производного от класса Building.

[java]
Villa presidentVilla = new Villa(…);
Building[] quarter = new Building[3];
quarter[0] = presidentVilla;

presidentVilla.getOutsideArea(); // Допустимо!
quarter[0].getOutsideArea(); // Ошибка!
[/java]

Присвоить ссылку на объект суперкласса переменной подкласса нельзя.

[java]
Villa v = quarter[i]; // ОШИБКА!
[/java]

Динамическое связывание

Что происходит при вызове метода, принадлежащего некоторому объекту?

  1. Компилятор проверяет объявленный тип объекта, а также имя метода. Например был вызван метод f: x.f(param). Компилятор нумерует все методы под именем f в классе C и все доступные методы под именем f в суперклассах. Таким образом компилятору становятся известны все возможные кандидаты на вызов метода под именем f.
  2. Компилятор определяет типы параметров , указанных при вызове метода. Если среди всех методов под именем f имеется только один метод, типы параметров которого совпадают с указанными, происходит его вызов. Этот процесс называется разрешением перегрузки.  Если компилятор ничего подходящего не нашел, то выдается сообщение об ошибке. Таким образом, компилятору известно имя и типы параметров метода, который должен быть вызван.
  3. Если метод является закрытым (private), статическим (static), конечным (final) или конструктором, компилятору точно известно, как его вызвать. Такой процесс называется статическим связыванием. В противном случае вызываемый метод определяется по фактическому типу неявного параметра, а во время выполнения программы происходит динамическое связывание.
  4. Если при выполнении программы для вызова метода используется динамическое связывание, виртуальная машина должна вызвать версию метода, соответствующую фактическому типу объекта, на который ссылается переменная x. Допустим, объект имеет фактический тип D подкласса, производного от клас­са С. Если в классе D определен метод f (String), то вызывается именно он. В противном случае поиск вызываемого метода f (String) осуществляется в суперклассе и т.д. На поиск вызываемого метода уходит слишком много времени, поэтому виртуальная машина зарнее создает для каждого класса таблицу методов, в которой перечисляются сигнатуры всех методов и фактически вызываемые методы.

Предотвращение наследования: конченые классы и методы

Классы, которые нельзя расширять, называются конечными и обозначаются ключевыми словом final. Все методы конечного класса являются конечными, но не поля!

P.S. с помощью final объявляются константы.

Отдельный метод класса также может быть конечным.

Существует единственный аргумент в пользу указания ключевого слова final при объявлении метода или класса: гарантия неизменности семантики в подклассе.

 

Приведение типов

Иногда ссылку на объект требуется привести к типу другого класса. Для такого приведения типов существует только одна причина: необходимость использовать все функциональные возможности объекта после того, как его фактический тип был на время забыт.

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

Правила приведения типов при наследовании:

  1. Приведение типов можно выполнять только  в иерархии наследования
  2. Для того, чтобы проверить корректность приведения суперкласса к подклассу, следует выполнить операцию instanceof

Но в целом при наследовании лучше свести к минимуму приведение типов и выполнении операции instanceof.

Абстрактные классы

Класс, содержащий один или несколько абстрактных методов, можно объявить абстрактным следующим образом:

[java]
abstract class Person
{
//Реализация метода не требуется
public abstract String getDescription();
}
[/java]

Помимо абстрактных методов, абстрактные классы могут содержать конкретные поля и методы.

Абстрактные методы представляют собой прототипы методов, реализованных в подклассах. Расширяя абстрактный класс, можно оставить некоторые или все абстрактные методы неопределенными.
Создать экземпляры абстрактного класса нельзя!
Класс может быть объявлен абстрактным, даже если он не содержит ни одного абстрактного метода.

Как только все методы из класса становятся конкретными, класс перестает быть абстрактным.
Абстрактные методы в основном применяются при создании интерфейсов.

Защищенный доступ

У подкласса отсутствует доступ к закрытым полям суперкласса (private).

Если член (поле или метод) класса объявлен как protected, то он доступен не только внутри самого класса, но и внутри всех классов-наследников.

Пользоваться защищёнными полями следует очень аккуратно. Например, если вашим классом будут пользоваться другие разработчики, то вы уже не сможете изменить реализацию класса, не уведомив их. А это противоречит ООП, поощряющему инкапсуляцию данных.

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

 

Таким образом:

  1. private — ограничивает область действия классом
  2. public — не ограничивает область действия
  3. protected — ограничивает область действия пакетом и всеми подклассами
  4. модификатор отсутствует — область действия ограничивается пакетом по умолчанию.

Глобальный суперкласс Object

Метод equals

Данный метод реализован в классе Object и он проверяет только следующее: ссылаются ли переменные на один и тот же объект.

Определяя метод equals() для подкласса, сначала следует вызвать одноименный метод из суперкласса. Если проверка даст отрицательный результат, то объекты нельзя считать равнозначными. Если же поля суперкласса совпадают, можно приступать к сравнению полей подкласса.

 

Спецификация Java требует, чтобы метод equals() обладал следующими характеристиками:

  1. Рефлексивность.  При вызове х.equals (х) по любой ненулевой ссылке х должно возвращаться логическое значение true.
  2. Симметричность. При вызове х.equals (у) по любым ссылкам х и у должно
    возвращаться логическое значение true тогда и только тогда, когда при вызове у.equals (х) возвращается логическое значение true.
  3. Транзитивность. Если при вызовах х.equals (у) и у.equals (z) по любым ссылкам х, у и z возвращается логическое значение true, то и при вызове х.equals (z) возвращается логическое значение true.
  4. Согласованность. Если объекты, на которые делаются ссылки х и у, не из­
    меняются, то при повторном вызове х.equals (у) должно возвращаться то же
    самое значение.
  5. При вызове х.equals (null) по любой непустой ссылке х должно возвращаться логическое значение false.

Как создать метод equals()

  1. Присвойте явному параметру имя otherObject. Впоследствии его тип нужно будет привести к типу другой переменной под названием other.
  2. Проверьте, одинаковы ли ссылки this и otherObject, таким образом: if (this == otherObject) return true; Это выражение необходимо лишь для более быстрой проверки.
  3. Выясните, является ли ссылка otherObject пустой (null). Если да, то следует возвратить логическое значение false. if (otherObject == null) return false;
  4. Сравните классы this и otherObject. Если семантика проверки может измениться в подклассе, то воспользуйтесь методом getClass():  if(getClass() != otherObject.getClass()) return false; Если одна и та же семантика остается справедливой для всех подклассов, произведите проверку с помощью instanceof следующим образом: if (!(otherObject instanceof ClassName)) return false;
  5. Приведите тип объекта otherObject к типу переменной требуемого класса: ИмяКласса other = (ИмяКласса)otherObject;
  6. Сравните все поля. Для полей примитивных типов служит операция ==, а для объектных полей — метод Objects.equals(). Если все поля двух объектов совпадают, то возвращается логическое значение true, а иначе — false. return поле1 == other.поле1 && поле2.equals(other.поле2) && …; Если вы переопределяете в подклассе метод equals(), в него следует включить вызов super.equals(other). P.S. Если имеются поля типа массива, то для проверки на равенство соответствующих элементов массива можно воспользоваться статическим методом Arrays.equals().

Метод hashCode()

Хеш-код — это целое число, генерируемое на основе конкретного объекта.
Метод hashCode() определен в классе Object. Поэтому у каждого объекта имеется хеш-код, определяемый по умолчанию.

Методы equals() и hashCode() должны быть совместимы: если в результате вызова x.equals(y) возвращается true, то и результаты вызовов x.hashCode() и y.hashCode() также должны совпадать.

Метод toString()

Данный метод возвращает значение объекта в виде символьной строки.

Например:

[java]
public String toString()
{
return getClass().getName()
+ "[name=" + name
+ ",salary=" + salary
+ ",hireDay=" + hireDay
+ "]";
}
[/java]

При создании подкласса следует определить собственный метод toString() добавить поля подкласса. Так, если в суперклассе вызывается getClass().getName(), то в подклассе просто вызывается метод super.toString().

Имеется веское основание для реализации toString() в каждом классе: если объект объединяется с символьной строкой с помощью операции +, то компилятор Java автоматически вызывает метод toString(), чтобы получить строковое представление этого объекта.

Массивы наследуют метод toString() от класса Object. Поэтому для вывода одномерных массивов можно воспользоваться Arrays.toString(). А для вывода многомерных массивов необходимо вызвать Arrays.deepToString().

Обобщенные списочные массивы

Как мы знаем, задав размер массива, его потом нелегко изменить. Эту проблему проще всего решить, используя списочные массивы. Для этого применяются экземпляры класса ArrayList. Он может динамически изменять размеры массива.

Класс ArrayList является обобщенным с параметром типа. Тип элемента массива пишется в угловых скобках, например: ArrayList.

[java]
ArrayList<Buiding> quarter = new ArrayList<Building>();
[/java]

Начиная с Java 7 можно опускать параметр типа с правой стороны выражения.

Для добавления новых элементов в списочный массив служит метод add().

Первоначальную емкость списочного массива можно передать конструктору:

[java]
ArrayList<Building> quarter = new ArrayList<Building>(100);
[/java]

Емкость списочного массива может быть увеличена, в отличие от размера массива.

Метод size() возвращает фактическое количество элементов в списочном массиве. Равнозначное выражение для определения размера обычного массива выглядит так: a.length.

Метод trimToSize() используется если вы уверены, что списочный массив будет иметь постоянный размер. Если добавить новые элементы в списочный массив после усечения его размера методом trimToSize(), то блок памяти будет перемещен, на что потребуется дополнительное время.

Доступ к элементам списочных массивов

Класса ArrayList не входит в состав Java, а является служебным классом в стандартной библиотеке. Поэтому вместо удобных квадратных скобок необходимо вызывать методы get() и set().

Внимание! Для заполнения необходимо вызывать add() вместо set()!

LinkedList — если часто вставляем/удаляем, в остальных случаях ArrayList.

Объектные оболочки и автоупаковка

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

[java]
// Автоупаковка это когда запись вида
list.add(3);
// Автоматически преобразуется в данную запись
list.add(new Integer(3));
[/java]

При сравнении объектов-оболочек необходимо использовать метод equals().

За упаковку и распаковку отвечает отвечает виртуальная машина, а не компилятор.

Все параметры в Java передаются только по значению!

Классы перечислений

[java]
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
[/java]

Тип, объявленные подобным образом представляет собой класс.

Рекомендации по применению наследования

  1. Размещать общие операции и поля в суперклассе
  2. Стараться не пользоваться полями с модификатором protected. Это не относится к методам
  3. Наследование используется для моделирования отношения типа «является»
  4. Не пользоваться наследованием если не все методы имеет смысл делать наследуемыми
  5. Переопределяя метод, не изменяйте его предполагаемое поведение
  6. Пользоваться полиморфизмом, а не данными о типе
  7. Не злоупотреблять рефлексией

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *