内部类
约 2700 字大约 9 分钟
2025-03-04
定义
一个类定义在另一类中,就叫做内部类
创建内部类
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在 方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName。(译者注:在外部类的静态方法中也可以直接指明类型 InnerClassName,在其他类中需要指明 OuterClassName.InnerClassName。)
main()
// 其中 contents返回的是Parcel2的内部类Contents,to也是 Parcel2.Contents c = q.contents(); Parcel2.Destination d = q.to("Borneo");
链接外部类
- 内部类可以使用外部类中的所有成员变量和方法
- 怎样做到内部类可以访问到外部类的所有成员变量
- 当**某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外部类对象的引用。然后,在你访问此外部类的成员时,就是用那个引用来选择外部类的成员。**幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外部类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 static 类时)。构建内部类对象时,需要一个指向其外部类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。
使用.this和.new
使用.this,可以在内部类实现获得外部类的引用。例如:外部类的名字.this
使用.new,创建内部类的时候,可以使用外部类名字.new 内部类名字
// innerclasses/DotNew.java // Creating an inner class directly using .new syntax public class DotNew { public class Inner {} public static void main(String[] args) { DotNew dn = new DotNew(); DotNew.Inner dni = dn.new Inner(); } }
如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
class WithInner { static class Inner {} } class InheritInner extends WithInner.Inner { //- InheritInner() {} // Won't compile InheritInner() {} public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi);
内部类与向上转型
通过实现接口,创建内部类时,不需要外部类名字.内部类名字
// innerclasses/TestParcel.java class Parcel4 { private class PContents implements Contents { private int i = 11; @Override public int value() { return i; } } protected final class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } @Override public String readLabel() { return label; } } public Destination destination(String s) { return new PDestination(s); } public Contents contents() { return new PContents(); } } public class TestParcel { public static void main(String[] args) { Parcel4 p = new Parcel4(); Contents c = p.contents(); Destination d = p.destination("Tasmania"); // Illegal -- can't access private class: //- Parcel4.PContents pc = p.new PContents(); } }
内部类方法和作用域
定义在方法中的类
// innerclasses/Parcel5.java // Nesting a class within a method public class Parcel5 { public Destination destination(String s) { final class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo; } @Override public String readLabel() { return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel5 p = new Parcel5(); //这里的Destination是PDestination的基类 Destination d = p.destination("Tasmania"); } }
定义在任意作用域的类
// innerclasses/Parcel6.java // Nesting a class within a scope public class Parcel6 { private void internalTracking(boolean b) { if(b) { class TrackingSlip { private String id; TrackingSlip(String s) { id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip("slip"); String s = ts.getSlip(); } // Can't use it here! Out of scope: //- TrackingSlip ts = new TrackingSlip("x"); } public void track() { internalTracking(true); } public static void main(String[] args) { Parcel6 p = new Parcel6(); p.track(); } }
TrackingSlip 类被嵌入在 if 语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。 然而,在定义 Trackingslip 的作用域之外,它是不可用的,除此之外,它与普通的类一样。
匿名内部类
较复杂的形式
// innerclasses/Parcel7b.java // Expanded version of Parcel7.java public class Parcel7b { class MyContents implements Contents { private int i = 11; @Override public int value() { return i; } } public Contents contents() { return new MyContents(); } public static void main(String[] args) { Parcel7b p = new Parcel7b(); Contents c = p.contents(); } }
简化形式,通过匿名内部类实现
// innerclasses/Parcel7.java // Returning an instance of an anonymous inner class public class Parcel7 { public Contents contents() { return new Contents() { // Insert class definition private int i = 11; @Override public int value() { return i; } }; // Semicolon required } public static void main(String[] args) { Parcel7 p = new Parcel7(); Contents c = p.contents(); } }
对于匿名内部类中需要实现有参的类
return new Contents(x) {
如果这个参数用在匿名内部类时,需要用final修饰参数,但是在jiava8之后,会自动为你加上,匿名内部类中不能重载示例初始化方法
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。 而且如果是实现接口,也只能实现一个接口。
public Destination destination(final String dest) { return new Destination() { private String label = dest; @Override public String readLabel() { return label; } }; }
嵌套类
将内部类声明为static,就叫做嵌套类
好处:
- 要创建嵌套类的对象,并不需要其外部类的对象,
- 不能从嵌套类的对象中访问非静态的外部类对象,也就是不需要this去引用外部类了
接口的内部类
- 内部类都是自动为static和public
从多层嵌套类中访问外部类的成员
- 可以看到在 MNA. A.B 中,调用方法 和 不需要任何条件(即使它们被定义为 private)。 这个例子同时展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法。 ".new"语法能产生正确的作用域,所以不必在调用构造器时限定类名。
g()``f()
// innerclasses/MultiNestingAccess.java
// Nested classes can access all members of all
// levels of the classes they are nested within
class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
public class MultiNestingAccess {
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();
}
}
为什么需要内部类
在对于实现抽象类或者抽象类的时候,为了实现多重继承,就可以使用内部类
lass D {}
abstract class E {}
class Z extends D {
E makeE() {
return new E() {};
}
}
public class MultiImplementation {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}
- 内部类的其它特性
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立。
- 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 稍后就会展示一个这样的例子。
- 创建内部类对象的时刻并不依赖于外部类对象的创建
- 内部类并没有令人迷惑的"is-a”关系,它就是一个独立的实体。
闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。 通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外部类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外部类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 private 成员。
interface Incrementable { void increment(); } // Very simple to just implement the interface: class Callee1 implements Incrementable { private int i = 0; @Override public void increment() { i++; System.out.println(i); } } class MyIncrement { public void increment() { System.out.println("Other operation"); } static void f(MyIncrement mi) { mi.increment(); } } // If your class must implement increment() in // some other way, you must use an inner class: class Callee2 extends MyIncrement { private int i = 0; @Override public void increment() { super.increment(); i++; System.out.println(i); } private class Closure implements Incrementable { @Override public void increment() { // Specify outer-class method, otherwise // you'll get an infinite recursion: Callee2.this.increment(); } } Incrementable getCallbackReference() { return new Closure(); } }
这个例子进一步展示了外部类实现一个接口与内部类实现此接口之间的区别。 就代码而言,Callee1 是更简单的解决方式。 Callee2 继承自 MyIncrement,后者已经有了一个不同的 方法,并且与 Incrementable 接口期望的 方法完全不相关。 所以如果 Callee2 继承了 MyIncrement,就不能为了 Incrementable 的用途而覆盖 方法,于是只能使用内部类独立地实现 Incrementable,还要注意,当创建了一个内部类时,并没有在外部类的接口中添加东西, 也没有修改外部类的接口。
回调的价值在于它的灵活性-可以在运行时动态地决定需要调用什么方法
内部类与控制框架(需要深入了解)
下面这个链接对于了解内部类的价值shi'hen
docs/book/11-Inner-Classes.md ·码到成攻/Java编程思想_第5版_中文 - Gitee.com
继承内部类
因为内部类的构造器必须连接到指向其外部类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。 问题在于,那个指向外部类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。 要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:
// innerclasses/InheritInner.java // Inheriting an inner class class WithInner { class Inner {} } public class InheritInner extends WithInner.Inner { //- InheritInner() {} // Won't compile InheritInner(WithInner wi) { wi.super(); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
通常使用语法,这是一个调用外部类的构造器的特殊语法
nclosingClassReference.super();
内部类可以被覆盖么?
答案是:不会覆盖,两个内部类是两个独立的两个实体
// innerclasses/BigEgg2.java
// Proper inheritance of an inner class
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
Egg2() { System.out.println("New Egg2()"); }
public void insertYolk(Yolk yy) { y = yy; }
public void g() { y.f(); }
}
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
public Yolk() {
System.out.println("BigEgg2.Yolk()");
}
@Override
public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}
public BigEgg2() { insertYolk(new Yolk()); }
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
}
输出结果
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
- 输出结果,可以直到子父类加载的顺序,然后重写内部类的方法
局部内部类
- 前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。 局部内部类不能有访问说明符,因为它不是外部类的一部分; 但是它可以访问当前代码块内的常量,以及此外部类的所有成员。 下面的例子对局部内部类与匿名内部类的创建进行了比较。
内部类标识符
这些类文件的命名有严格的规则:外部类的名字,加上“$",再加上内部类的名字。
例如,LocalInnerClass.java 生成的 .class 文件包括:
Counter.class LocalInnerClass$1.class LocalInnerClass$LocalCounter.class LocalInnerClass.class
总结
- 接口和内部类结合起来就解决了c++中的用多重继承所能解决的问题