Внутренний класс в интерфейсе и в классе

В чем разница между этими двумя объявлениями внутреннего класса? Также прокомментируйте преимущества/недостатки?

случай A: класс внутри класса.

public class Levels {   
  static public class Items {
    public String value;
    public String path;

    public String getValue() {
      return value;}
  }
}

и случай B: класс внутри интерфейса.

public interface Levels{

  public class Items {
    public String value;
    public String path;

    public String getValue() {
      return value;}
  }
}

Внесено исправление: к размещению метода getvalue.

дополнительная информация: я могу создать экземпляр класса Items в обоих случаях A и B в другом классе, который ВООБЩЕ не реализует интерфейс.

public class Z{//NOTE: NO INTERFACE IMPLEMENTED here!!!!
 Levels.Items items = new Levels.Items();
}

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

Поэтому говорить, что класс Items в случае B не является статическим, не имеет смысла. Поскольку оба случая A и B создаются одинаково, я не ищу семантику того, что является статическим, внутренним или вложенным. Хватит давать мне ответы по семантике. Мне нужны компилятор, время выполнения и поведенческие различия/преимущества, или, если нет, то так и скажите. Больше никаких ответов по семантике, пожалуйста!!!!! Эксперт по внутренностям спецификаций JVM или .NET VM, пожалуйста, ответьте на этот вопрос, а не на семантику учебника.


person Blessed Geek    schedule 04.12.2009    source источник
comment
Я не ищу семантику того, называется ли он внутренним или вложенным классом. Я ищу различия в функциях.   -  person Blessed Geek    schedule 04.12.2009
comment
@ h2g2 - только после переформатирования кода - второй пример неверен, у вас не может быть реализованного метода в интерфейсе. Или, может быть, фигурные скобки были не на своих местах...   -  person Andreas Dolk    schedule 04.12.2009
comment
Для определения интерфейса, которое вы показали, я получаю ошибку компиляции (как с JDK/5, так и с 6), методы интерфейса не могут иметь тела. Вы скопировали/вставили свой код, который компилируется, или просто набрали его?   -  person sateesh    schedule 04.12.2009
comment
лично мне вообще не нравятся публичные внутренние классы. если они используются только одним классом, я делаю их частными, если они используются несколькими классами, они заслуживают своего собственного пространства имен. я также никогда бы не создал внутренний класс в интерфейсе. я бы не хотел/ожидал, что это будет работать так, но никогда не удосужился проверить.   -  person pstanton    schedule 06.12.2009


Ответы (6)


Статические внутренние классы в основном похожи на классы верхнего уровня, за исключением того, что внутренний класс имеет доступ ко всем статическим переменным и методам окружающего класса. Имя окружающего класса эффективно добавляется к пространству имен пакета внутреннего класса. Объявляя класс как статический внутренний класс, вы сообщаете, что класс каким-то образом неразрывно связан с контекстом окружающего класса.

Нестатические внутренние классы менее распространены. Основное отличие состоит в том, что экземпляры нестатического внутреннего класса содержат неявную ссылку на экземпляр окружающего класса и, как следствие, имеют доступ к переменным экземпляра и методам этого экземпляра окружающего класса. Это приводит к некоторым странным идиомам инстанцирования, например:

Levels levels = new Levels(); // first need an instance of the enclosing class

// The items object contains an implicit reference to the levels object
Levels.Items items  = levels.new Items(); 

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

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

person Community    schedule 04.12.2009
comment
нестатические внутренние классы довольно распространены, особенно в графическом коде. - person gerardw; 19.02.2014

Внутренний класс static является вложенным классом, а нестатический называется внутренним классом. Дополнительные сведения см. здесь.

Тем не менее, я люблю цитировать отрывок из той же ссылки.

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

Вы не использовали слово static во втором случае. И вы думаете, что это будет неявно static, потому что это интерфейс. Вы правы, предполагая это.

Вы можете создать экземпляр внутреннего класса в своем интерфейсе, как статический вложенный класс, потому что это действительно static вложенный класс.

Levels.Items hello = new Levels.Items();

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

Обычно внутренний класс в классе, не в интерфейсе, создается, как показано ниже.

Levels levels = new Levels();
Levels.Items items = levels.new Items();

Более того, "нестатический" внутренний класс будет иметь неявную ссылку на его внешний класс. Это не относится к "статическому" вложенному классу.

person Adeel Ansari    schedule 04.12.2009
comment
Элементы в интерфейсе являются статическими, поэтому второй случай также является статическим классом. Обратите внимание, что вопрос заключается в том, что один окутан классом, а другой - интерфейсом. Вопрос в том, каковы поведенческие различия между встраиванием статического класса в класс и в интерфейс. - person Blessed Geek; 04.12.2009

Если вы объявляете вложенный класс в интерфейсе, он всегда общедоступен и статичен. Так:

public interface Levels{
    class Items {
        public String value;
        public String path;

        public String getValue() {return value;}
    }
}

Точно такой же, как

public interface Levels{
    public static class Items {
        public String value;
        public String path;

        public String getValue() {return value;}
    }
}

И даже

public interface Levels{
    static class Items {
        public String value;
        public String path;

        public String getValue() {return value;}
    }
}

Я проверил это с помощью javap -verbose, и все они производят

Compiled from "Levels.java"
public class Levels$Items extends java.lang.Object
  SourceFile: "Levels.java"
  InnerClass: 
   public #14= #3 of #23; //Items=class Levels$Items of class Levels
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method   #4.#21; //  java/lang/Object."<init>":()V
const #2 = Field    #3.#22; //  Levels$Items.value:Ljava/lang/String;
const #3 = class    #24;    //  Levels$Items
const #4 = class    #25;    //  java/lang/Object
const #5 = Asciz    value;
const #6 = Asciz    Ljava/lang/String;;
const #7 = Asciz    path;
const #8 = Asciz    <init>;
const #9 = Asciz    ()V;
const #10 = Asciz   Code;
const #11 = Asciz   LineNumberTable;
const #12 = Asciz   LocalVariableTable;
const #13 = Asciz   this;
const #14 = Asciz   Items;
const #15 = Asciz   InnerClasses;
const #16 = Asciz   LLevels$Items;;
const #17 = Asciz   getValue;
const #18 = Asciz   ()Ljava/lang/String;;
const #19 = Asciz   SourceFile;
const #20 = Asciz   Levels.java;
const #21 = NameAndType #8:#9;//  "<init>":()V
const #22 = NameAndType #5:#6;//  value:Ljava/lang/String;
const #23 = class   #26;    //  Levels
const #24 = Asciz   Levels$Items;
const #25 = Asciz   java/lang/Object;
const #26 = Asciz   Levels;

{
public java.lang.String value;

public java.lang.String path;

public Levels$Items();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable: 
   line 2: 0

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      5      0    this       LLevels$Items;


public java.lang.String getValue();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   getfield    #2; //Field value:Ljava/lang/String;
   4:   areturn
  LineNumberTable: 
   line 7: 0

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      5      0    this       LLevels$Items;


}
person Daniel Worthington-Bodart    schedule 02.04.2012

Примеры вложенных/внутренних классов, которые вы приводите, являются (IMO) плохими примерами. Кроме того, второй пример не является допустимым для Java, поскольку интерфейс может только объявлять (неявно) абстрактные методы. Вот лучший пример:

public interface Worker {

    public class Response {
        private final Status status;
        private final String message;
        public Response(Status status, String message) {
            this.status = status; this.message = message;
        }
        public Status getStatus() { return status; }
        public String getMessage() { return message; }
    }

    ...

    public Response doSomeOperation(...);
}

Встраивая класс Response, мы указываем, что он является фундаментальной частью Worker API и больше не используется.

Класс Map.Entry является хорошо известным примером этой идиомы.

person Stephen C    schedule 04.12.2009
comment
Map.Entry — это интерфейс. - person Franklin Yu; 01.05.2016

ИМХО, преимущество в том, что у вас меньше классов, загромождающих папку вашего проекта, если они тривиальны; недостаток заключается в том, что когда ваш внутренний класс растет вместе с изменением требований, обслуживание становится вашим кошмаром.

person Ricky    schedule 04.12.2009

Я думал, что первый объявит класс Levels и статический внутренний класс с именем Items. На элементы может ссылаться Levels.Items, и они будут статическими.

В то время как второй объявит простой внутренний класс, доступ к которому можно получить с помощью Levels.Items, как показано ниже:

Levels.Items hello = new Levels.Items();

РЕДАКТИРОВАТЬ: это совершенно неправильно, прочитайте комментарии и другие ответы.

person BlueTrin    schedule 04.12.2009
comment
Ваш первый абзац - полное заблуждение. В этом случае не путайтесь со словом static. - person Adeel Ansari; 04.12.2009
comment
Более того, ваш фрагмент кода полностью подходит для вложенного класса static. Но не для внутреннего класса. Таким образом, ваш код становится недействительным в контексте, в который вы его вставили. Это должно быть что-то вроде Levels.Items items = levelsInstance.new Items();. Обратите внимание: вы не можете создать экземпляр внутреннего класса без создания экземпляра внешнего. Надеюсь, это развеет сомнения. - person Adeel Ansari; 04.12.2009