1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
6.2 继承的语法.md 15 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

6.2 Синтаксис наследования

Наследование тесно связано с Java (и другими языками объектно-ориентированного программирования). Мы ввели концепцию наследования ещё в главе 1 и периодически использовали её во всех последующих главах до настоящей, поскольку некоторые специфические случаи требуют использования наследования. Кроме того, при создании нового класса всегда происходит наследование, так как новый класс автоматически наследует все члены данных и методы от стандартного корневого класса Object.

Синтаксис для объединения очень прост и интуитивно понятен. Однако для наследования требуется использование совершенно другой формы. Когда говорится "этот новый класс похож на тот старый класс", то чтобы выразить это в коде, используется имя класса. Но перед открывающей фигурной скобкой класса следует ключевое слово extends, за которым следует имя базового класса. При таком подходе новый класс автоматически получает доступ ко всем членам данных и методам базового класса. Вот пример:

//: Detergent.java
// Синтаксис наследования и его свойства

class Cleanser {
  private String s = new String("Cleanser");
  public void append(String a) { s += a; }
  public void dilute() { append(" dilute()"); }
  public void apply() { append(" apply()"); }
  public void scrub() { append(" scrub()"); }
  public void print() { System.out.println(s); }
  public static void main(String[] args) {
    Cleanser x = new Cleanser();
    x.dilute(); x.apply(); x.scrub();
    x.print();
  }
}
``````public class Detergent extends Cleanser {
  // Изменение одного метода:
  public void scrub() {
    append(" Detergent.scrub()");
    super.scrub(); // Вызов версии базового класса
  }
  // Добавление новых методов к интерфейсу:
  public void foam() { append(" foam()"); }
  // Тестирование нового класса:
  public static void main(String[] args) {
    Detergent x = new Detergent();
    x.dilute();
    x.apply();
    x.scrub();
    x.foam();
    x.print();
    System.out.println("Тестирование базового класса:");
    Cleanser.main(args);
  }
} ///:~

Этот пример демонстрирует множество особенностей. Во-первых, в методе `append()` класса `Cleanser` строка соединяется с переменной `s`. Это достигается с помощью оператора `+=`. Как и оператор `+`, оператор `+=` используется Java для "перегрузки" строковых значений. Во-первых, как `Cleanser`, так и `Detergent` содержат метод `main()`. Мы можем создать метод `main()` для каждого своего класса. Обычно рекомендуется писать код таким образом, чтобы тестовый код мог быть заключен в класс. Даже если программа содержит множество классов, метод `main()` будет вызван только для публичных классов, запрошенных через командную строку. Поэтому при использовании `java Detergent` вызывается `Detergent.main()`  даже если `Cleanser` не является публичным классом. Такой подход позволяет легко выполнять unit-тестирование для каждого класса. После завершения тестирования нет необходимости удалять метод `main()`: его можно оставить для последующих тестов. Здесь можно видеть явный вызов `Cleanser.main()` методом `Detergent.main()`.
```Особое внимание следует обратить на то, что все классы в `Cleanser` имеют доступность `public`. В случае отсутствия всех модификаторов доступа, члены по умолчанию являются "дружественными", что позволяет им быть доступными только внутри одного пакета. Таким образом, любой человек внутри этого пакета может использовать эти методы без модификаторов доступа. Например, `Detergent` столкнётся с проблемами только если он находится вне данного пакета. Однако, если другой класс из другого пакета намерен расширять `Cleanser`, ему будут доступны только те члены, которые имеют модификатор доступа `public`. Поэтому хорошей практикой при планировании наследования является установка всех полей как `private` и всех методов как `public` (методы со значением `protected` также доступны производным классам; мы вернёмся к этому вопросу позже). Конечно, в некоторых специфических случаях могут потребоваться изменения, но это не считается хорошей практикой.Обратите внимание, что `Cleanser` имеет ряд методов в своём интерфейсе: `append()`, `dilute()`, `apply()`, `scrub()` и `print()`. Поскольку `Detergent` наследуется от `Cleanser` (через ключевое слово `extends`), он автоматически получает все эти методы  даже если они не были явно определены в `Detergent`. Это позволяет рассматривать наследование как повторное использование "интерфейса" или "расширение интерфейса" (подробности реализации могут быть произвольными, но это не является основной темой).Как показывает пример в `scrub()`, можно получить метод, определённый в базовом классе, и изменить его. В этом случае обычно требуется вызвать метод из базового класса в новой версии. Однако простое вызов `scrub()` внутри `scrub()` приведёт к рекурсивному вызову, что недопустимо. Для решения этой проблемы Java предоставляет ключевое слово `super`, которое ссылается на "суперкласс" (Superclass), из которого текущий класс наследует. Таким образом, выражение `super.scrub()` вызывает версию метода `scrub()` из базового класса.

При наследовании вы не ограничены использованием только методов базового класса. Вы можете добавить свои новые методы в производный класс. Этот процесс аналогичен добавлению любого другого метода в обычный класс: просто определяйте новый метод. Ключевое слово `extends` указывает нам, что мы расширяем интерфейс базового класса, добавляя новый метод. Примером такого подхода служит метод `foam()`. В `Detergent.main()` мы можем видеть, что объект `Detergent` может вызывать все доступные методы как в `Cleanser`, так и в `Detergent` (например, `foam()`).

## 6.2.1 Инициализация базового классаПоскольку здесь присутствуют два класса  базовый и производный, а не один, как ранее, это может вызвать некоторые трудности при представлении результата объекта производного класса. Извне кажется, что новый класс имеет тот же интерфейс, что и базовый класс, и может содержать дополнительные методы и поля. Однако наследование не сводится просто к копированию интерфейса базового класса. При создании объекта производного класса он содержит "подобъект" базового класса. Этот подобъект выглядит так, будто бы мы создали его, используя сам базовый класс.Конечно, этот подобъект базового класса должен быть правильно инициализирован, и существует только один способ гарантировать это: выполнение инициализации в конструкторе через вызов конструктора базового класса, который имеет достаточно полномочий для правильной инициализации. В конструкторе производного класса Java автоматически вставляет вызов конструктора базового класса. Ниже приведён пример, демонстрирующий использование трёхуровневого наследования:

```java
//: Cartoon.java
// Вызовы конструкторов во время наследования

class Art {
  Art() {
    System.out.println("Art constructor");
  }
}

class Drawing extends Art {
  Drawing() {
    System.out.println("Drawing constructor");
  }
}

public class Cartoon extends Drawing {
  Cartoon() {
    System.out.println("Cartoon constructor");
  }
  public static void main(String[] args) {
    Cartoon x = new Cartoon();
  }
} ///:~

Выход программы показывает автоматический вызов:

Art constructor
Drawing constructor
Cartoon constructor

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

Даже если мы не создаем конструктор Cartoon(), компилятор также создаст для нас по умолчанию конструктор и произведёт вызов конструктора базового класса.

(1) Конструктор с параметрами## Приведенный выше пример имеет свой собственный по умолчанию конструктор; то есть, они не принимают никаких параметров. Компилятор может легко вызвать их, поскольку нет необходимости передавать какие-либо специфичные параметры. Если класс не имеет по умолчанию конструктора, или если требуется вызвать конструктор базового класса с параметрами, тогда явно следует написать код для вызова базового класса. Это можно сделать с помощью ключевого слова super, а также с правильным списком аргументов, как показано ниже:

//: Chess.java
// Наследование, конструкторы и аргументы
``````java
class Game {
  Game(int i) {
    System.out.println("Конструктор класса Game");
  }
}

class BoardGame extends Game {
  BoardGame(int i) {
    super(i);
    System.out.println("Конструктор класса BoardGame");
  }
}

public class Chess extends BoardGame {
  Chess() {
    super(11);
    System.out.println("Конструктор класса Chess");
  }
  public static void main(String[] args) {
    Chess x = new Chess();
  }
} ///:~

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

(2) Обработка исключений от базового конструктора

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


Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master