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

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

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

7.5 Интерфейсы

Ключевое слово interface (интерфейс) позволяет углубиться в абстрактные концепции ещё на один уровень. Его можно представить как "чистый" абстрактный класс. Он позволяет создателю определять базовый вид класса: названия методов, списки аргументов и типы возвращаемых значений, но не определяет тела этих методов. Интерфейсы также могут содержать члены данных с базовыми типами данных, но они все по умолчанию являются static и final. Интерфейсы предоставляют только форму, но не детали реализации.

Интерфейс описывает себя следующим образом: "Для всех классов, которые реализуют меня, внешний вид должен быть таким же, как у меня сейчас". Поэтому любые строки кода, использующие конкретный интерфейс, знают, какие методы могут быть вызваны для этого интерфейса. Это и есть вся суть интерфейсов. Мы часто используем интерфейсы для установления "контракта" между классами. Некоторые объектно-ориентированные языки программирования используют ключевое слово protocol (протокол), которое выполняет ту же задачу, что и интерфейс.

Чтобы создать интерфейс, используйте ключевое слово interface, а не class. Как и при работе с классами, вы можете добавить ключевое слово public перед ключевым словом interface (но только если интерфейс определён в файле с тем же именем); либо его можно опустить, чтобы создать "доброжелательное" состояние.Чтобы создать класс, который соответствует определенному интерфейсу (или группе интерфейсов), используйте ключевое слово implements (реализовать). Выражение "интерфейс выглядит так, вот подробности его работы" является основной идеей. Кроме того, большинство остальной работы аналогично наследованию. Вот примерный чертеж для примера с музыкальными инструментами:

После реализации интерфейса получается обычный класс, который может быть расширен стандартными способами.

Методы, объявленные в интерфейсе, могут быть явно определены как public. Однако даже если это не делается явно, они будут по умолчанию иметь доступность public. Поэтому при реализации интерфейса методы, взятые из него, должны быть определены как public. В противном случае они будут иметь "доброжелательное" состояние, что ограничит ваш доступ к этим методам во время наследования — компилятор Java не позволит вам сделать это.

В модифицированной версии примера с классом Instrument можно четко заметить эту особенность. Обратите внимание, что каждый метод в интерфейсе строго является объявлением, и это единственное, что разрешает компилятор. Кроме того, ни один метод в Instrument5 не был объявлен как public, но они автоматически получают свойство public. Пример представлен ниже:

//: Music5.java
// Интерфейсы
import java.util.*;
``````java
interface Instrument5 {
  // Константа времени компиляции:
  int i = 5; // статическая и конечная
  // Нельзя иметь определений методов:
  void play(); // Автоматически публичный
  String what();
  void adjust();
}

class Wind5 implements Instrument5 {
  public void play() {
    System.out.println("Wind5.play()");
  }
  public String what() { return "Wind5"; }
  public void adjust() {}
}

class Percussion5 implements Instrument5 {
  public void play() {
    System.out.println("Percussion5.play()");
  }
  public String what() { return "Percussion5"; }
  public void adjust() {}
}

class Stringed5 implements Instrument5 {
  public void play() {
    System.out.println("Stringed5.play()");
  }
  public String what() { return "Stringed5"; }
  public void adjust() {}
}

class Brass5 extends Wind5 {
  public void play() {
    System.out.println("Brass5.play()");
  }
  public void adjust() {
    System.out.println("Brass5.adjust()");
  }
}

class Woodwind5 extends Wind5 {
  public void play() {
    System.out.println("Woodwind5.play()");
  }
  public String what() { return "Woodwind5"; }
}

public class Music5 {
  // Не заботится о типе, поэтому новые типы,
  // добавленные в систему, всё ещё работают правильно:
  static void tune(Instrument5 i) {
    // ...
    i.play();
  }
  static void tuneAll(Instrument5[] e) {
    for(int i = 0; i < e.length; i++)
      tune(e[i]);
  }
  public static void main(String[] args) {
    Instrument5[] orchestra = new Instrument5[5];
    int i = 0;
    // Поднятие типа во время добавления в массив:
    orchestra[i++] = new Wind5();
    orchestra[i++] = new Percussion5();
    orchestra[i++] = new Stringed5();
    orchestra[i++] = new Brass5();
    orchestra[i++] = new Woodwind5();
    tuneAll(orchestra);
  }
}

///:~Код остальной части работает аналогичным образом. Мы можем свободно решать, как преобразовать типы данных до одного "обычного" класса Instrument5, одного "абстрактного" класса Instrument5 или одного "интерфейса" Instrument5. Все поведение одинаково. В самом деле, мы видим это в методе tune(): нет никаких свидетельств того, является ли Instrument5 "обычным" классом, "абстрактным" классом или "интерфейсом". Это сделано намеренно: каждое решение позволяет программисту контролировать создание и использование объектов различным образом.

7.5.1 Множественное наследование в JavaИнтерфейсы представляют собой более "чистую" форму по сравнению с абстрактными классами. Однако их использование не ограничивается этим. Поскольку интерфейсы не содержат конкретной реализации — то есть, нет никаких данных, связанных с "интерфейсом" — нет способа предотвратить объединение нескольких интерфейсов вместе. Это важно, так как мы часто хотим выразить идею: "x является частью a, также является частью b, а также является частью c". В C++ действие объединения нескольких классов называется "множественным наследованием", что может быть сложным, поскольку каждый класс может иметь свою собственную реализацию. В Java мы можем выполнить такое же действие, но только один из этих классов имеет конкретную реализацию. Поэтому при объединении нескольких интерфейсов проблема множественного наследования из C++ не возникает. Пример:

В производном классе нам не обязательно иметь абстрактный или конкретный (без абстрактных методов) базовый класс. Если действительно требуется наследовать от непосредственно от интерфейса, то можно наследовать только от одного. Все остальные базовые элементы должны быть "интерфейсами". Мы помещаем все имена интерфейсов после ключевого слова implements, разделяя их запятыми. Можно использовать несколько интерфейсов, и каждый из них будет представлять собой отдельный тип, который можно преобразовать вверх по иерархии. В следующем примере показано, как "конкретный" класс объединяется с несколькими интерфейсами, создавая новый класс:

public class ConcreteClass implements Interface1, Interface2 {
    // Код класса
}
``````java
//: Adventure.java
// Множество интерфейсов
import java.util.*;

interface CanFight {
  void fight();
}

interface CanSwim {
  void swim();
}

interface CanFly {
  void fly();
}

class ActionCharacter {
  public void fight() {}
}

class Hero extends ActionCharacter
    implements CanFight, CanSwim, CanFly {
  public void swim() {}
  public void fly() {}
}

public class Adventure {
  static void t(CanFight x) { x.fight(); }
  static void u(CanSwim x) { x.swim(); }
  static void v(CanFly x) { x.fly(); }
  static void w(ActionCharacter x) { x.fight(); }
  public static void main(String[] args) {
    Hero i = new Hero();
    t(i); // Обрабатываем его как CanFight
    u(i); // Обрабатываем его как CanSwim
    v(i); // Обрабатываем его как CanFly
    w(i); // Обрабатываем его как ActionCharacter
  }
} ///:~

Как видно из этого примера, Hero объединяет конкретный класс ActionCharacter с интерфейсами CanFight, CanSwim и CanFly. При объединении конкретного класса с интерфейсами конкретный класс должен располагаться первым, а затем следует список интерфейсов (иначе компилятор сообщит об ошибке). Обратите внимание, что сигнатура метода fight() в интерфейсе CanFight и классе ActionCharacter одинакова, а также то, что в классе Hero конкретной реализации метода fight() нет. По правилам интерфейсов: мы можем наследовать от него (что будет видно позже), но это даст нам другой интерфейс. Чтобы создать объект нового типа, он должен быть классом, который предоставляет все необходимые реализации. Хотя в классе Hero явной реализации метода fight() нет, она предоставляется вместе с классом ActionCharacter, поэтому эта реализация автоматически предоставляется, и мы можем создать объекты класса Hero. В классе Adventure можно заметить четыре метода, использующих различные интерфейсы и конкретные классы как свои параметры. После создания объекта Hero его можно передать любому из этих методов. Это означает, что он последовательно преобразуется в каждый из интерфейсов. Поскольку эти интерфейсы были созданы с использованием Java, это не вызывает никаких проблем, и программисту не требуется особого внимания к этому процессу.Обратите внимание, что вышеупомянутый пример показал одно из ключевых преимуществ использования интерфейсов и одну из основных причин для их применения: возможность преобразования в несколько базовых типов. Вторая причина использования интерфейса аналогична причине использования абстрактного базового класса: предотвращение клиента-программиста от создания объекта этого класса и указание того, что это просто интерфейс. Однако это приводит к вопросу: использовать интерфейс или абстрактный класс? Если использовать интерфейс, можно получить преимущества как от абстрактного класса, так и от интерфейса. Поэтому если вы хотите создать базовый класс без определённых методов или членов данных, лучше всего использовать интерфейс вместо абстрактного класса. На самом деле, если заранее известно, что какой-то тип может стать базовым, первым выбором должно быть использование интерфейса. Абстрактный класс следует выбирать только тогда, когда действительно нужны определённые методы или члены данных.## 7.5.2 Расширение интерфейса через наследование

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

//: HorrorShow.java
// Расширяющий интерфейс с помощью наследования

interface Преследователь {
    void угрожать();
}

interface ОпасныйПреследователь extends Преследователь {
    void уничтожать();
}

interface Летальный {
    void убивать();
}

class ДраконЗила implements ОпасныйПреследователь {
    public void угрожать() {}
    public void уничтожать() {}
}
```Интерфейс `ОпасныйМонстр` является расширением интерфейсов `Монстр` и `Летальный`. В нем объявлен метод `питькровь()`.

Класс `УжасныйШоу` содержит два статических метода `u()` и `v()`, а также метод `main()`, который демонстрирует использование этих методов с объектом типа `ДраконЗила`.

```java
interface ОпасныйМонстр
    extends Монстр, Летальный {
  void питькровь();
}

class УжасныйШоу {
  static void u(Монстр b) { bугрожать(); }
  static void v(ОпасныйМонстр d) {
    dугрожать();
    dуничтожать();
  }
  public static void main(String[] args) {
    ДраконЗила if2 = new ДраконЗила();
    u(if2);
    v(if2);
  }
}

ОпасныйМонстр представляет собой простое расширение интерфейса Монстр. Это привело к созданию нового интерфейса, реализованного в классе ДраконЗила.Синтаксис объявления Vampire позволяет использовать ключевое слово implements при наследовании от нескольких интерфейсов. Обычно ключевое слово extends используется для наследования от одного класса, но поскольку интерфейсы могут наследовать несколько других интерфейсов, то в объявлении нового интерфейса можно указывать несколько базовых интерфейсов через запятую.### 7.5.3 Группировка констант

Поскольку все поля в интерфейсе автоматически имеют модификаторы static и final, интерфейсы являются хорошим средством для группировки констант, что аналогично использованию enum в C или C++. Пример:

//: Months.java
// Использование интерфейсов для группировки констант
package c07;

public interface Months {
  int
    JANUARY = OnClickListener.CLICK, FEBRUARY = 2, MARCH = 3,
    APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
    AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
    NOVEMBER = 11, DECEMBER = 12;
}

В соответствии с правилами названий Java, все static final примитивные типы данных (то есть константы времени компиляции) должны писаться полностью заглавными буквами, разделенными нижними чертами для каждого слова внутри имени.

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

Теперь, импортировав c07.* или c07.Months, мы можем использовать эти константы извне пакета, как будто это любой другой пакет. Также значения можно ссылать через выражения вида Months.JANUARY. Конечно, получаемый тип — это просто int, так что этот подход не предоставляет такого же уровня типовой безопасности, как C++ enum. Однако он значительно лучше, чем использование "магических" чисел, закрепленных в программе.

Если требуется обеспечить дополнительную типовую безопасность, можно создать такой класс (замечание ①):

//: Month2.java
// Расширенная система перечислений
package c07;
``````java
public final class Month2 {
  private String name;
  private Month2(String nm) { name = nm; }
  public String toString() { return name; }
  public final static Month2
    ЯНВАРЬ = new Month2("January"),
    ФЕВРАЛЬ = new Month2("February"),
    МАРТ = new Month2("March"),
    АПРЕЛЬ = new Month2("April"),
    МАЙ = new Month2("May"),
    ИЮНЬ = new Month2("June"),
    ИЮЛЬ = new Month2("July"),
    АВГУСТ = new Month2("August"),
    СЕНТЯБРЬ = new Month2("September"),
    ОКТЯБРЬ = new Month2("October"),
    НОЯБРЬ = new Month2("November"),
    ДЕКАБРЬ = new Month2("December");
  public final static Month2[] месяц =  {
    ЯНВАРЬ, ФЕВРАЛЬ, МАРТ, АПРЕЛЬ, МАЙ, ИЮНЬ,
    ИЮЛЬ, АВГУСТ, СЕНТЯБРЬ, ОКТЯБРЬ, НОЯБРЬ, ДЕКАБРЬ
  };
  public static void main(String[] args) {
    Month2 m = Month2.ЯНВАРЬ;
    System.out.println(m);
    m = Month2.месяц[12];
    System.out.println(m);
    System.out.println(m == Month2.ДЕКАБРЬ);
    System.out.println(m.equals(Month2.ДЕКАБРЬ));
  }
}

①: Эта идея была вдохновлена электронной почтой от Rich Hoffarth. ```Этот класс называется Month2, так как в стандартной библиотеке Java уже существует класс `Month`. Это `final` класс с `private` конструктором, поэтому никто не может наследовать его или создать экземпляр этого класса. Единственные экземпляры — это `final static` объекты, которые создаются внутри самого класса, включая: ЯНВАРЬ, ФЕВРАЛЬ, МАРТ и т.д. Эти объекты также используются в массиве `месяц`, который позволяет нам выбирать месяцы по номеру, а не по имени (обратите внимание, что массив содержит лишний ЯНВАРЬ, увеличивающий смещение на 1, чтобы Декабрь действительно был 12-м месяцем). В методе `main()`, мы можем заметить безопасность типов: `m` является объектом типа `Month2`, поэтому его можно присвоить только значению типа `Month2`. В примере `Months.java`, предоставлена переменная типа `int`, поэтому переменная типа `int`, которая должна представлять месяц, могла бы получить целочисленное значение, что было бы менее надёжно.Метод, представленный здесь, также позволяет использовать операторы `==` или `equals()`, как показано в конце метода `main()`.

7.5.4 Инициализация полей в интерфейсе

Поля, определённые в интерфейсе, автоматически имеют свойства static и final. Они не могут быть "пустыми final", но могут быть инициализированы выражением, которое не является константой. Например:

//: RandVals.java
// Инициализация полей интерфейса с помощью
// инициализаторов, которые не являются константами
import java.util.*;

public interface RandVals {
  int rint = (int)(Math.random() * 10);
  long rlong = (long)(Math.random() * 10);
  float rfloat = (float)(Math.random() * 10);
  double rdouble = Math.random() * 10;
} ///:~

Конечно, поля не являются частью интерфейса, а хранятся в статическом хранилище этого интерфейса.

//: TestRandVals.java

public class TestRandVals {
  public static void main(String[] args) {
    System.out.println(RandVals.rint);
    System.out.println(RandVals.rlong);
    System.out.println(RandVals.rfloat);
    System.out.println(RandVals.rdouble);
  }
} ///:~

Опубликовать ( 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