Java中的静态嵌套类,为什么使用?

236

我正在查看 LinkedList 的 Java 代码,发现它使用了一个静态嵌套类 Entry

public class LinkedList<E> ... {
...

 private static class Entry<E> { ... }

}
为什么要使用静态嵌套类而不是普通的内部类?
我唯一能想到的原因是,Entry 没有访问实例变量的权限,因此从面向对象编程的角度来看,它具有更好的封装性。
但我认为可能还有其他原因,比如性能。那会是什么呢?
注意:我希望自己的术语是正确的,我本来想称其为静态内部类,但我认为这是错误的:http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html

https://www.javatpoint.com/java-inner-class - Daniel Viglione
14个回答

290
您链接的Sun页面列出了两者之间的一些关键区别:

嵌套类是其封闭类的成员。非静态嵌套类(内部类)可以访问封闭类的其他成员,即使它们被声明为私有。静态嵌套类无法访问封闭类的其他成员。
...

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

LinkedList.Entry 没有必要成为顶级类,因为它只有LinkedList 使用(还有一些其他接口也有名为 Entry 的静态嵌套类,例如 Map.Entry - 相同的概念)。并且由于它不需要访问 LinkedList 的成员,将其设置为静态则更加干净整洁。
正如Jon Skeet指出的那样,如果您使用的是嵌套类,则最好从它是静态的开始,然后根据您的使用情况决定是否真正需要非静态嵌套类。

1
如果一个静态嵌套类没有访问外部类的实例成员,那么它如何与外部类的实例成员进行交互? - Geek
@SinthiaV,你说得没错,那基本上就是引用文本所说的话的重新陈述。 - matt b
1
@mattb 但正如@Geek所指出的,Sun页面是自相矛盾的:“静态嵌套类与其外部类(和其他类)的实例成员交互,就像任何其他顶级类一样。”如果在文档的前面只有一个段落说:“静态嵌套类无法访问封闭类的其他成员。”也许他们想说的是:“嵌套(非静态)类与其外部类(和其他类)的实例成员交互,就像任何其他顶级类一样。” - tonix
1
@DavidS 感谢提供的链接!是的,我错了,现在看到我的评论,我发现我的重新表述是不正确的。正如你所说:“内部类通过对其封闭类的隐式引用与实例成员进行交互”,这指出了“非静态内部类”以及“匿名内部类”或“在块内定义的局部类”的另一个有趣属性:它们都不能有“无参”构造函数,因为编译器将隐式地添加每个构造函数的参数序列,以便传递封闭类实例的引用。非常简单。 - tonix
1
你可以使用静态内部类来实例化仅具有私有构造函数的外部类。这在建造者模式中被使用。但是你不能使用内部类做同样的事情。 - seenimurugan
显示剩余5条评论

49

在我看来,每当您看到一个内部类时,问题应该是相反的 - 它是否真的需要成为一个内部类,带来了额外的复杂性和对包含类实例的隐式(而非明确且更清晰,依我之见)引用?

请注意,我作为C#粉丝有偏见 - C#没有内部类的等效物,尽管它具有嵌套类型。 我不能说我已经错过了内部类 :)


4
可能我说错了,但在我看来那是一个静态嵌套类的例子,而不是内部类。甚至在示例中,他们还指出在嵌套类中无法访问周围类的实例变量。 - ColinD
2
嵌套类型是C#相比Java非常正确的领域之一。我总是惊叹于它的语义/逻辑正确性。 - nawfal
3
@nawfal:是的,除了一些小问题外,我非常钦佩C#语言的设计(和规范)。 - Jon Skeet
1
@JonSkeet,你有关于这些小问题的文章或博客吗?我很想看看你认为的“小问题”是什么 :) - nawfal
虽然有点晚了,但是不允许将类打包在一起,促进封装和引用的局部性有什么好处呢?我只是好奇为什么缺少某些东西是一种美德 - 在这种情况下,拥有那些东西必须是一个缺陷 - 那么这个缺陷是什么? - theRiley
显示剩余4条评论

30

这里需要考虑一些非显而易见的内存保留问题。由于非静态内部类会维护对其“外部”类的隐式引用,如果内部类实例被强引用,那么外部实例也会被强引用。当外部类没有被垃圾回收时,即使看起来没有任何引用指向它,也可能会导致一些令人困惑的情况。


如果“外部”类是final的,因此根本无法实例化,那么这个参数在这种情况下是否有意义?因为如果外部类是final的,那么保持对外部类的引用就毫无意义了。 - getsadzeg

17

静态内部类用于建造者模式。 静态内部类可以实例化仅具有私有构造函数的外部类。 与内部类不同,您需要在访问内部类之前创建外部类的对象。

class OuterClass {
    private OuterClass(int x) {
        System.out.println("x: " + x);
    }
    
    static class InnerClass {
        public static void test() {
            OuterClass outer = new OuterClass(1);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        OuterClass.InnerClass.test();
        // OuterClass outer = new OuterClass(1); // It is not possible to create outer instance from outside.
    }
}

这将输出 x: 1


我们可以从非静态内部类调用外部类的私有构造函数。 - vijaya kumar

11

静态嵌套类就像任何其他外部类一样,因为它没有访问外部类成员的权限。

只是出于打包方便,我们可以将静态嵌套类合并到一个外部类中以提高可读性。除此之外,静态嵌套类没有其他用途。

这种用法的例子可以在Android R.java(资源)文件中找到。Android的Res文件夹包含布局(包含屏幕设计)、drawable文件夹(包含用于项目的图像),值文件夹(其中包含字符串常量)等等。

由于所有文件夹都是Res文件夹的一部分,因此android工具会生成一个R.java(资源)文件,该文件内部包含每个内部文件夹的许多静态嵌套类。

以下是在Android中生成的R.java文件的外观:这里只是出于打包方便而使用。

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.techpalle.b17_testthird;

public final class R {
    public static final class drawable {
        public static final int ic_launcher=0x7f020000;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
    public static final class menu {
        public static final int main=0x7f070000;
    }
    public static final class string {
        public static final int action_settings=0x7f050001;
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050002;
    }
}

静态嵌套类就像任何其他外部类一样,因为它无法访问外部类成员 - 这是不正确的。静态嵌套类(SNC)可以完全访问封闭类的所有类成员。它无法访问“实例成员”。请参见内部类和嵌套静态类示例 - user47

10

首先,非静态内部类具有额外的隐藏字段,该字段指向外部类的实例。因此,如果Entry类不是静态的,那么除了具有它不需要的访问权限之外,它将携带四个指针而不是三个。

作为一条规则,我会说,如果您定义一个基本上用于充当数据成员集合(如C中的“struct”)的类,请考虑使其静态。


7

4

简单例子:

package test;

public class UpperClass {
public static class StaticInnerClass {}

public class InnerClass {}

public static void main(String[] args) {
    // works
    StaticInnerClass stat = new StaticInnerClass();
    // doesn't compile
    InnerClass inner = new InnerClass();
}
}

如果一个类是非静态的,那么除了在其上层类的实例中之外,它不能被实例化(例如 main 是静态函数的情况除外)。

你的StaticInnerClass实际上不是一个静态嵌套/内部类,而是一个顶层静态类。 - theRiley

2

静态和普通的区别之一与类加载有关。您不能在其父类的构造函数中实例化内部类。

PS:我一直认为“嵌套”和“内部”是可以互换的。这些术语可能存在微妙的差异,但大多数Java开发人员都能理解其中任何一个。


2
  1. JVM knows no nested classes. Nesting is just syntactic sugar.

    Below images shows Java file:

    enter image description here

    Below images show class files representation of the java file :

    enter image description here

    Notice that 2 class files are generated, one for parent and another for nested class.

  2. Non-static nested class' objects have access to the enclosing scope. That access to the enclosing scope is maintained by holding an implicit reference of the enclosing scope object in the nested object

  3. Nested class is a way to represent the intent that the nested class type represents a component of the parent class.

    public class Message {
    
    private MessageType messageType; // component of parent class
    
    public enum MessageType {
        SENT, RECEIVE;
    }
    }
    
    
    
    class Otherclass {
    
    public boolean isSent(Message message) {
        if (message.getMessageType() == MessageType.SENT) { // accessible at other places as well
            return true;
        }
        return false;
    }
    }
    
  4. private static nested class represents Point#3 & the fact the nested type can only be the subcomponent to the parent class. It can't be used separately.

    public class Message {
    
     private Content content; // Component of message class
    
     private static class Content { // can only be a component of message class
    
      private String body;
      private int sentBy;
    
      public String getBody() {
         return body;
      }
    
      public int getSentBy() {
         return sentBy;
      }
    
    }
    }
    
    class Message2 {
      private Message.Content content; // Not possible
    }
    
  5. More details here.


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