问题

Java中的内部类和静态嵌套类之间的主要区别是什么?设计/实施在选择其中一种方面起作用吗?


#1 热门回答(1476 赞)

Java Tutorial

嵌套类分为两类:静态和非静态。被声明为静态的嵌套类简单地称为静态嵌套类。非静态嵌套类称为内部类。

静态嵌套类可以使用封闭类名来访问:

OuterClass.StaticNestedClass

例如,要为静态嵌套类创建对象,请使用以下语法:

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

作为内部类的实例的对象存在于外部类的实例中。考虑以下类:

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

InnerClass的一个实例只能存在于OuterClass的一个实例中,并且可以直接访问其封闭实例的方法和字段。

要实例化一个内部类,你必须首先实例化外部类。然后,使用以下语法在外部对象内创建内部对象:

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

见:Java Tutorial - Nested Classes

为了完整性,请注意,不带封闭实例的内部类也是如此:

class A {
  int t() { return 1; }
  static A a =  new A() { int t() { return 2; } };
}

在这里,new A(){...}是一个在静态中定义的内部类,并且没有封闭的实例。


#2 热门回答(520 赞)

TheJava tutorial says

术语:嵌套类分为两类:静态和非静态。被声明为静态的嵌套类简单地称为静态嵌套类。非静态嵌套类称为内部类。

按照一般的说法,术语“嵌套”和“内部”可以被大多数程序员交换使用,但是我将使用正确的术语“嵌套类”,它涵盖内部和静态。

类可以无限制地嵌套,例如类A可以包含类B,其中包含包含类D的类C等。但是,多于一级的类嵌套很少见,因为它通常是不好的设计。

有三个原因可以创建一个嵌套类:

  • 组织:有时候,将一个类排序到另一个类的名称空间似乎是最明智的,特别是当它不会用于其他任何语境时
  • 访问:嵌套类对其包含的类的变量/字段有特殊的访问权限(确切地说,哪些变量/字段取决于嵌套类的类型,无论是内部还是静态的)。
  • 便利性:不得不为每个新类型创建一个新文件都很麻烦,特别是当这个类型只用于一个上下文时

在Java中有种四种嵌套类。简而言之,它们是:

  • 静态类:声明为另一个类的静态成员
  • 内部类:声明为另一个类的实例成员
  • 本地内部类:在另一个类的实例方法中声明
  • 匿名内部类:类似于本地内部类,但写为返回一次性对象的表达式

让我详细说明一下。

##静态类

静态类是最容易理解的类型,因为它们与包含类的实例无关。

静态类是被声明为另一个类的静态成员的类。就像其他静态成员一样,这样的类实际上只是一个挂钩,它使用包含类作为其名称空间,例如,类Goatdeclared作为classphino中的静态成员,名为pizza.Rhino.Goat。

package pizza;

public class Rhino {

    ...

    public static class Goat {
        ...
    }
}

坦率地说,静态类是一个相当不值钱的功能,因为类已经被包分解成名称空间。创建一个静态类的唯一真正可以想象的理由是,这样的类可以访问其包含的类的私有静态成员,但是我觉得这是静态类特性存在的一个非常蹩脚的理由。

##内部类

内部类是声明为另一个类的非静态成员的类:

package pizza;

public class Rhino {

    public class Goat {
        ...
    }

    private void jerry() {
        Goat g = new Goat();
    }
}

与静态类一样,内部类被称为包含类名称pizza.Rhino.Goat的限定,但在包含类中,可以通过它的简单名称来知道它。然而,内部类的每个实例都与其包含的类的特定实例绑定在一起:上面的Goatcreated injrit隐含地绑定到了这个实例。否则,我们在实例化山墙时使相关的Rhino实例显式化:

Rhino rhino = new Rhino();
Rhino.Goat goat = rhino.new Goat();

(请注意,您将内部类型称为Goatin the newnewsyntax:Java推断来自therhinopart的包含类型,而且,新的rhino.Goat()对我也更有意义。)

那么这对我们有什么好处呢?好吧,内部类实例可以访问包含类实例的实例成员。这些封闭的实例成员在内部类中被引用,它们的简单名称notviathis(内部类引用内部类实例,而不是关联的包含类实例):

public class Rhino {

    private String barry;

    public class Goat {
        public void colin() {
            System.out.println(barry);
        }
    }
}

在内部类中,你可以把包含的类作为Rhino.this来引用,你可以用它来指它的成员,例如。 Rhino.this.barry。

Local Inner Classes

本地内部类是在方法体中声明的类。这样的类只在其包含的方法中是已知的,所以它只能被实例化,并且在其包含的方法中访问其成员。增益是一个本地内部类实例绑定并可以访问其包含方法的最终局部变量。当实例使用其包含方法的最后一个局部变量时,即使变量超出了范围(这实际上是Java原始的,限制版本的闭包),变量仍会保留实例创建时的值。

因为本地内部类既不是类或包的成员,也不会声明具有访问级别。 (但是,要清楚自己的成员具有像普通课程那样的访问级别。)

如果在一个实例方法中声明了一个本地内部类,那么内部类的实例化与包含方法所持有的实例绑定在实例的创建时间上,因此包含类的实例成员在实例中是可访问的内心阶层。本地内部类是通过名称实例化的,例如本地内部classCatis实例化为新的Cat(),而不是像你期望的那样新建this.Cat()。

##匿名内部类

匿名内部类是一种语法上方便的方法写一个当地的内部课堂。最常见的情况是,每次运行包含的方法时,最多只会实例化一次本地内部类。那么,如果我们能够将本地内部类定义和它的单一实例化合并成一个方便的语法形式,那将是很好的,如果我们不需要为该类考虑一个名字(更不利用您的代码包含的名称越多越好)。一个匿名的内部类允许这些东西:

new *ParentClassName*(*constructorArgs*) {*members*}

这是一个表达式,返回extendsParentClassName的未命名类的新实例。你不能提供你自己的构造函数;而是隐式提供的,它只是简单地调用超级构造函数,所以提供的参数必须适合超级构造函数。 (如果父项包含多个构造函数,那么调用“最简单”的构造函数,这是由一组相当复杂的规则确定的“最简单”规则,不值得深入研究 - 只需注意NetBeans或Eclipse告诉您的内容。)

或者,您可以指定一个接口来实现:

new *InterfaceName*() {*members*}

这样的声明创建了一个扩展了Object和implementsInterfaceName的未命名类的新实例。再次,你不能提供你自己的构造函数;在这种情况下,Java隐含地提供了一个无参数,无所作为的构造函数(所以在这种情况下永远不会有构造函数参数)。

即使你不能给一个匿名的内部类构造函数,你仍然可以使用初始化块(放置在任何方法之外的块)执行任何设置。

要明确一个匿名的内部类是一种不太灵活的创建一个本地内部类的方法。如果你想要一个实现多个接口的本地内部类,或者实现了接口,同时扩展了除指定自己的构造函数的其他类以外的类,那么你就不能创建一个常规的命名本地内部类。


#3 热门回答(124 赞)

我不认为真正的区别在上述答案中变得清晰。

首先让条款正确:

  • 嵌套类是一个类,它包含在源代码级别的另一个类中。
  • 如果使用static修饰符声明它,它是静态的。
  • 非静态嵌套类称为内部类。 (我留在非静态嵌套类。)

马丁的回答是正确的。但是,实际的问题是:声明一个嵌套类是否静态的目的是什么?

你使用静态嵌套类如果你只是想把你的类放在一起,如果它们属于局部在一起,或者如果嵌套类专用于封闭类。静态嵌套类与其他类没有语义上的区别。

非静态嵌套类是一个不同的野兽。类似于匿名内部类,这样的嵌套类实际上是闭包。这意味着他们捕捉周围的范围和他们的封闭实例并使其可访问。也许一个例子会澄清这一点。查看容器的这个存根:

public class Container {
    public class Item{
        Object data;
        public Container getContainer(){
            return Container.this;
        }
        public Item(Object data) {
            super();
            this.data = data;
        }

    }

    public static Item create(Object data){
        // does not compile since no instance of Container is available
        return new Item(data);
    }
    public Item createSubItem(Object data){
        // compiles, since 'this' Container is available
        return new Item(data);
    }
}

在这种情况下,您希望从子项目到父容器的引用。使用一个非静态的嵌套类,这个工作没有一些工作。您可以使用语法Container.this访问Container的封闭实例。

以下更多核心解释:

如果查看编译器为(非静态)嵌套类生成的Java字节码,它可能会变得更加清晰:

// class version 49.0 (49)
// access flags 33
public class Container$Item {

  // compiled from: Container.java
  // access flags 1
  public INNERCLASS Container$Item Container Item

  // access flags 0
  Object data

  // access flags 4112
  final Container this$0

  // access flags 1
  public getContainer() : Container
   L0
    LINENUMBER 7 L0
    ALOAD 0: this
    GETFIELD Container$Item.this$0 : Container
    ARETURN
   L1
    LOCALVARIABLE this Container$Item L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 1
  public <init>(Container,Object) : void
   L0
    LINENUMBER 12 L0
    ALOAD 0: this
    ALOAD 1
    PUTFIELD Container$Item.this$0 : Container
   L1
    LINENUMBER 10 L1
    ALOAD 0: this
    INVOKESPECIAL Object.<init>() : void
   L2
    LINENUMBER 11 L2
    ALOAD 0: this
    ALOAD 2: data
    PUTFIELD Container$Item.data : Object
    RETURN
   L3
    LOCALVARIABLE this Container$Item L0 L3 0
    LOCALVARIABLE data Object L0 L3 2
    MAXSTACK = 2
    MAXLOCALS = 3
}

正如你所看到的,编译器创建一个隐藏字段'Container this $ 0`。这是在具有Container类型的附加参数的构造函数中设置的,以指定封闭实例。您无法在源代码中看到此参数,但编译器为嵌套类隐式生成该参数。

马丁的例子

OuterClass.InnerClass innerObject = outerObject.new InnerClass();

会被编译成类似的调用(在字节码中)

new InnerClass(outerObject)

为了完整起见:

匿名类a非静态嵌套类的完美示例,它没有与之关联的名称,以后不能引用。


原文链接