ch10:内部类(inner class)
定义
将一个类的定义放在另一个类的定义内部,这就是内部类。
创建内部类
创建内部类的方式就如定义一样,放置在外围类里面就行:
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. withing 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
*/
}
在ship()方法里面使用内部类时,与普通类没有什么区别。
更常用的是外围类定义一个方法只想内部类,代码如下:
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 class:
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("Borneo");
}
/**
* output:
* Tasmania
*/
}
如果想从外部类的非静态方法之外的任意位置创建某个内部类对象,那么必须指明具体的外部类名:OuterClassName.InnerClassName
链接到外部类
以上讲的内部类还只是一种代码隐藏和组织代码的模式。
当然还有其他用途,当生成一个内部类对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以可以随意访问外围对象的所有成员,不需要任何特殊条件。此外,还拥有外围类的所有元素的访问权。
示例代码如下:
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;
@Override
public boolean end() {
return i == items.length;
}
@Override
public Object current() {
return items[i];
}
@Override
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。这样产生的引用自动地具有正确的类型,这一点在编译器知晓识别了,因此没有任何运行时开销。
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"
//如果仅用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();
}
/**
* output:
* DotThis.f()
*/
}
直接创建内部类对象需要提供外部类对象的引用,使用.new语法创建。如下:
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
注意,必须在有外部类的情况下才能创建内部类对象,因为内部类会暗暗地连接到创建它的外部类对象上,对于嵌套类(静态内部类),就可以不需要外部类对象的引用(也就是可以直接创建内部类对象)。
在方法和作用域内的内部类
内部类的位置是灵活的,在外部类里面,外部类的方法里面,外部类的代码块中都可以,只是位置决定了该内部类的作用范围,使用的时候需要注意。
public class Parcel5 {
public Destination destination(String s) {
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 d = p.destination("Tasmania");
}
}
PDestination是destination()方法的一部分,作用点仅在方法内部。返回的是向上转型的Destination。
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语句的作用域内,并不是说该类的创建是有条件的,它其实与别的类一起编译过了。除了作用域外,与普通类一样。
匿名内部类
创建一个继承自接口或者类的一个匿名类的对象,通过new表达式返回的引用被自动向上转型为对父类(接口或者类)的引用。
class Contents {
private int i = 11;
public int value() {
return i;
}
}
public class Parcel7 {
public Contents contents() { // insert a class definition
return new Contents() {
private int i = 12;
public int value() {
return i;
}
}; // semicolon required in this case
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
以上展示的通过匿名内部类向上转型父类contents,该contents是一个实体类,一般而言,我们创建的匿名内部类以接口居多,上面展示的是对于类也是可以使用该方式。
对于需要有参数构造器的,以下展示的是带构造参数的匿名内部类。
class Wrapping {
private int x;
public Wrapping(int x) {
this.x = x;
}
int value() {
return x;
};
}
public class Parcel8 {
//构造器参数x不必是final的
public Wrapping wrapping(int x) {
//base constructor call:
return new Wrapping(x) {
@Override
public int value() {
return super.value() * 47;
}
};// semicolon required
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
}
在匿名内部类末尾的分号,并不是用来标记此内部类结束的,只是该表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。
在匿名类中定义字段时,还能够对其执行初始化操作:
public class Parcel9 {
//匿名类对象中初始化的参数dest必须是final的
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}
匿名内部类构造器方法传入的参数因为是初始化时就定了,所以不必要求是final的,但是对于匿名内部类初始化时的参数,必须是final的。
嵌套类
如果不需要内部类对象与外围类对象有联系,可以将内部类声明为static。这个通常称为嵌套类。
普通内部类对象隐式地保存了一个引用,指向创建它的外围类。
使用嵌套类需要注意:
1. 创建嵌套类,不需要外围对象
2. 不能从嵌套类的对象中访问非静态的外围类对象。
示例代码如下:
public class Parcel11 {
private String value1 = "value1";
private static String value2 = "value2";
public static class Inner {
public String getValue() {
return "value";
//无法获取到value1的值,因为对于该嵌套类来说是单独存在的,不能访问非静态变量
//! return value1;
}
//可以获取到静态变量的值
public String getValue2() {
return value2;
}
}
public static void main(String[] args) {
//直接使用new OuterClassName.InnerClassName()创建嵌套类对象,
// 前提是嵌套类的在这里是可见的
System.out.println(new Parcel11.Inner().getValue());
}
}
接口内部类
正常情况下,不能在接口中放置任何代码,但是嵌套类可作为接口的一部分存在,因为实在接口中,所以该类自动的为public和static属性。因为类是static的,所以只是将嵌套类置于接口的命名空间内,并不违反接口的规则。
示例:
public interface ClassInterface {
void howdy();
class Test implements ClassInterface {
@Override
public void howdy() {
System.out.println("howdy()");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
多层嵌套类
一个类中可以嵌套很多层,这个没有限制。
示例:
class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
public class MutiNestingAccess {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnab = mnaa.new B();
mnab.h();
}
}
为什么需要内部类
每个内部类都能独立地继承一个(接口)的实现,所以无论外围类是否已经继承了某个(接口)的实现,对于内部类是没有影响的。
对于实现多接口,外部类一个即可以实现,内部类也同样可以,但是对于继承(普通类和抽象类),只有通过内部类解决,即内部类比较完美的完善了多继承的问题,相对于c++来说(实现多接口只是一部分上解决了这个问题)。
class D {}
abstract class E {}
class Z extends D {
E makeE() {
return new E() {};
}
}
public class MutiImplementation {
static void taskD(D d) {}
static void taskE(E e) {}
public static void main(String[] args) {
Z z = new Z();
taskD(z);
taskE(z.makeE());
}
}
如果不需要解决“多重继承”的问题,自然可以使用其他方式解决,可以不使用内部类,但是如果使用内部类,还可以获得其他的一些特性:
1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
2. 在单个外围类,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
3. 创建内部类对象的时刻并不依赖于外围类对象的创建。
4. 内部类并没有令人迷惑的“is-a”关系;它就是一个独立的实体。
- 闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
通过这个定义可以看出,内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向外围类对象的引用,在此作用域内,内部类有权限操作所有成员,包括private成员。
内部类的继承
内部类有点特殊,因为其必须持有外围类的引用才能够进行初始化,所以以下示例展示了内部类的继承问题:
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
//不能使用默认构造器
//InheritInner() {} 该构造器不会编译
InheritInner(WithInner withInner) {
withInner.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
如上示例代码,继承自内部类,必须在构造器方法中调用外部类中调用
enclosingClassReference.super()
提供必要的引用,方法才能编译通过。
内部类可以被覆盖吗
创建一个内部类,然后继承其外围类并重新定义此内部类时,以下为示例代码:
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
public Egg() {
System.out.println("new Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
//此处被不能“覆盖”基类中的内部类
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
/**
* output:
* new Egg()
* Egg.Yolk()
*/
}
内部类可以明确的继承,示例:
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();
public 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()");
}
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();
}
/**
* output:
* Egg2.Yolk()
* New Egg2()
* Egg2.Yolk()
* BigEgg2.Yolk()
* BigEgg2.Yolk.f()
*/
}
局部内部类
在代码块里面也可以使用类,这称之为局部内部类,其作用范围是有限的。
局部内部类不能有访问限制符,因为不是外围类的一部分,但是可以访问当前代码块内的常量,以及此外围类的所有成员。
内部类标识符
由于每个类会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个“meta-class”,叫做Class对象),内部类也必须生成一个.class文件包含它们的Class对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“$”,再加上内部类的名字。
如果内部类时匿名的,编译器会简单的产生一个数字作为其标识符。如果内部类时嵌套在别的内部类中,只需直接将它们的名字加载其外围类标识符与“$”的后面。
//简单的获取类名的示例代码
class Test{
class Inner {
public class Inner1 {}
}
}
public class ClassNameTest {
public static void main(String[] args) {
Test t= new Test();
Test.Inner inner1 = t.new Inner();
Test.Inner.Inner1 inner11 = inner1.new Inner1();
System.out.println(inner1.getClass().getName());
System.out.println(inner11.getClass().getName());
}
/**
* output:
* Test$Inner
* Test$Inner$Inner1
*/
}