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之外的某个类时实现接口,或者指定自己的构造函数,那么您只能创建一个常规的命名本地内部类。

其他回答

嵌套类的另一个用例,除了已经提到的那些用例之外,是当嵌套类具有只能从外部类访问的方法时。这是可能的,因为外部类可以访问嵌套类的私有构造函数、字段和方法。

在下面的示例中,银行可以发行具有私有构造函数的Bank.CreditCard,并可以使用Bank.credit card的私有setLimit(…)实例方法根据当前银行策略更改信用卡的限额。从任何其他类只能访问Bank.CreditCard的公共方法。

public class Bank {

    // maximum limit as per current bank policy
    // is subject to change
    private int maxLimit = 7000;

    // ------- PUBLIC METHODS ---------

    public CreditCard issueCard(
            final String firstName,
            final String lastName
    ) {
        final String number = this.generateNumber();
        final int expiryDate = this.generateExpiryDate();
        final int CVV = this.generateCVV();
        return new CreditCard(firstName, lastName, number, expiryDate, CVV);
    }


    public boolean setLimit(
            final CreditCard creditCard,
            final int limit
    ) {
        if (limit <= this.maxLimit) {    // check against current bank policy limit
            creditCard.setLimit(limit);  // access private method Bank.CreditCard.setLimit(int)
            return true;
        }
        return false;
    }

    // ------- PRIVATE METHODS ---------

    private String generateNumber() {
        return "1234-5678-9101-1123";   // the numbers should be unique for each card
    }


    private int generateExpiryDate() {
        return 202405;                  // date is YYYY=2024, MM=05
    }


    private int generateCVV() {
        return 123;                     // is in real-life less predictable
    }


    // ------- PUBLIC STATIC NESTED CLASS ---------

    public static final class CreditCard {
        private final String firstName;
        private final String lastName;
        private final String number;
        private final int expiryDate;
        private final int CVV;

        private int balance;
        private int limit = 100; // default limit

        // the constructor is final but is accessible from outer class
        private CreditCard(
                final String firstName,
                final String lastName,
                final String number,
                final int expiryDate,
                final int CVV
        ) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.number = number;
            this.expiryDate = expiryDate;
            this.CVV = CVV;
        }

        // ------- PUBLIC METHODS ---------

        public String getFirstName() {
            return this.firstName;
        }

        public String getLastName() {
            return this.lastName;
        }

        public String getNumber() {
            return this.number;
        }

        public int getExpiryDate() {
            return this.expiryDate;
        }

        // returns true if financial transaction is successful
        // otherwise false
        public boolean charge(final int amount) {
            final int newBalance = this.balance - amount;
            if (newBalance < -this.limit) {
                return false;
            }
            this.balance = newBalance;
            return true;
        }

        // ------- PRIVATE METHODS ---------

        private int getCVV() {
            return this.CVV;
        }

        private int getBalance() {
            return this.balance;
        }

        private void setBalance(final int balance) {
            this.balance = balance;
        }

        private int getLimit() {
            return limit;
        }

        private void setLimit(final int limit) {
            this.limit = limit;
        }
    }
}

嵌套类:类内的类

类型:

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

差异:

非静态嵌套类[内部类]

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

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和/或嵌套类新手的学习者

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

1.内部类例子:

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

内部类是嵌套类的子集:

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

内层专业:

内部类的实例可以访问外部类的所有成员,即使是标记为“private”的成员

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中内部类和静态嵌套类的主要区别是什么?答:只需仔细阅读上面提到的每门课的细节。

嗯……内部类是嵌套类……您是指匿名类和内部类吗?

编辑:如果您实际上是指内部v.s.匿名:内部类只是在类中定义的类,例如:

public class A {
    public class B {
    }
}

…而匿名类是匿名定义的类的扩展,因此没有定义实际的“类”,如:

public class A {
}

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

进一步编辑:

维基百科声称Java存在差异,但我已经用Java工作了八年,这是我第一次听到这样的区别——更不用说那里没有引用来支持这一说法……总之,内部类是在类中定义的类(静态或非静态),嵌套只是另一个意思相同的术语。

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

我认为这里没有什么要补充的,大多数答案完美地解释了静态嵌套类和内部类之间的区别。但是,当使用嵌套类与内部类时,请考虑以下问题。正如在两个答案中提到的,如果没有封闭类的实例,内部类就无法实例化,这意味着它们持有指向封闭类实例的指针,这可能导致内存溢出或堆栈溢出异常,因为即使不再使用封闭类,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()实例化,则上述程序可以打印“我被摧毁了!”;