一个定义在另外一个类中的类,叫做内部类java
内部类容许你把一些逻辑相关的类组织在一块儿,并控制位于内部的类的可见性,这么看来,内部类就像是一种代码隐藏机制:将类置于其余类的内部,从而隐藏名字与组织代码的模式。程序员
建立内部类的方式就如同你想的同样,把类的定义置于外部类里面闭包
public class Outer { // 其余一些成员声明 ... public class Inner { // 其余一些成员声明 ... } }
内部类与其外部类之间存在一种联系,经过这个联系能够访问外部类的全部成员,而不须要任何特殊条件,能够这么说,内部类拥有其外部类的全部元素的访问权。ide
这是如何作到的呢?当某个外部类的对象建立一个内部类对象时,该内部类对象会秘密地捕获一个指向外部类对象的引用。在你访问外部类成员时,能够经过这个引用选择外部类的成员。全部这些实现的细节由编译器帮忙处理,大多数时候都无需程序员关心。this
若是你须要生成对外部类对象的引用,可使用外部类的名字后面紧跟圆点和 thiscode
public class Outer { public class Inner { public Outer getOuter() { return Outer.this; } } public static void main(String[] args) { Outer outer = new Outer(); Outer outer2 = outer.getOuter(); } }
若是你想建立某个内部类对象,必须在 new 表达式中提供其外部类的引用,这时须要使用 .new 语法对象
public class Outer { public class Inner {} public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); } }
若是从外部类的非静态方法以外的位置建立某个内部类对象,要具体指明这个内部类对象的类型:OuterClassName.InnerClassName
(在外部类的静态方法中也能够直接指明类型 InnerClassName
)继承
当将内部类向上转型为其基类(接口),能够很方便地隐藏实现细节,由于外界所获得的只是指向基类或接口的引用。接口
// 这是一个接口 public interface Content {...} class Outer { // 注意这里将访问修饰符设为 private private class PContent implements Content { ... } public Content getContent() { return new PContent(); } }
在 Outer 类中,内部类 PContent 是私有的,除了 Outer 外没有人能够访问。这意味着,客户端程序员若是想了解这些内部类成员,那是要受限制的。客户端程序员没法访问任何新增的、不属于公共接口的方法,也不能向下转型,由于不能访问其名字。作用域
不管一个内部类被嵌套多少层,它都能透明地访问全部它所嵌入的外部类的全部成员
class MNA { private void f() {} class A { private void g(){} public class B { void h() { g(); f(); } } } } public class TestClass { public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna.new A(); MNA.A.B mnaab = mnaa.new B(); mnaab.h(); } }
成员内部类是最普通的内部类,它和外部类的成员变量同样,定义在类的内部,能够声明访问修饰符。成员内部类有以下特性:
OuterClassName.this.innerClassMember
在方法的做用域内(而不是在其余类的做用域内)建立一个完整的类,这个类被称做局部内部类。因为是定义在方法内,因此不能声明访问修饰符,做用范围仅在声明类的代码块中,但这并不意味着一旦方法做用域执行完毕,局部内部类就不可用了。局部内部类有以下特性:
public class Outer { public Outer method() { class Inner { ... } } }
只用一次的没有名字的类,具体看下面的例子:
public class Outer { public Content method() { return new Content() { private int i = 11; @Override public int getValue() { return i } } } }
这种奇怪的语法指的是:建立一个继承自 Content 的匿名类的对象。经过 new 表达式返回的这个引用被自动向上转型为 Content 的引用。上述匿名内部类是下述形式的一种简化形式,也就是说,匿名内部类能够看做对成员内部类或局部内部类的一种简写:
public class Outer { class MyContent implements Content { private int i = 11; @Override public int getValue() { return i } } public Content method() { return new MyContent(); } }
匿名内部类没有构造器(由于它根本没有名字),若是咱们但愿为匿名内部类作一些初始化操做,那该怎么办呢?若是该匿名内部类的基类构造器有参数,则能够直接使用,但编译器会要求其参数引用是 final(即便你不加,Java8 也会为咱们自动加上 final)。或者经过代码块,模仿构造器的行为来实现初始化。
public class Outer { public Content method() { return new Content(final int a, final int b) { private int i = a; private int j; { j = b; } } } }
若是不须要内部类对象与其外部类对象之间有联系,能够将内部类声明为 static。既然静态内部类不须要与外部类有联系,那么也就没有指向外部类的 this 引用,更像一个独立的类,这意味着:
public class Outer { public static class Inner { private String label; public String method1() { ... } static int x = 10; public static void method2() { ... } } }
静态内部类能够做为接口的一部分,由于你放到接口中的任何类都自动是 public 和 static 的,甚至能够在内部类中实现其外部类接口。
public interface ClassInterface { void method1(); class InnerClass implements ClassInterface { @Override public void method1() { ... } } }
内部类的第一个用途就是能够操做建立它的外部类的对象,典型的例子就是集合中常使用的迭代器 iterator。这只是其中之一,使用内部类最吸引人的缘由莫过于:
每一个内部类都能独立地继承自某个类或实现某个接口,不管外部类是否继承了某个类或实现某个接口
基于这个特性,使得多重继承成为可能,咱们可让多个内部类以不一样的方式实现不一样的接口,或继承不一样的类。下面这个例子使用了匿名内部类,可能大家已经习觉得常,但别忘了匿名内部类其实就是内部类的一种简写形式。
class D {} abstract class E {} class Z extend D { E getE() { return new E() {}; } } public class TestClass { static void setD(D d){} static void setE(E e){} public static void main(String[] args) { Z z = new Z(); setD(z); setE(z.getE); } }
另外,内部类也是实现回调的绝佳选择。经过内部类能够实现一个闭包,所谓闭包就是一个可调用的对象,它记录了一些信息。在须要提供闭包来实现回调时,咱们能够采用匿名内部类的方式。
由于内部类的构造器必须链接到其外部类对象的引用,因此在继承内部类时,那个指向外部类对象的引用必须被初始化,而在派生类中又再也不存在这个引用了。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关系。
class WithInner { class Inner {} } public class InheritInner extends WithInner.Inner { InheritInner(WithInner wi) { wi.super(); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner li = new InheritInner(wi); } }
构造器要传递一个指向外部类对象的引用,此外,还必须显式地调用基类构造器。
内部类能够被覆盖吗?答案是不能。若是建立一个内部类,而后继承其外部类并从新定义此内部类,并不会覆盖该内部类。两个内部类是彻底独立的,各自在本身的命名空间。
每一个类编译后都会产生一个 .class 文件,内部类也会生成一个 .class 文件,但其命名有严格的规则:外部类的名字 + $ + 内部类的名字,例如 OuterClass.java 生成的 .class 文件包括:
OuterClass.class OuterClass$InnerClass.class
若是内部类是匿名的,编译器会简单地生成一个数字做为其标识符。若是内部类嵌套在别的内部类之中,只需直接将它们的名字在外部类标识符与 $ 的后面。