Java中内部类和静态嵌套类的主要区别是什么?在选择其中一种时,设计/实现是否起到作用?
Java中内部类和静态嵌套类的主要区别是什么?在选择其中一种时,设计/实现是否起到作用?
来自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() { ... }
是在静态上下文中定义的内部类,并且没有封闭实例。import OuterClass.StaticNestedClass;
然后仅将类引用为OuterClass。请注意,这不会改变原来的意思。 - Camilo Díaz RepkaJava教程中提到:
术语: 嵌套类被分为两类:静态和非静态。声明为静态的嵌套类简称为静态嵌套类。非静态嵌套类被称为内部类。
在通俗的说法中,大多数程序员使用“嵌套”和“内部”这两个词是可以互换的,但我会使用正确的术语“嵌套类”,这个术语既包括内部类也包括静态嵌套类。
类可以进行无限嵌套,例如类A可以包含类B,类B又包含类C,类C又包含类D等等,但是超过一层类的嵌套很少出现,因为通常情况下这样做设计不佳。
有三个原因可能会创建嵌套类:
Java中有四种嵌套类,它们分别是:
让我详细解释一下:
静态类最容易理解,因为它们与包含类的实例无关。
静态类是作为另一个类的静态成员声明的类。就像其他静态成员一样,这样的类实际上只是使用包含类作为其命名空间的吊挂物,例如,在包pizza中声明为类Rhino的静态成员的类Goat将称为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)限定的,但在包含类中,它可以用简单名称来表示。然而,每个内部类的实例都与其包含类的特定实例绑定在一起:在上面的例子中,jerry中创建的Goat隐式地与jerry中的this Rhino实例绑定在一起。否则,在实例化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);
}
}
}
在内部类中,可以使用 Rhinoceros.this 来引用包含类的this,并且您可以使用 this 引用其成员,例如 Rhinoceros.this.barry 。new *ParentClassName*(*constructorArgs*) {*members*}
这是一个表达式,返回一个未命名的类的新实例,该类继承自ParentClassName。您不能提供自己的构造函数;相反,会隐含地提供一个构造函数,该构造函数仅调用超级构造函数,因此提供的参数必须适合超级构造函数。(如果父类包含多个构造函数,则调用“最简单”的构造函数,“最简单”由一组相当复杂的规则决定,不值得详细学习--只需注意NetBeans或Eclipse提示即可。)
另外,您可以指定要实现的接口:
new *InterfaceName*() {*members*}
这样的声明将创建一个继承自Object并实现InterfaceName接口的未命名类的新实例。同样,您不能提供自己的构造函数。在这种情况下,Java隐式地提供了一个无参、什么也不做的构造函数(因此,在这种情况下永远不会有构造函数参数)。
即使您不能给匿名内部类提供构造函数,您仍然可以使用初始化块(放置在任何方法外面的{}块)进行任何所需的设置。
请明确,匿名内部类只是一种创建具有一个实例的局部内部类的不太灵活的方式。如果您想要实现多个接口的局部内部类,或者实现继承自Object之外的某些类的接口,或者指定自己的构造函数,则必须创建常规的命名局部内部类。
我认为以上答案中并没有清楚地表明真正的区别。
首先要明确术语:
Martin的答案到目前为止是正确的。然而,实际问题是:声明嵌套类为静态或非静态的目的是什么?
如果您只想将类组合在一起以便于管理,或者嵌套类仅在封闭类中使用,您可以使用静态嵌套类。静态嵌套类和其他类之间没有语义差异。
非静态嵌套类则是不同的类型。类似于匿名内部类,这种嵌套类实际上是闭包。这意味着它们捕获其周围的作用域和封闭实例,并使其可访问。也许一个示例会澄清这个问题。请参阅此容器桩:
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 来指定封闭实例。您在源代码中看不到这个参数,但编译器会隐式为嵌套类生成它。
Martin 的示例
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
可以将其编译为类似以下字节码的调用
new InnerClass(outerObject)
为了完整起见:
匿名类是非静态嵌套类的典型例子,它没有与之关联的名称,并且无法在后面引用。
我认为以上答案都没有真正解释嵌套类和静态嵌套类在应用设计方面的真正区别:
嵌套类可以是非静态或静态的,在每种情况下都是在另一个类中定义的类。如果嵌套类仅为其封闭类服务,则应该存在于其中,如果嵌套类对其他类(不仅仅是封闭类)有用,则应将其声明为顶级类。
非静态嵌套类:与包含类的封闭实例隐式关联,这意味着可以调用封闭实例的方法和访问变量。非静态嵌套类的一个常见用途是定义适配器类。
静态嵌套类:无法访问封闭类实例并调用其方法,因此应在嵌套类不需要访问封闭类实例时使用。 静态嵌套类的一个常见用途是实现外部对象的组件。
所以从设计角度来看,两者之间的主要区别是:非静态嵌套类可以访问容器类的实例,而静态嵌套类则无法访问。
以下是Java内部类和静态嵌套类之间的关键差异和相似之处。
希望能对您有所帮助!
与封闭类的实例相关联,因此要实例化它首先需要一个外部类的实例(请注意new关键字的位置):
Outerclass.InnerClass innerObject = outerObject.new Innerclass();
无法定义任何静态成员
无法访问外部类的实例方法或字段
没有与封闭类的任何实例相关联,因此要实例化它:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
根据 Oracle 文档(完整文档)提供的解释,有以下几个原因:
它是一种逻辑上分组仅在一个位置使用的类的方式: 如果一个类仅对另一个类有用,则将其嵌入该类并将这两个类保持在一起是合理的。嵌套这样的“辅助类”使它们的包更加简洁。
它增加了封装性:考虑两个顶层类 A 和 B,其中 B 需要访问 A 的成员,否则这些成员将被声明为 private。通过将类 B 隐藏在类 A 中,A 的成员可以被声明为 private,并且 B 可以访问它们。此外,B 本身也可以对外部隐藏。
它可以带来更可读且易于维护的代码:将小类嵌套在顶层类中可以使代码更接近其使用位置。
简单来说,我们需要嵌套类主要是因为Java不提供闭包。
嵌套类是在另一个封闭类的内部定义的类。它们分为两种类型 - 静态和非静态。
它们被视为封闭类的成员,因此您可以指定任何四个访问修饰符 - private, package, protected, public
。我们无法使用顶层类来拥有这种豪华,因为顶层类只能声明为public
或package-private。
内部类(也称为非堆栈类)可以访问顶级类的其他成员,即使它们被声明为私有,而静态嵌套类则无法访问顶级类的其他成员。
public class OuterClass {
public static class Inner1 {
}
public class Inner2 {
}
}
Inner1
是我们的静态内部类,Inner2
是我们的非静态内部类。它们之间的主要区别在于,你不能创建一个没有外部类的实例Inner2
,而你可以独立地创建一个Inner1
对象。
何时使用内部类呢?
想象这样一种情况,类A
和类B
相关联,类B
需要访问类A
的成员,且类B
只与类A
相关。这时就可以使用内部类了。
要创建内部类的一个实例,你需要创建外部类的一个实例。
OuterClass outer = new OuterClass();
OuterClass.Inner2 inner = outer.new Inner2();
或者。OuterClass.Inner2 inner = new OuterClass().new Inner2();
何时使用静态内部类?
当您知道静态内部类与包含类/顶级类的实例没有任何关系时,您将定义一个静态内部类。如果您的内部类不使用外部类的方法或字段,则它只是一种浪费空间的东西,因此请将其定义为静态。
例如,要创建静态嵌套类的对象,请使用以下语法:
OuterClass.Inner1 nestedObject = new OuterClass.Inner1();
静态嵌套类的优点是它不需要包含类/顶层类的对象来工作。这有助于您在运行时减少应用程序创建的对象数量。
OuterClass.Inner2 inner = outer.new Inner2();
吗? - Erik Kaplun然而,还需要记住一些要点:
顶层类和静态嵌套类在语义上相同,唯一的区别是在静态嵌套类的情况下,它可以对其外部[父]类的私有静态字段/方法进行静态引用,反之亦然。
内部类可以访问外部[父]类的实例变量。但是,并不是所有的内部类都有封闭实例,例如在静态上下文中的内部类,例如在静态初始化程序块中使用的匿名类,没有。
匿名类默认扩展父类或实现父接口,并且没有进一步的子句来扩展任何其他类或实现更多接口。所以,
new YourClass(){};
的意思是 class [Anonymous] extends YourClass {}
new YourInterface(){};
的意思是 class [Anonymous] implements YourInterface {}
我认为仍然存在一个更大的问题,即何时使用哪个?这在很大程度上取决于您正在处理的情景,但阅读@jrudolph给出的回答可能有助于您做出一些决策。
嵌套类:在类内部定义的类
类型:
区别:
非静态嵌套类[内部类]
在非静态嵌套类中,内部类的对象存在于外部类的对象中。因此,外部类的数据成员可以被内部类访问。所以,要创建内部类的对象,我们必须先创建外部类的对象。
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);
内部类的实例在外部类的实例创建时同时创建。因此,内部类的成员和方法可以访问外部类实例(对象)的成员和方法。当外部类实例的作用域结束时,内部类实例也将不再存在。
静态嵌套类没有具体的实例。它们只是在第一次使用时加载(就像静态方法一样)。它是一个完全独立的实体,其方法和变量没有任何访问外部类实例的权限。
静态嵌套类与外部对象无关,速度更快,且不占用堆/栈内存,因为无需创建此类的实例。因此,经验法则是尝试定义静态嵌套类,并尽可能限制其范围(private >= class >= protected >= public),如果确实有必要,则将其转换为内部类(通过删除 "static" 标识符)并放宽范围。
使用嵌套静态类的一个微妙之处在于,在某些情况下可能会很有用。
与通过其构造函数实例化类之前实例化静态属性不同, 嵌套静态类内部的静态属性似乎直到调用类的构造函数,或者至少是在首次引用这些属性之后才被实例化,即使它们被标记为“final”。
考虑以下示例:
public class C0 {
static C0 instance = null;
// Uncomment the following line and a null pointer exception will be
// generated before anything gets printed.
//public static final String outerItem = instance.makeString(98.6);
public C0() {
instance = this;
}
public String makeString(int i) {
return ((new Integer(i)).toString());
}
public String makeString(double d) {
return ((new Double(d)).toString());
}
public static final class nested {
public static final String innerItem = instance.makeString(42);
}
static public void main(String[] argv) {
System.out.println("start");
// Comment out this line and a null pointer exception will be
// generated after "start" prints and before the following
// try/catch block even gets entered.
new C0();
try {
System.out.println("retrieve item: " + nested.innerItem);
}
catch (Exception e) {
System.out.println("failed to retrieve item: " + e.toString());
}
System.out.println("finish");
}
}
即使“嵌套”和“innerItem”都被声明为“static final”,但是设置nested.innerItem直到类被实例化(或至少在嵌套静态项首次被引用之后)才会发生,可以通过注释上面提到的行并取消注释来自己验证。对于“outerItem”则不适用。这至少是我在Java 6.0中看到的。