Java内部类和静态嵌套类

2073

Java中内部类和静态嵌套类的主要区别是什么?在选择其中一种时,设计/实现是否起到作用?


99
Joshua Bloch在《Effective Java》一书的第22条建议中提到:优先使用静态成员类而不是非静态成员类。 - Raymond Chenon
24
记录一下,它是同一本书的第三版中的第24项。 - ZeroCool
28个回答

10

我认为这里没有什么需要补充的,大多数答案已经很好地解释了静态嵌套类和内部类之间的区别。然而,当使用嵌套类与内部类时,请考虑以下问题。 正如一些答案中提到的,内部类不能在没有封闭类实例的情况下被实例化,这意味着它们持有对其封闭类实例的指针,这可能会导致内存溢出或堆栈溢出异常,因为即使封闭类不再使用,GC也无法垃圾回收它们。为了澄清这一点,请查看以下代码:

public class Outer {


    public  class Inner {

    }


    public Inner inner(){
        return new Inner();
    }

    @Override
    protected void finalize() throws Throwable {
    // as you know finalize is called by the garbage collector due to destroying an object instance
        System.out.println("I am destroyed !");
    }
}


public static void main(String arg[]) {

    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();

    // out instance is no more used and should be garbage collected !!!
    // However this will not happen as inner instance is still alive i.e used, not null !
    // and outer will be kept in memory until inner is destroyed
    outer = null;

    //
    // inner = null;

    //kick out garbage collector
    System.gc();

}
如果你去掉对// inner = null;的注释,程序将输出"I am destroyed !",但是如果保留这个注释它就不会输出。
原因是当内部实例还有引用时,垃圾收集器无法回收它,而由于它引用(具有指向)了外部实例,所以外部实例也不会被回收。在项目中拥有足够多此类对象时,可能会导致内存耗尽。
与静态内部类相比,静态内部类不持有内部类实例的指针,因为它与实例无关,而是与类相关联的。如果你使内部类静态化并使用Outer.Inner i = new Outer.Inner();进行实例化,上述程序也可以打印"I am destroyed !"。

“静态内部”这个词是自相矛盾的。 - user207421

10

这些术语可以互换使用。如果您想真的严谨一点,那么您可以定义“嵌套类”是指没有封闭实例的静态内部类。在代码中,可能会像这样:

public class Outer {
    public class Inner {}

    public static class Nested {}
}

然而,这并不是一个被广泛接受的定义。


3
“static inner” 这个词本身就是自相矛盾的。 - user207421
7
定义内部类为非静态嵌套类的不是惯例,而是《Java语言规范》(JLS)。 https://docs.oracle.com/javase/zh_CN/specs/jls/se8/html/jls-8.html#jls-8.1.3 - Lew Bloch
1
而且这些术语不能“互换使用”。 - user207421

10

创建实例时,非静态内部类的实例是使用外部类对象的引用进行创建的,因为它是在定义了该内部类的外部类中创建的。这意味着它具有封闭实例。 但是,静态内部类的实例是使用外部类的引用而不是外部类对象的引用创建的。这意味着它没有封闭实例。

例如:

class A
{
  class B
  {
    // static int x; not allowed here…..    
  }
  static class C
  {
    static int x; // allowed here
  }
}

class Test
{
  public static void main(String… str)
  {
    A o=new A();
    A.B obj1 =o.new B();//need of inclosing instance

    A.C obj2 =new A.C();

    // not need of reference of object of outer class….
  }
}

1
“static inner” 是一个自相矛盾的说法。嵌套类要么是静态的,要么是内部的。 - user207421

9

嗯……内部类是嵌套类的一种形式……您是指匿名类和内部类吗?

编辑:如果您实际上是指内部类和匿名类:内部类只是在类内定义的一个类,例如:

public class A {
    public class B {
    }
}

…而匿名类是一个匿名定义的类的扩展,因此没有实际的“类”被定义,如下所示:

public class A {
}

A anon = new A() { /* You could change behavior of A here */ };

进一步编辑:

维基百科声称Java中有区别,但我已经使用Java工作了八年,这是我第一次听到这样的区别 - 更不用说没有引用来支持这种说法...总之,内部类是在类内定义的类(静态或非静态),而嵌套只是另一个意思,表示相同的事情。

静态和非静态嵌套类之间存在微妙的区别...基本上,非静态内部类隐式地访问封闭类的实例字段和方法(因此它们不能在静态上下文中构造,这将是编译器错误)。另一方面,静态嵌套类没有隐式访问实例字段和方法,并且可以在静态上下文中构造。


4
根据Java官方文档,内部类和静态嵌套类存在差异 -- 静态嵌套类不会引用其封闭类并且主要用于组织目的。如需更深入的描述,请查看Jegschemesch的回复。 - mipadi
1
我认为语义上的差异主要是历史原因造成的。当我编写C# -> Java 1.1编译器时,Java语言参考非常明确:嵌套类是静态的,内部类不是(因此具有this$0)。无论如何,这很令人困惑,我很高兴它现在不再是问题。 - Tomer Gabel
2
JLS在https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.3中定义了“内部类”,这就是为什么Java中不可能有非静态的“内部类”。 “嵌套”不是“只是另一个意思”,而且“内部类是在类内定义的类(静态或非静态)”也不是真的。那是错误的信息。 - Lew Bloch

9
我认为以上答案都没有给你一个真正的例子,来说明嵌套类和静态嵌套类在应用程序设计方面的区别。而静态嵌套类和内部类之间的主要区别是能否访问外部类实例字段。
让我们来看看以下两个例子。
静态嵌套类:使用静态嵌套类的一个很好的例子是构建器模式(https://dzone.com/articles/design-patterns-the-builder-pattern)。
对于BankAccount,我们使用静态嵌套类,主要是因为:
  1. 静态嵌套类实例可以在外部类之前创建。
  2. 在构建器模式中,构建器是一个帮助类,用于创建BankAccount。
  3. BankAccount.Builder仅与BankAccount相关联。没有其他类与BankAccount.Builder相关。因此最好将它们组织在一起,而不使用名称约定。
public class BankAccount {

    private long accountNumber;
    private String owner;
    ...

    public static class Builder {

    private long accountNumber;
    private String owner;
    ...

    static public Builder(long accountNumber) {
        this.accountNumber = accountNumber;
    }

    public Builder withOwner(String owner){
        this.owner = owner;
        return this; 
    }

    ...
    public BankAccount build(){
            BankAccount account = new BankAccount(); 
            account.accountNumber = this.accountNumber;
            account.owner = this.owner;
            ...
            return account;
        }
    }
}

内部类:内部类的常见用途是定义事件处理程序。https://docs.oracle.com/javase/tutorial/uiswing/events/generalrules.html

对于MyClass,我们使用内部类,主要因为:

  1. 内部类MyAdapter需要访问外部类成员。

  2. 在本例中,MyAdapter仅与MyClass相关联。没有其他类与MyAdapter相关,因此最好将它们组织在一起而不使用名称约定。

public class MyClass extends Applet {
    ...
        someObject.addMouseListener(new MyAdapter());
    ...
    class MyAdapter extends MouseAdapter {
        public void mouseClicked(MouseEvent e) {
            ...// Event listener implementation goes here...
            ...// change some outer class instance property depend on the event
        }
    }
}

从设计角度来看,非常有帮助的答案。 - voila

8

嵌套类是一个非常通用的术语:每个不是顶级的类都是一个嵌套类。 内部类是一个非静态的嵌套类。 Joseph Darcy写了一篇非常好的关于嵌套、内部、成员和顶层类的解释。


8

针对初学者,包括Java和/或嵌套类的基础知识

嵌套类可以是:
1. 静态嵌套类。
2. 非静态嵌套类(也称为内部类) => 请记住这一点。


1. 内部类
例如:

class OuterClass  {
/*  some code here...*/
     class InnerClass  {  }
/*  some code here...*/
}


内部类是嵌套类的子集:

  • 内部类是特定类型的嵌套类
  • 内部类是嵌套类的子集
  • 你可以说一个内部类也是一个嵌套类,但你不能说一个嵌套类也是一个内部类。

内部类的特点:

  • 内部类的实例可以访问外部类的所有成员变量和方法,即使它们被标记为“私有”


2.静态嵌套类:
示例:

class EnclosingClass {
  static class Nested {
    void someMethod() { System.out.println("hello SO"); }
  }
}

案例1:从非封闭类实例化静态嵌套类
class NonEnclosingClass {

  public static void main(String[] args) {
    /*instantiate the Nested class that is a static
      member of the EnclosingClass class:
    */

    EnclosingClass.Nested n = new EnclosingClass.Nested(); 
    n.someMethod();  //prints out "hello"
  }
}

案例2:从封闭类实例化静态嵌套类
class EnclosingClass {

  static class Nested {
    void anotherMethod() { System.out.println("hi again"); } 
  }

  public static void main(String[] args) {
    //access enclosed class:

    Nested n = new Nested(); 
    n.anotherMethod();  //prints out "hi again"
  }

}

静态类的特点:

  • 静态内部类只能访问外部类的静态成员,无法访问非静态成员。

结论:
问题:在Java中,内部类和静态嵌套类之间的主要区别是什么?
答案:只需查看上面提到的每个类的具体细节即可。


"静态内部"是一个自相矛盾的说法。 - user207421

6
在Java中,内部类和嵌套静态类都是在另一个类中声明的类,称为Java中的顶层类。在Java术语中,如果您声明了一个嵌套类为静态,则它将被称为Java中的嵌套静态类,而非静态嵌套类只是简单地称为内部类。
什么是Java中的内部类? 任何不是顶级或声明在另一个类中的类都被称为嵌套类,在这些嵌套类中,被声明为非静态的类被称为Java中的内部类。Java中有三种类型的内部类: 1)局部内部类 - 声明在代码块或方法内。 2)匿名内部类 - 是一种没有名称引用并且在创建时初始化的类。 3)成员内部类 - 被声明为外部类的非静态成员。
public class InnerClassTest {
    public static void main(String args[]) {      
        //creating local inner class inside method i.e. main() 
        class Local {
            public void name() {
                System.out.println("Example of Local class in Java");

            }
        }      
        //creating instance of local inner class
        Local local = new Local();
        local.name(); //calling method from local inner class

        //Creating anonymous inner class in Java for implementing thread
        Thread anonymous = new Thread(){
            @Override
            public void run(){
                System.out.println("Anonymous class example in java");
            }
        };
        anonymous.start();

        //example of creating instance of inner class
        InnerClassTest test = new InnerClassTest();
        InnerClassTest.Inner inner = test.new Inner();
        inner.name(); //calling method of inner class
    }

     //Creating Inner class in Java
    private class Inner{
        public void name(){
            System.out.println("Inner class example in java");
        }
    }
}

什么是Java中的嵌套静态类?

嵌套静态类是另一个被声明为成员并被设置为静态的类。嵌套静态类也被声明为外部类的成员,可以像其他成员一样被设置为私有、公共或受保护。与内部类相比,嵌套静态类的主要优点之一是嵌套静态类的实例不附加到任何封闭实例的外部类。在Java中创建嵌套静态类的实例时,您也不需要任何外部类的实例

1)它可以访问外部类的静态数据成员,包括私有成员。
2)静态嵌套类无法访问非静态(实例)数据成员方法

public class NestedStaticExample {
    public static void main(String args[]){  
        StaticNested nested = new StaticNested();
        nested.name();
    }  
    //static nested class in java
    private static class StaticNested{
        public void name(){
            System.out.println("static nested class example in java");
        }
    }
}

参考:Java中内部类和静态嵌套类的区别及示例


3
“静态嵌套类无法访问非静态(实例)成员或方法。”这句话是不正确的,并且会导致混淆。它们确实可以访问私有实例信息——只要创建一个实例来访问该实例信息即可。它们没有像内部类那样的封闭实例,但它们确实可以访问其封闭类的实例私有成员。 - T.J. Crowder

5

一张图示

在此输入图片描述

静态嵌套类非静态嵌套类的主要区别在于,静态嵌套类无法访问非静态外部类成员。


4

我认为这里的人们应该注意到,静态嵌套类只是第一个内部类。

例如:

 public static class A {} //ERROR

 public class A {
     public class B {
         public static class C {} //ERROR
     }
 }

 public class A {
     public static class B {} //COMPILE !!!

 }

因此,总结一下,静态类不依赖于其所包含的类。因此,它们不能在普通类中使用(因为普通类需要一个实例)。

2
这都是胡说八道。这只是表明内部类不能包含一个静态类。关于“它包含哪个类并不重要”的部分是没有意义的,下一句话也是如此。 - user207421

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