Почему статический вложенный интерфейс будет использоваться в Java?

235

Я только что нашел статический вложенный интерфейс в нашей кодовой базе.

class Foo {
    public static interface Bar {
        /* snip */
    }
    /* snip */
}

Я никогда не видел этого раньше. Первоначальный разработчик недоступен. Поэтому я должен спросить так:

Какова семантика статического интерфейса? Что изменится, если я удалю static? Зачем кому-то это делать?

Пн.
источник
2
Это не внутренний интерфейс: это вложенный интерфейс. Внутренний имеет особое значение в Java.
Маркиз Лорн

Ответы:

293

Ключевое слово static в приведенном выше примере является избыточным (вложенный интерфейс автоматически становится «статическим») и может быть удалено без влияния на семантику; Я бы порекомендовал его удалить. То же самое касается «public» в интерфейсных методах и «public final» в интерфейсных полях - модификаторы являются избыточными и просто добавляют беспорядок в исходный код.

В любом случае, разработчик просто объявляет интерфейс с именем Foo.Bar. Дальнейшая связь с классом включения отсутствует, за исключением того, что код, который не может получить доступ к Foo, также не сможет получить доступ к Foo.Bar. (Из исходного кода - байт-код или отражение могут получить доступ к Foo.Bar, даже если Foo закрыт для пакета!)

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

public class Foo {
    public interface Bar {
        void callback();
    }
    public static void registerCallback(Bar bar) {...}
}
// ...elsewhere...
Foo.registerCallback(new Foo.Bar() {
    public void callback() {...}
});
Джесси Глик
источник
1
В ответе Джесси Глика, что это значит: (Из исходного кода - байт-код или отражение могут получить доступ к Foo.Bar, даже если Foo является частной посылкой!).
Васу
Kaillash, частные методы могут быть доступны через relfection (в отражающем пакете) и через прямой доступ к байт-коду сгенерированных файлов .class.
gmoore
2
Под «байт-кодом ... можно получить доступ к Foo.Bar» я имею в виду, что скомпилированный класс, ссылающийся на Foo.Bar, может быть загружен и запущен, даже если он не может ссылаться на Foo. Это может произойти, если класс был скомпилирован ранее, когда Foo был общедоступным, или если класс был собран вручную или скомпилирован из некоторого не Java-языка и т. Д. Компилятор Java, однако, проверяет модификатор доступа во включающем классе даже если результирующий байт-код не будет ссылаться на этот включающий класс.
Джесси Глик
@Jesse Можно ли получить доступ к закрытому статическому классу в закрытом классе верхнего уровня с помощью отражения?
Pacerier
1
@Pacerier: нет. Точнее, вы можете загрузить класс и проверить его члены, но не можете создать его экземпляр или вызвать методы, не используя setAccessible (true). Другими словами, это видно, но не доступно через отражение. Но если вложенный статический класс является общедоступным, он доступен по умолчанию через отражение, даже если он недоступен статически (во время компиляции).
Джесси Глик
72

На вопрос был получен ответ, но одна из веских причин использовать вложенный интерфейс - если его функция напрямую связана с классом, в котором он находится. Хороший пример этого - a Listener. Если у вас есть класс, Fooи вы хотите, чтобы другие классы могли прослушивать события в нем, вы могли бы объявить интерфейс с именем FooListener, что нормально, но, вероятно, было бы более понятно объявить вложенный интерфейс и реализовать эти другие классы Foo.Listener( вложенный класс Foo.Eventнеплох с этим).

ColinD
источник
11
Типичным примером является java.util.Map.Entry(который является интерфейсом, вложенным в другой интерфейс).
Пауло Эберманн
4
Я знаю, что это старая тема, но я бы предпочел, чтобы внешний класс находился в его собственном пакете, а любые дополнительные интерфейсы (например Map.Entry) или классы также были в этом пакете. Я говорю это потому, что мне нравится, чтобы мои занятия были короткими и конкретными. Также читатель может увидеть, какие другие объекты связаны с классом, посмотрев на классы в пакете. Я бы, наверное, имел пакет java.collections.mapдля карт. Это о ОО и модульности. java.utilв нем слишком много utilэто как common- запах ИМО
Дэвид Керр
1
@ Шагги: java.utilконечно, слишком много в нем. Тем не менее, я не думаю, что разбивать его на такие мелкозернистые пакеты, как вы предлагаете, также идеально.
ColinD
1
@DavidKerr Предположим, вы вызываете этот интерфейс java.util.MapEntryвне его собственного пакета. Первый рефлекс: как интерфейс связан с этим классом? Я смотрю в класс. Если Javadoc не ссылается на этот интерфейс, я понятия не имею, как они связаны. Плюс люди не смотрят на импортную упаковку. У каждого свое мнение. Никто не прав, никто не ошибается
Рэймонд Ченон
@RaymondChenon было частью того, что я сказал, чтобы поместить Map и связанные с ней классы, например Map.Entry, в отдельный пакет, например java.collections.map. Таким образом, весь код, связанный с картой, находится в одном месте (пакете), и класс Map верхнего уровня не обременяется. Возможно, я бы поместил соответствующие реализации (HashMap и т. Д.) В один и тот же пакет.
Дэвид Керр
14

Интерфейсы-члены неявно статичны. Модификатор static в вашем примере может быть удален без изменения семантики кода. См. Также спецификацию языка Java 8.5.1. Декларации типов статических членов

Bas Leijdekkers
источник
Это не «внутренний интерфейс»: это вложенный интерфейс. Внутренний имеет особое значение в Java.
Маркиз Лорн
@EJP, я читаю различные блоги, используя термины взаимозаменяемо. Внутренний интерфейс существует? Чем они отличаются от вложенного интерфейса?
Number945
@BreakingBenjamin, вложенный интерфейс означает статический. Существует внутренний класс и вложенный класс, но нет внутреннего интерфейса. Даже если это так, он должен называться вложенным интерфейсом. Внутренний - не статичный и вложенный статический.
Сундар Раджан
9

Внутренний интерфейс должен быть статическим для доступа. Интерфейс связан не с экземплярами класса, а с самим классом, поэтому к нему можно получить доступ Foo.Barследующим образом:

public class Baz implements Foo.Bar {
   ...
}

В большинстве случаев это не отличается от статического внутреннего класса.

Клинтон Н. Дрейсбах
источник
35
Вложенный интерфейс автоматически статичен, независимо от того, записывается ключевое слово или нет.
Пауло Эберманн
3
Нам действительно нужен способ голосования сообщества, чтобы принять другой ответ: stackoverflow.com/a/74400/632951
Pacerier
@ ClintonN.Dreisbach Можете ли вы объяснить, что подразумевается под The interface isn't associated with instances of the class, but with the class itselfболее, я не
понял
6

Ответ Джесси близок, но я думаю, что есть лучший код, чтобы продемонстрировать, почему внутренний интерфейс может быть полезен. Посмотрите на код ниже, прежде чем читать дальше. Можете ли вы найти, почему внутренний интерфейс полезен? Ответ в том, что класс DoSomethingAlready может быть создан с любым классом, который реализует A и C; не только конкретный класс зоопарка. Конечно, это может быть достигнуто, даже если AC не является внутренним, но представьте, что объединяете более длинные имена (не только A и C) и делаете это для других комбинаций (скажем, A и B, C и B и т. Д.), И вы легко Посмотрите, как все выходит из-под контроля. Не говоря уже о том, что люди, проверяющие ваше дерево исходников, будут перегружены интерфейсами, которые имеют смысл только в одном классе.внутренний интерфейс позволяет создавать пользовательские типы и улучшает их инкапсуляцию .

class ConcreteA implements A {
 :
}

class ConcreteB implements B {
 :
}

class ConcreteC implements C {
 :
}

class Zoo implements A, C {
 :
}

class DoSomethingAlready {
  interface AC extends A, C { }

  private final AC ac;

  DoSomethingAlready(AC ac) {
    this.ac = ac;
  }
}
user1982892
источник
2
Это нонсенс. Класс Zooдействительно не реализует интерфейс AC, поэтому случаи Zooне могут быть переданы в конструктор , DoSomethingAlreadyкоторый ожидает AC. Тот факт, что ACрасширяет оба, Aи C, не означает, что классы реализуют Aи Cволшебным образом также реализуют AC.
Хольгер
3

Чтобы ответить на ваш вопрос очень прямо, посмотрите на Map.Entry.

Map.Entry

также это может быть полезно

Статическая Вложенная Inerfaces Запись в блоге

Генри Б
источник
4
Это пример, но не совсем ответ.
Пауло Эберманн
Я использую Map.Entry для создания "Pair" объектов много. Это разоблачено. Реализация Pair имеет две школы мысли, но здесь дело не в этом. Map.Entry может быть внутренним, но я использую его снаружи.
Равиндранат Акила
0

Обычно я вижу статические внутренние классы. Статические внутренние классы не могут ссылаться на содержащие классы, в отличие от нестатических классов. Если вы не столкнетесь с какими-то коллизиями пакетов (там уже есть интерфейс под названием Bar в том же пакете, что и Foo), я думаю, я бы сделал это своим собственным файлом. Это также может быть дизайнерское решение для обеспечения логической связи между Foo и Bar. Возможно, автор предполагал, что Bar будет использоваться только с Foo (хотя статический внутренний интерфейс этого не навязывает, просто логическое соединение)

basszero
источник
«Статическое внутреннее» является противоречием в терминах. Вложенные классы либо статические или внутренние.
Маркиз Лорн
0

Если вы замените класс Foo на интерфейс Foo, ключевое слово public в приведенном выше примере также будет избыточным, поскольку

Интерфейс, определенный внутри другого интерфейса, будет неявно общедоступным.

Данило Волох
источник
Не ответили на вопрос
дождь
0

В 1998 году Филипп Вадлер предложил различие между статическими интерфейсами и нестатическими интерфейсами.

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

Например, он предложил решение проблемы выражения , которая заключается в несоответствии между выражением «сколько может выражаться ваш язык», с одной стороны, и выражением «термины, которые вы пытаетесь представить на своем языке», с другой стороны. ,

Пример различия между статическими и нестатическими вложенными интерфейсами можно увидеть в его примере кода :

// This code does NOT compile
class LangF<This extends LangF<This>> {
    interface Visitor<R> {
        public R forNum(int n);
    }

    interface Exp {
        // since Exp is non-static, it can refer to the type bound to This
        public <R> R visit(This.Visitor<R> v);
    }
}

Его предложение никогда не делалось в Java 1.5.0. Следовательно, все остальные ответы верны: нет никакой разницы для статических и нестатических вложенных интерфейсов.

Pindatjuh
источник
Следует отметить, что все это относится к GJ, который был очень ранним процессором для дженериков в Java.
Маркиз Лорн
-1

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

class Bob
{
  void FuncA ()
  {
    Foo.Bar foobar;
  }
}

Без статики вышеприведенное не скомпилируется. Преимущество этого в том, что вам не нужен новый исходный файл только для объявления интерфейса. Он также визуально связывает интерфейс Bar с классом Foo, поскольку вы должны написать Foo.Bar, и подразумевает, что класс Foo делает что-то с экземплярами Foo.Bar.

Описание типов классов в Java .

Skizz
источник
Без статической она делает компиляцию. «Статический» является избыточным.
Маркиз Лорн
@EJP: В соответствии с той статьей, на которую я ссылался, без статики внутренний класс может использоваться только классом-владельцем, а не чем-либо, кроме класса owing. Статика делает его вложенным классом верхнего уровня и может использоваться объектами вне области действия класса-владельца (по крайней мере, согласно статье). Статика не является полностью избыточной и влияет на область действия внутреннего класса. Это относится к классам, интерфейсы всегда могут действовать как объекты верхнего уровня (необходимо проверить спецификацию языка, чтобы проверить). Возможно, мне следовало разделить интерфейс и классную часть моего ответа (мой плохой).
Skizz
-6

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

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

Статика не всегда так полезна. Например, если вы делаете сравнение случаев, вы можете хранить данные несколькими различными способами. Вы не можете создать три статических метода с одинаковыми сигнатурами. Вам нужно 3 разных экземпляра, не статичных, а затем вы можете сравнить их, потому что если они статические, данные не изменятся вместе с вводом.

Статические методы хороши для одноразовых возвратов и быстрых вычислений или легко получаемых данных.

Vordreller
источник
1
Я знаю, что значит статика. Я просто никогда не видел его на интерфейсе раньше. Поэтому ваш вопрос не по теме - извините
пн.
1
Я думаю, что его ответ не по теме.
Корай Тугай