Java中内部类和静态嵌套类的主要区别是什么?设计/实现是否在选择其中一个方面发挥作用?


当前回答

Java教程说:

术语:嵌套类是分为两类:静态并且是非静态的。嵌套的类被声明为静态的静态嵌套类。非静态嵌套类称为内部类类。

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

类可以无限嵌套,例如类A可以包含B类,B类包含C类,C类包含D类等。但是,很少有一个以上的类嵌套,因为这通常是错误的设计。

创建嵌套类有三个原因:

组织:有时将一个类分类到另一个类的命名空间中似乎是最明智的,尤其是当它不会在任何其他上下文中使用时访问:嵌套类具有对其包含类的变量/字段的特殊访问权限(确切地说,哪些变量/字段取决于嵌套类的类型,无论是内部的还是静态的)。方便:必须为每个新类型创建一个新文件,这同样令人烦恼,尤其是当该类型只在一个上下文中使用时

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

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

让我详细说明一下。

静态类

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

静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,这样的类实际上只是一个使用包含类作为其名称空间的挂接器,例如,pizza包中声明为Rhino类静态成员的Goat类被称为pizza.Hino.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.Hino.Goat来限定,但在包含类内部,可以通过其简单名称来限定。然而,内部类的每个实例都与其包含类的一个特定实例相关联:上面,在jerry中创建的Goat被隐式地绑定到Rhino实例,这是在jerrry中创建的。否则,当我们实例化Goat时,我们将显式显示关联的Rhino实例:

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

(注意,你在奇怪的新语法中将内部类型称为Goat:Java从rhino部分推断出包含类型。是的,new rhino.Goat()对我来说也更有意义。)

那么这给我们带来了什么?内部类实例可以访问包含类实例的实例成员。这些封闭实例成员仅通过其简单名称在内部类中引用,而不是通过this(内部类中的this引用内部类实例,而不是关联的包含类实例):

public class Rhino {

    private String barry;

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

在内部类中,您可以将包含类的this引用为Rhino.this,也可以使用它来引用其成员,例如Rhino.tis.barry。

本地内部类

本地内部类是在方法体中声明的类。这样的类只在其包含方法中是已知的,因此只能在其包含的方法中实例化并访问其成员。这样做的好处是,本地内部类实例绑定到并可以访问其包含方法的最终本地变量。当实例使用其包含方法的最终局部变量时,即使变量已超出范围(这实际上是Java的粗糙、有限版本的闭包),该变量仍保留其在创建实例时所保持的值。

因为本地内部类既不是类或包的成员,所以它没有用访问级别声明。(但是,要清楚,它自己的成员拥有与普通类一样的访问级别。)

如果在实例方法中声明了本地内部类,则在创建实例时,内部类的实例化与包含方法的this所持有的实例相关联,因此包含类的实例成员可以像在实例内部类中一样访问。本地内部类仅通过其名称实例化,例如,本地内部类Cat被实例化为new Cat(),而不是您可能期望的new this.Cat()。

匿名内部类

匿名内部类是编写本地内部类的语法上方便的方法。最常见的情况是,本地内部类在每次运行其包含方法时最多实例化一次。那么,如果我们能够将本地内部类定义及其单个实例化组合成一种方便的语法形式,这将是很好的,如果我们不必为类想出一个名称(代码中包含的无用名称越少越好),这也是很好的。匿名内部类同时允许以下两种功能:

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

这是一个返回扩展ParentClassName的未命名类的新实例的表达式。您不能提供自己的构造函数;相反,一个是隐式提供的,它只是调用超级构造函数,因此提供的参数必须适合超级构造函数。(如果父级包含多个构造函数,则称为“最简单”的构造函数,由一组相当复杂的规则决定,不值得详细学习——只需注意NetBeans或Eclipse告诉您的内容。)

或者,您可以指定要实现的接口:

new *InterfaceName*() {*members*}

这样的声明创建了一个未命名类的新实例,该类扩展了Object并实现了InterfaceName。同样,您不能提供自己的构造函数;在本例中,Java隐式地提供了一个无参数、不做任何事情的构造函数(因此在本例中将不会有构造函数参数)。

即使您不能给匿名内部类一个构造函数,您仍然可以使用初始值设定项块(放置在任何方法外部的{}块)进行任何设置。

请注意,匿名内部类只是用一个实例创建本地内部类的一种不太灵活的方式。如果您想要一个实现多个接口的本地内部类,或者在扩展除Object之外的某个类时实现接口,或者指定自己的构造函数,那么您只能创建一个常规的命名本地内部类。

其他回答

嵌套类:类内的类

类型:

静态嵌套类非静态嵌套类[内部类]

差异:

非静态嵌套类[内部类]

在非静态嵌套类中,内部类的对象存在于外部类的对象中。因此,内部类可以访问外部类的数据成员。所以要创建内部类的对象,我们必须首先创建外部类的对象。

outerclass outerobject=new outerobject();
outerclass.innerclass innerobjcet=outerobject.new innerclass(); 

静态嵌套类

在静态嵌套类中,内部类的对象不需要外部类的对象,因为单词“static”表示不需要创建对象。

class outerclass A {
    static class nestedclass B {
        static int x = 10;
    }
}

如果要访问x,请在内部方法中写入以下内容

  outerclass.nestedclass.x;  i.e. System.out.prinltn( outerclass.nestedclass.x);

从Java教程:

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

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

OuterClass.StaticNestedClass

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

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

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

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

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

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

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

请参见:Java教程-嵌套类

为了完整性,请注意还有这样一种东西,即没有封闭实例的内部类:

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

这里,new A(){…}是在静态上下文中定义的内部类,没有封闭实例。

不同之处在于,同样是静态的嵌套类声明可以在封闭类之外实例化。

当您有一个非静态的嵌套类声明(也称为内部类)时,Java不允许您实例化它,除非通过封闭类。从内部类创建的对象链接到从外部类创建的目标,因此内部类可以引用外部类的字段。

但是如果它是静态的,那么链接就不存在,外部字段就不能被访问(除非像其他对象一样通过普通引用),因此您可以自己实例化嵌套类。

我认为上面的答案都没有向您解释嵌套类和静态嵌套类在应用程序设计方面的真正区别:

概述

嵌套类可以是非静态的或静态的,并且在每种情况下都是在另一个类中定义的类。嵌套类应该只存在于服务于封闭类,如果嵌套类对其他类(不仅仅是封闭类)有用,则应该声明为顶级类。

差别

非静态嵌套类:与包含类的封闭实例隐式关联,这意味着可以调用封闭实例的方法和访问变量。非静态嵌套类的一个常见用法是定义Adapter类。

静态嵌套类:无法访问封闭类实例并在其上调用方法,因此当嵌套类不需要访问封闭类的实例时应使用。静态嵌套类的一个常见用法是实现外部对象的组件。

结论

所以从设计角度来看,两者之间的主要区别是:非静态嵌套类可以访问容器类的实例,而静态类不能。

我认为这里没有什么要补充的,大多数答案完美地解释了静态嵌套类和内部类之间的区别。但是,当使用嵌套类与内部类时,请考虑以下问题。正如在两个答案中提到的,如果没有封闭类的实例,内部类就无法实例化,这意味着它们持有指向封闭类实例的指针,这可能导致内存溢出或堆栈溢出异常,因为即使不再使用封闭类,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;该计划将付诸实施“我被摧毁了!”,但保持评论不会。原因是白色内部实例仍然被引用,GC无法收集它,并且因为它引用(具有指向)外部实例,所以它也没有被收集。项目中有足够的这些对象,可能会耗尽内存。与静态内部类相比,静态内部类不指向内部类实例,因为它与实例无关,而是与类相关。如果将Inner类设置为静态并使用Outer.Inner I=newOuter.Innr()实例化,则上述程序可以打印“我被摧毁了!”;