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


217

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

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

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

}

使用静态嵌套类而不是普通内部类的原因是什么?

我能想到的唯一原因是Entry不能访问实例变量,因此从OOP的角度来看,它具有更好的封装性。

但是我认为可能还有其他原因,也许是性能。可能是什么?

注意。我希望我的术语正确,可以将其称为静态内部类,但我认为这是错误的:http : //java.sun.com/docs/books/tutorial/java/javaOO/nested.html


Answers:


271

链接到的Sun页面在两者之间有一些关键区别:

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

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

不需要LinkedList.Entry是顶级类,因为它被使用LinkedList(有些其他接口也具有名为的静态嵌套类Entry,例如Map.Entry-相同的概念)。而且由于它不需要访问LinkedList的成员,因此使其成为静态是有意义的-这是一种更为简洁的方法。

正如Jon Skeet指出的那样,如果您使用的是嵌套类,则我认为一个更好的主意是从它的静态开始,然后根据您的使用情况确定它是否真的需要是非静态的。


Bah,我似乎无法获得该评论固定链接,但其评论如下:#comment113712_253507
Zach Lysobey 2012年

1
@matt b如果静态嵌套类无权访问Outer类的实例成员,则它如何与Outer类的实例成员交互?
极客2013年

1
@mattb但是@Geek注意到了,Sun页面是矛盾的: A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class 如果文档前的一段话说那怎么可能: Static nested classes do not have access to other members of the enclosing class 也许他们想说: A nested (non-static) class interacts with the instance members of its outer class (and other classes) just like any other top-level class
tonix

1
@DavidS感谢您的链接!是的,我错了,现在阅读我的评论,我发现我的措词不正确。正如你所说的:An inner class interacts with the instance members through an implicit reference to its enclosing class,这点出了另一个有趣的特性non-static inner classes,以及anonymous inner classes或者local classes defined inside a block:他们都不能有一个no-arg构造原因编译器将隐含在前面加上为了每一个构造函数的ARG序列通过封闭的实例的引用类。很简单
tonix

1
您可以使用静态内部类实例化仅具有私有构造函数的外部类。这在构建器模式中使用。您不能对内部类执行相同的操作。
seenimurugan

47

在我看来,每当您看到一个内部类时,问题都应该是相反的- 确实需要一个内部类吗?它具有额外的复杂性,并且对实例的隐式(而不是显式和清晰的)引用包含类的?

提醒您,我偏向C#迷-C#虽然具有嵌套类型,但没有等效的内部类。我不能说我已经错过了内部课程:)


4
我可能是错的,但是在我看来,这就像是静态嵌套类的示例,而不是内部类。他们甚至在示例中指定他们无权访问嵌套类中周围类的实例变量。
ColinD

是的,Colin是对的-C#没有内部类,它具有嵌套类。请注意,C#中的静态嵌套类与Java中的静态嵌套类不同:)
Jon Skeet

2
嵌套类型是C#与Java相比极其正确的领域之一。我总是赞叹它的语义/逻辑正确性
。– nawfal

3
@nawfal:是的,除非我对C#语言的设计(和指定)程度感到敬畏,否则对此我有些敬畏。
乔恩·斯基特

1
@JonSkeet您是否有文章或博客关注这些小问题?我很乐意通过您发现的“小问题” :)
nawfal 2013年

27

这里有一些非显而易见的内存保留问题需要考虑。由于非静态内部类维护对其“外部”类的隐式引用,因此,如果强烈引用内部类的实例,则也强烈引用外部实例。当外部类没有被垃圾收集时,即使看起来没有人引用它,这也可能导致一些麻烦。


如果“外部”类是最终类,因此根本无法实例化,那么在这种情况下此参数有意义吗?因为它具有/保持对外部类的引用是没有用的,如果后者是最终的。
getsadzeg '19

10

好吧,一方面,非静态内部类具有一个额外的隐藏字段,该字段指向外部类的实例。因此,如果Entry类不是静态的,则除了具有不需要的访问权限外,它还将携带四个指针而不是三个指针。

通常,我想说的是,如果您定义一个基本上存在的类来充当数据成员的集合(如C中的“结构”),请考虑使其成为静态的。


8

静态内部类在构建器模式中使用。静态内部类可以实例化只有私有构造函数的外部类。因此,您可以使用静态内部类实例化仅具有私有构造函数的外部类。对于内部类,您不能执行相同的操作,因为需要在访问内部类之前创建外部类的对象。

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


7

静态嵌套类与其他任何外部类一样,因为它无法访问外部类成员。

为了包装上的方便,我们可以将静态嵌套类合并为一个外部类,以提高可读性。除此之外,没有静态嵌套类的其他用例。

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

由于所有文件夹都是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;
    }
}


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开发人员都会理解。


1

非静态内部类可能导致内存泄漏,而静态内部类可以防止此类泄漏。如果外部类拥有大量数据,则可能会降低应用程序的性能。


术语“静态内部”是一个矛盾。
罗恩侯爵

1
@EJP,嘘...任何时候有人提到“静态内部类”,人们都会真正下车……
Sakiboy

0

我不了解性能差异,但是正如您所说的,静态嵌套类不是封闭类实例的一部分。除非您确实需要将其作为内部类,否则创建静态嵌套类似乎更简单。

有点像为什么我总是在Java中将变量定为final-如果它们不是final,我知道它们之间会发生一些有趣的事情。如果使用内部类而不是静态嵌套类,则应该有充分的理由。


内部类也不是“封闭类实例的一部分”。
罗恩侯爵,

内部类从本质上依赖于封闭类,并且可以直接访问封闭类的成员,因此它实际上是封闭类的一部分。实际上,它是成员。
theRiley

0

在某些情况下,使用静态嵌套类而不是非静态嵌套类可以节省空间。例如:Comparator说说在班级内部实现学生。

public class Student {
  public static final Comparator<Student> BY_NAME = new ByName();
  private final String name;
  ...
  private static class ByName implements Comparator<Student> {
    public int compare() {...}
  }
}

然后static确保Student类只有一个Comparator,而不是在每次创建新的Student实例时都实例化一个新的Comparator。


-1

内部阶级的优势

  1. 一次使用
  2. 支持并改善封装
  3. 可读性
  4. 私有领域访问

没有外部类的存在,内部类将不存在。

class car{
    class wheel{

    }
}

内部类有四种类型。

  1. 普通的内部阶级
  2. 方法本地内部类
  3. 匿名内部阶级
  4. 静态内部类

点-

  1. 从静态内部类中,我们只能访问外部类的静态成员。
  2. 在内部类内部,我们无法声明静态成员。
  3. 为了在外部类的静态区域中调用普通内部类。

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  4. 为了在外部类的实例区域中调用普通内部类。

    Inner i=new Inner();

  5. 为了在外部类的外部调用普通内部类。

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  6. inside内部类此指针指向内部类。

    this.member-current inner class outerclassname.this--outer class

  7. 对于内部类,适用的修饰符是-public,default,

    final,abstract,strictfp,+private,protected,static

  8. outside $ inner是内部类名称的名称。

  9. 内部类的实例方法,然后我们可以访问外部类的静态和实例字段。

10.内部类里面有静态方法,那么我们只能访问静态字段的

外层阶级。

class outer{

    int x=10;
    static int y-20;

    public void m1() {
        int i=30;
        final j=40;

        class inner{

            public void m2() {
                // have accees x,y and j
            }
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.