
- •Часть 1
- •Часть 1
- •Часть 1'
- •1. Задачи, поставленные при разработке языка. Компиляция и интерпретация.
- •2. Приложения и апплеты. Их жизненные циклы. Передача параметров.
- •3. Классы. Инкапсуляция. Описание полей и методов
- •4. Примитивные типы данных и операции над ними. Классы-оболочки.
- •Целочисленные типы данных.
- •Булев тип
- •5. Операторы реализации алгоритмов.
- •Нормальное и прерванное выполнение операторов
- •Пустой оператор
- •Оператор if
- •If (логическое выражение) выражение или блок 1 else выражение или блок 2
- •Оператор switch
- •Цикл while
- •Цикл do
- •Цикл for
- •Оператор continue
- •Оператор break
- •Оператор return
- •Использование оператора throw
- •6. Классы: наследование, полиморфизм. Полиморфиз
- •Защищенные (protected) методы и переменные.
Защищенные (protected) методы и переменные.
Методы и переменные, защищенные на уровне пакета (то есть те, которые указаны без спецификатора доступа), если суперкласс находится в том же пакете, что и Adult.
Конструкторы являются специальными методами. Они не являются полноправными объектно-ориентированными членами, поэтому они не наследуются.
Если суперкласс переопределяет метод или переменную суперкласса (другими словами, если подкласс реализует член с таким же именем) - это скрывает член суперкласса. Если быть более точным, переопределение переменной скрывает ее, а переопределение метода просто переопределяет его, но эффект одинаков: переопределенный член суперкласса, по существу, скрыт. Однако вы можете обратиться к членам суперкласса при помощи ключевого слова super:
super.hiddenMemberName
В ситуации с классом Adult все, что он пока наследует, - это методы класса Object (например, toString()). То есть, следующий фрагмент кода абсолютно корректен:
Adult anAdult = new Adult();
anAdult.toString();
Метод toString() не существует явно в Adult. Adult наследует его.
Здесь есть ловушки, о которых вы должны помнить. Во-первых, очень легко дать переменным и методам подкласса такое же имя, которое имеют переменные и методы суперкласса, а затем удивляться, почему вы не можете вызвать наследованный метод. Помните, что давая в подклассе методу то же имя, которое уже существует в суперклассе, вы скрываете его. Во-вторых, конструкторы не наследуются, но они вызываются. Существует неявный вызов конструктора суперкласса в любом конструкторе подкласса, который вы пишите, и это первое действие, которое выполняет конструктор подкласса. Вы должны смириться с этим. Вы никак не можете это изменить. Например, конструктор нашего класса Adult во время выполнения на самом деле выглядит следующим образом, не смотря на то, что мы ничего не вводили в тело конструктора:
public Adult() {
super();
}
Эта строка в теле конструктора вызывает конструктор суперкласса, не имеющий аргументов. В данном случае это конструктор класса Object.
Определение иерархии классов
Предположим, что у нас есть еще один класс с названием Baby. Он выглядит следующим образом:
public class Baby {
protected int age = 0;
protected String firstname = "firstname";
protected String lastname = "lastname";
protected String gender = "MALE";
protected int progress = 0;
public Baby() {
}
public void move() {
System.out.println("Moved.");
}
public void talk() {
System.out.println("Spoke.");
}
}
Наши классы Adult и Baby выглядят очень похожими. Фактически, они почти идентичны. Такое дублирование кода делает процесс обслуживания кода более болезненным, чем он должен быть. Мы можем создать суперкласс, переместить все общие элементы в этот класс и удалить дублирование кода. Наш суперкласс мог бы называться Person и выглядеть следующим образом:
public class Person {
protected int age = 0;
protected String firstname = "firstname";
protected String lastname = "lastname";
protected String gender = "MALE";
protected int progress = 0;
public Person() { }
public void move() {
System.out.println("Moved.");
}
public void talk() {
System.out.println("Spoke.");
}
}
Теперь Adult и Baby могут быть подклассами Person, что делает эти два класса очень простыми:
public class Adult extends Person{
public Adult() { }
}
public class Baby extends Person{
public Baby() { }
}
Имея такую иерархию, мы можем обратиться к экземпляру каждого подкласса как к экземпляру любого из его суперклассов в иерархии. Например:
Adult anAdult = new Adult();
System.out.println("anAdult is an Object: " + (Adult instanceof Object));
System.out.println("anAdult is a Person: " + (Adult instanceof Person));
System.out.println("anAdult is anAdult: " + (Adult instanceof Adult));
System.out.println("anAdult is a Baby: " + (Adult instanceof Baby));
Этот код выдает три результата со значением true и один со значением false. Можно также для объекта выполнить приведение типов к любому типу объекта, который находится выше его по иерархии, например:
Adult anAdult = new Adult();
Person aPerson = (Person) anAdult;
aPerson.move();
Ps. нет необходимости указывать явное преобразование из класса в суперкласс. Т.к. в данном случае всё что было в Person есть в Adult, поэтому верно:
Person aPerson = anAdult;
Этот код откомпилируется без проблем. Мы можем выполнить приведение типа объекта Adult к типу Person, а затем вызвать метод Person с ним.
Благодаря такой иерархии код наших подклассов упрощается. Вы видите здесь проблему? Теперь все объекты Adult и все объекты Baby будут говорить и перемещаться одинаково. Есть только одна реализация каждого поведения. Это не то, что нам нужно, потому что они должны говорить или двигаться по-разному. Мы могли бы не переопределять методы move() и talk() в подклассах, но тогда мы имели бы, в сущности, бесполезное "стандартное" поведение, определенное в суперклассе. Что нам действительно нужно - это заставить каждый из наших подклассов передвигаться и говорить своим собственным способом. Именно для этого и существуют абстрактные классы.