接口中的内部类 vs 类中的内部类

28

这两种内部类声明有什么区别?还请评论其优缺点。

情况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方法的位置。

进一步信息: 我能够在完全不实现接口的另一个类中的两种情况 A 和 B 中实例化 Items 类。

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

由于接口不会被实例化,因此可以通过点表示法访问接口内的所有元素,而无需实例化LEVELS接口,因为您不能实例化一个接口 - 这有效地使得定义在接口内部的类对静态引用具有渗透性。

因此,在B案例中说Items类不是静态的是没有意义的。由于A和B两种情况的实例化方式相同,我不想针对静态、内部或嵌套的语义进行讨论。请停止给我关于语义的答案。我想知道编译器、运行时和行为差异/优势,如果没有,则说出来。请不要再回答关于语义的问题了!希望熟悉JVM或.NET VM规范内部的专家能够回答这个问题,而不是那些只懂书本语义学的人。


我不在寻找它被称为内部类还是嵌套类的语义学差异,我在寻找它们之间的功能差异。 - Blessed Geek
2
@h2g2 - 仅仅重新格式化代码后,你的第二个例子是不正确的,接口中不能有已实现的方法。或者可能是花括号没有放在正确的位置... - Andreas Dolk
根据您所展示的接口定义,我在 JDK/5 和 6 中都遇到了编译错误:“接口方法不能有主体”。您是复制/粘贴可编译的代码还是手动输入的? - sateesh
个人而言,我不喜欢公共内部类。如果它们只被一个类使用,我会将它们设为私有;如果它们被多个类使用,它们应该拥有自己的命名空间。我也绝不会在接口中创建内部类。我不希望/期望它能够像那样工作,但从未费心测试过。 - pstanton
6个回答

27
一个static内部类是一个嵌套类,而非静态的内部类则被称为内部类。更多信息,请看这里
然而,我喜欢引用同样链接中的一段摘录。

静态嵌套类与其外部类的实例成员(和其他类)交互,就像任何其他顶级类一样。实际上,静态嵌套类在行为上是一个已经为方便包装而嵌套在另一个顶级类中的顶级类。

在第二种情况下,您没有使用static这个单词。因为它是一个接口,您认为它隐式地是static。您的假设是正确的。
您可以在接口中实例化内部类,就像静态嵌套类一样,因为它实际上是一个static嵌套类。
Levels.Items hello = new Levels.Items();

所以,上面的语句在你提出的这两种情况下都是有效的。你的第一种情况是静态嵌套类,而在第二种情况中,你没有指定static,但即使如此,它也将成为一个静态嵌套类,因为它在接口中。因此,除了一个嵌套在类中,另一个嵌套在接口中之外,没有任何区别

通常在类中的内部类而不是在接口中会像下面这样被实例化。

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

此外,“非静态”内部类将具有对其外部类的隐式引用。但这在“静态”嵌套类中并不成立。


接口中的元素是静态的,因此第二种情况也是一个静态类。请注意问题是一个被类包含,而另一个被接口包含。问题是在将静态类嵌入类和嵌入接口之间的行为差异是什么。 - Blessed Geek

15

静态内部类与顶层类大致相似,除了内部类可以访问封闭类的所有静态变量和方法。封闭类名称实际上被附加到内部类的包命名空间中。通过将类声明为静态内部类,您正在表达该类与封闭类的上下文密不可分。

非静态内部类较少见。主要区别是非静态内部类的实例包含对封闭类实例的隐式引用,并因此可以访问该封闭类实例的实例变量和方法。这导致一些奇怪的实例化习惯用法,例如:

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(); 

非静态内部类与其封闭类之间的联系比静态内部类更为密切。它们有着合法的用途(例如,迭代器通常作为非静态内部类在它们迭代的数据结构的类中实现)。

当你只需要静态内部类的行为时,声明非静态内部类是一个常见的错误。


1
非静态内部类非常常见,特别是在图形代码中。 - gerardw

15
如果您在接口中声明一个嵌套类,则该类始终是publicstatic。所以:
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;


}

7

我认为你举的嵌套/内部类示例不太好。此外,第二个示例不是有效的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类是这种习惯用法的一个著名示例。


1
Map.Entry 是一个接口。 - Franklin Yu

0

我认为第一个声明了一个名为Levels的类和一个叫做Items的静态内部类。Items可以通过Levels.Items进行引用,并且是静态的。

而第二个声明了一个简单的内部类,可以通过使用Levels.Items进行访问,就像下面这样:

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

编辑:这是完全错误的,请阅读评论和其他回复。


1
你的第一个段落完全是一个误解。在这种情况下,不要混淆“静态”这个词。 - Adeel Ansari
1
此外,您的代码片段对于“静态”嵌套类完全有效。但不适用于内部类。因此,在您放置它的上下文中,您的代码变得无效。应该像这样:Levels.Items items = levelsInstance.new Items();。请注意,您不能在不实例化外部类的情况下实例化内部类。我希望这能够消除疑虑。 - Adeel Ansari

0

在我看来,优点是如果它们是琐碎的话,你的项目文件夹中会有更少的类;缺点是当你的内部类随着需求的变化而不断增长时,维护将成为你的噩梦。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接