内部类
将一个类定义置入另一个类中,这就叫做内部类。
需要注意的是内部类和合成(composition)有着本质区别。
创建内部类:
//: innerclasses/Parcel1.java
// Creating inner classes.
public class Parcel1 {
class Contents { //内部类
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tasmania");
}
} /* Output: Tasmania *///:~
注意:外部类一个特殊的方法,它会返回指向内部类的句柄。如下:
//: innerclasses/Parcel2.java
// Returning a reference to an inner class.
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) { return new Destination(s); }
public Contents contents() { return new Contents(); }
public void ship(String dest) {
Contents c = contents();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tasmania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("Borneo");
}
} /* Output:
Tasmania
*///:~
要在外部类的非static方法中生成内部类的对象,必须指明OuterClassName.InnerClassName,如Parcel2.Contents c = q.contents();
内部类引用外部类成员
内部类的对象能连接到创建它的封装对象(enclosing object),所以内部类的对象不需要任何特殊的条件就能访问其封装对象的类成员(即外部类的)。总结来说,内部类能访问其封装类的所有元素,见下例:
//: innerclasses/Sequence.java
// Holds a sequence of Objects.
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] items;
private int next = 0;
public Sequence(int size) {
items = new Object[size];
}
public void add(Object x) {
if(next < items.length) items[next++] = x;
}
private class SequenceSelector implements Selector { //内部类
private int I = 0;
public boolean end() {
return I == items.length;
}
public Object current() { return items[i]; }
public void next() { if(I < items.length) i++; }
}
public Selector selector() { return new SequenceSelector(); }
public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for(int I = 0; I < 10; i++)
sequence.add(Integer.toString(i));
Selector selector = sequence.selector(); //上溯造型
while(!selector.end()) {
System.out.print(selector.current() + " ");
selector.next();
}
}
}
/* Output: 0 1 2 3 4 5 6 7 8 9 *///:~
为什么内部类能够访问外部类的变量和方法?因为内部类暗中捕获到指向其外部类对象的句柄。之后当引用外部类的成员时,会使用该句柄选择成员。
.this和.new
创建外部类对象的句柄,则在外部类名后接.this,创建出的句柄自动是正确的类型,并在编译时被获知并确认,因而没有运行开销。见下例:
//: innerclasses/DotThis.java
// Qualifying access to the outer-class object.
public class DotThis {
void f() { System.out.println("DotThis.f()");
}
public class Inner {
public DotThis outer() {
return DotThis.this;
// A plain "this" would be Inner’s "this"
}
}
public Inner inner() { return new Inner(); }
public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f(); //DotThis.this.f() 相当于dt.f()
}
} /* Output: DotThis.f() *///:~
用.new创建外部类的内部类对象
//: innerclasses/DotNew.java
// Creating an inner class directly using the .new syntax.
public class DotNew {
public class Inner {} //内部类Inner
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
} ///:~
没新建外部类的对象就不能新建内部类的对象。但是如果是static内部类,则不需要新建外部类的对象。
内部类和上溯造型
当上溯造型到一个基础类(特别是到接口)时,内部类完全进入不可见或不可用状态。所以可以非常方便地隐藏实施细节。所以如下例:
//: innerclasses/TestParcel.java
class Parcel4 {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
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();
}
} ///:~
利用private内部类,可完全禁止任何类型编码依赖(type-coding dependencies),并可将具体的实施细节完全隐藏起来。从客户程序员的角度来看,接口的扩展(extension of an interface)没有意义的,因为不能访问不属于公共接口的任何额外方法。这样一来,Java编译器也有机会生成效率更高的代码
方法和作用域中的内部类
可以在方法或作用域中创建内部类,为什么要这么做有以下两个原因:
-
如前面例子所示,可以实现某种接口,从而可以创建并返回句柄。
-
在解决复杂问题时,想创建一个类来解决问题,但不想将其公开。
在下面例子里,将修改前面的代码,以便使用:
(1) 方法内定义内部类
(2) 在方法的作用域定义内部类
(3) 实现接口的匿名类(anonymous class)
(4) 用于扩展拥有非默认构造方法的类的匿名类
(5) 用于执行字段初始化的匿名类
(6) 通过实例初始化(instance initialization)进行构建(匿名内部类不可拥有构建器)的匿名类1) 第一个例子显示在方法作用域创建整个类。这叫做本地内部类(local inner class)
//: innerclasses/Parcel5.java
// Nesting a class within a method.
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination { //方法中的内部类
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s); //返回内部类
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania"); //上溯造型到实现的接口
}
} ///:~
注意:本地内部类不能访问外部类的成员,因为其不是外部类的一部分,但是可以访问当前代码块的final变量和封装类的所有成员。
同时注意区分本地内部类和匿名内部类的不同:
本地内部类可有有构造方法,匿名内部类没有构造方法,只能实例初始化,
所以需要使用构造方法或要创建该类的多个对象时,要用本地内部类取代匿名内部类
2) 第二个例子展示在作用域中嵌套内部类
//: 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();
}
} ///:~
3)第三例,匿名内部类
//: innerclasses/Parcel7.java
// Returning an instance of an anonymous inner class.
public class Parcel7 {
public Contents contents() {
return new Contents() { // 内部类 加入类定义
private int i = 11;
public int value() { return i; }
}; // 此处需要分号
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
} ///:~
这种语法要表达的意思是:“创建从Contents衍生出来的匿名类的一个对象”。由new 表达式返回的句柄会自动上溯造型成一个Contents句柄。匿名内部类的语法其实
要表达的是:
//: innerclasses/Parcel7b.java
// Expanded version of Parcel7.java
public class Parcel7b {
class MyContents implements Contents {
private int i = 11;
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();
}
} ///:~
在匿名内部类中,Contents是由默认构造方法创建的
4)下例展示基类需要带参数的构造方法时怎么做
//: innerclasses/Parcel8.java
// Calling the base-class constructor.
public class Parcel8 { //Parcel8是内部类
public Wrapping wrapping(int x) {
// Base constructor call:
return new Wrapping(x) { // Pass constructor argument.
public int value() {
return super.value() * 47;
}
}; // 此处需要分号
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
} ///:~
在此,简单的将参数传给基类的构造方法,即便Wrapping只是实现了的普通类,它也可以用作其派生类的通用“接口”
匿名内部类最后的分号并不意味着类体的结束。它意味着包含匿名类的表达式的结束。因此其分号和其他地方使用分号的意义相同。
5)在匿名类定义字段的时候初始化
//: innerclasses/Parcel9.java
// An anonymous inner class that performs
// initialization. A briefer version of Parcel5.java.
public class Parcel9 {
//要想在匿名内部类使用,参数必须是final
// anonymous inner class:
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
} ///:~
如果定义了一个匿名内部类,想在匿名类中用定义在匿名内部类外的对象,则参数句柄必须是final,否则编译器会报错。
6)在匿名类中没有构造方法,但可以使用实例初始化,从而可以在匿名内部类中创建构造方法
//: innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import static net.mindview.util.Print.*;
abstract class Base {
public Base(int i) {
print("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{ print("Inside instance initializer"); }
public void f() {
print("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
/* Output:
Base constructor, i = 47
Inside instance initializer In anonymous f() *///:~
实际上,实例初始化时匿名内部类的构造方法。当然,是受限制的,你不能重载实例初始化,所以只能有其中一个构造方法
匿名类不能同时继承类和实现接口,也不能实现多个接口,要实现只能实现一个接口。
嵌套类(nested class)
如果不需要在内部类和外部类之间建立联系,则可以将内部类设为static。这通常称为嵌套类。嵌套类的含义是:
- 不需要外部类对象就能创建嵌套类的对象
- 嵌套类的对象不能访问外部类的非static对象
一般内部类不能有静态数据,静态字段或嵌套类。但是嵌套类可以有静态数据,静态字段或嵌套类。见下例:
//: innerclasses/Parcel11.java
// Nested classes (static inner classes).
public class Parcel11 {
private static class ParcelContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected static class ParcelDestination implements Destination {
private String label;
private ParcelDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
// Nested classes can contain other static elements:
public static void f() {}
static int x = 10;
static class AnotherLevel {
public static void f() {}
static int x = 10;
}
}
public static Destination destination(String s) { return new ParcelDestination(s); }
public static Contents contents() { return new ParcelContents(); }
public static void main(String[] args) {
Contents c = contents();
Destination d = destination("Tasmania");
}
} ///:~
在主方法中不需要Parcel11的类。并且在嵌套类中不需要this。
内部类标识符
内部类也有.class文件,但其名字有严格的要求,必须是:
enclosingClassName$innerClassName
如:
TestBed$Tester.class
如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部类标识符使用。若内部类嵌套于其他内部类中,则它们的名字简单地追加在一个$以及外部类标识符的后面。
接口中的类
一般不可以在接口中放任何代码,但是接口中可以有嵌套类。接口中的类都自动为public和static。嵌套类只是放在接口的命名空间中。甚至可以在内部类中实现接口,如下:
//: innerclasses/ClassInInterface.java
// {main: ClassInInterface$Test}
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
} /* Output: Howdy! *///:~
当需要创建一些在接口不同实现中可以通用的代码时,在接口内使用嵌套类会很方便。
此前提到过在每个类中创建主方法做测试用。这样做的一个坏处是会产生大量额外编译过的代码,这个问题可以用嵌套类解决,见下例:
//: innerclasses/TestBed.java
// Putting test code in a nested class.
// {main: TestBed$Tester}
public class TestBed {
public void f() { System.out.println("f()"); }
public static class Tester {
public static void main(String[] args) {
TestBed t = new TestBed();
t.f();
}
}
}
/* Output: f() *///:~
这会生成一个名字为TestBed T e s t e r ( 注 意 要 在 U n i x / L i n u x 系 统 中 避 免 Tester(注意要在Unix/Linux系统中避免 Tester(注意要在Unix/Linux系统中避免)的类。可以用这个类进行测试,要发布代码的时候把这个类删掉就可以了。
从多重嵌套类向外扩展
不论内部类被嵌套的有多深,它都可以访问被嵌套的所有类的所有成员,见下例:
//: 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(); //g()和f()是其外部类的方法
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();
}
} ///:~
为什么要使用内部类?
使用内部类的核心原因是:
内部类可以独立地继承,不受外部类是否已经继承的限制
内部类能有效的继承多个非接口(non-interface)
考虑以下情况,如果要实现两个接口,有两个方法可以选择:单类实现或者用内部类(single class or an inner class)
//: innerclasses/MultiInterfaces.java
// Two ways that a class can implement multiple interfaces.
package innerclasses;
interface A {}
interface B {}
class X implements A, B {} //单类继承
class Y implements A { //匿名内部类
B makeB() {
// Anonymous inner class:
return new B() {};
}
}
public class MultiInterfaces {
static void takesA(A a) {}
static void takesB(B b) {}
public static void main(String[] args) {
X x = new X();
Y y = new Y();
takesA(x);
takesA(y);
takesB(x);
takesB(y.makeB());
}
} ///:~
在没有特殊情况的限制下,两中方法都是可行,没有太多区别的
但如果不是两个接口,而是两个抽象类或具体类,那该如果实现
//: innerclasses/MultiImplementation.java
// With concrete or abstract classes, inner
// classes are the only way to produce the effect
// of "multiple implementation inheritance."
package innerclasses;
class 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());
}
} ///:~
使用内部类有以下几个特点:
- 内部类可以有多个实例,每个实例有自己的状态信息并且独立于外部类对象的信息
- 在一个外部类中可以有多个内部类,每个内部类以不同的方式实现相同的接口或集成相同的类
- 内部类对象的创建与外部类对象的创建并不是紧密捆绑在一起(static内部类)
- 内部类没有容易混淆的“is-a”关系,它是一个独立的实体。
继承内部类
因为内部类的构造方法必须与封装类对象的句柄联系在一起,继承内部类的情况会有点复杂。问题在于依附封装类对象的句柄必须初始化,然而在衍生类中已经没有默认对象可以依附,所以必须使用特殊的语法实现。
//: 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);
}
} ///:~
注意:在构造方法中参数要传入外部类的句柄并且要调用外部类的构造方法:eclosingClassReference.super();
这样编译器才会进行编译
内部类能否被重写?
可以像重写方法一样重写内部类吗?听起来很厉害,其实重写内部类并没有多大作用:
//: innerclasses/BigEgg.java
// 内部类不能像方法一样被重写
import static net.mindview.util.Print.*;
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() {
print("Egg.Yolk()");
}
}
public Egg() {
print("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() {
print("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
}
/* Output:
New Egg()
Egg.Yolk() *///:~
从结果可以看出重写内部类没有效果
继承的内部类和被继承的内部类,这两个内部类完全是两个不同的类,每个都在自己的命名空间里
但是可以显式地继承内部类重写内部类的方法。