JAVA学习笔记【4】
内部类
- 如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在 main() 方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName。(在外部类的静态方法中也可以直接指明类型 InnerClassName,在其他类中需要指明 OuterClassName.InnerClassName。)
- 当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。
- 使用 .this 和 .new
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。下面的示例展示了如何使用 .this:
// innerclasses/DotThis.java
// Accessing 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.f()
复制ErrorOK!
有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在 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();
}
}
要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 DotNew,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明)dn.new DotNew.Inner。
下面你可以看到将 .new 应用于 Parcel 的示例:
// innerclasses/Parcel3.java
// Using .new to create instances of inner classes
public class Parcel3 {
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 static void main(String[] args) {
Parcel3 p = new Parcel3();
// Must use instance of outer class
// to create an instance of the inner class:
Parcel3.Contents c = p.new Contents();
Parcel3.Destination d =
p.new Destination("Tasmania");
}
}
在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
- 如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 final 的(也就是说,它在初始化后不会改变,所以可以被当作 final),就像你在 destination() 的参数中看到的那样。这里省略掉 final 也没问题,但是通常最好加上 final 作为一种暗示。
// innerclasses/Parcel9.java
public class Parcel9 {
// Argument must be final or "effectively final"
// to use within the anonymous inner class:
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");
}
}
- 在匿名类中不可能有命名构造器,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果.
// innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class
abstract class Base {
Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{ System.out.println(
"Inside instance initializer"); }
@Override
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
输出:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
在此例中,不要求变量一定是 final 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。
// innerclasses/Parcel10.java
// Using "instance initialization" to perform
// construction on an anonymous inner class
public class Parcel10 {
public Destination
destination(final String dest, final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
@Override
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Tasmania", 101.395F);
}
}
输出:
Over budget!
在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是 if 语句)。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。
- 嵌套类:
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 static,这通常称为嵌套类。想要理解 static 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 static 的时,就不是这样了。嵌套类意味着:
1.要创建嵌套类的对象,并不需要其外围类的对象。
2.不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:
// innerclasses/Parcel11.java
// Nested classes (static inner classes)
public class Parcel11 {
private static class ParcelContents implements Contents {
private int i = 11;
@Override
public int value() { return i; }
}
protected static final class ParcelDestination
implements Destination {
private String label;
private ParcelDestination(String whereTo) {
label = whereTo;
}
@Override
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");
}
}
在 main() 中,没有任何 Parcel11 的对象是必需的;而是使用选取 static 成员的普通语法来调用方法-这些方法返回对 Contents 和 Destination 的引用。在一个普通的(非 static)内部类中,通过一个特殊的 this 引用可以链接到其外围类对象。嵌套类就没有这个特殊的 this 引用,这使得它类似于一个 static 方法。
- 接口内部的类:
嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样:
// innerclasses/ClassInInterface.java
// {java ClassInInterface$Test}
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
@Override
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
输出:
Howdy!
- 从多层嵌套类中访问外部类的成员
// 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();
}
}
- 如果使用内部类,还可以获得其他一些特性:
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 稍后就会展示一个这样的例子。
创建内部类对象的时刻并不依赖于外围类对象的创建
内部类并没有令人迷惑的"is-a”关系,它就是一个独立的实体。 - 闭包与回调
通过内部类实现闭包
// innerclasses/Callbacks.java
// Using inner classes for callbacks
// {java innerclasses.Callbacks}
package innerclasses;
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();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) {
callbackReference = cbh;
}
void go() { callbackReference.increment(); }
}
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 =
new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
输出:
Other operation
1
1
2
Other operation
2
Other operation
3
这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,Callee1 是更简单的解决方式。Callee2 继承自 MyIncrement,后者已经有了一个不同的 increment() 方法,并且与 Incrementable 接口期望的 increment() 方法完全不相关。所以如果 Callee2 继承了 MyIncrement,就不能为了 Incrementable 的用途而覆盖 increment() 方法,于是只能使用内部类独立地实现 Incrementable,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。
注意,在 Callee2 中除了 getCallbackReference() 以外,其他成员都是 private 的。要想建立与外部世界的任何连接,接口 Incrementable 都是必需的。在这里可以看到,interface 是如何允许接口与接口的实现完全独立的。 内部类 Closure 实现了 Incrementable,以提供一个返回 Callee2 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 Incrementable 的引用,都只能调用 increment(),除此之外没有其他功能(不像指针那样,允许你做很多事情)。
Caller 的构造器需要一个 Incrementable 的引用作为参数(虽然可以在任意时刻捕获回调引用),然后在以后的某个时刻,Caller 对象可以使用此引用回调 Callee 类。
回调的价值在于它的灵活性-可以在运行时动态地决定需要调用什么方法。例如,在图形界面实现 GUI 功能的时候,到处都用到回调。
- 内部类标识符:
Counter.class
LocalInnerClass$1.class
LocalInnerClass$LocalCounter.class
LocalInnerClass.class
如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“$”的后面。