第十章 内部类
讲一个类的定义放在另一个类定义的内部,这就是内部类。
10.1 创建内部类
在类内定义类
10.2 链接到外部类
内部类对象拥有外部类所有元素的访问权,不需要任何条件。应用:“迭代器”设计模式。
//: 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
*///:~
10.3 使用.this与.new
10.3.1 生成对外部对象的引用
使用.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();
}
} /* Output:
DotThis.f()
*///:~
10.3.2 创建内部类对象
无法直接创建,需要先创建外部类,然后再调用.new
//: innerclasses/DotNew.java
// Creating an inner class directly using the .new syntax.
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
} ///:~
10.4 内部类与向上转型
将内部类上转型为其基类,尤其是转型成一个接口时,内部类就有了用武之地。这是因为此内部类–某个接口的实现–能够完全不可见。
//: innerclasses/Destination.java
public interface Destination {
String readLabel();
} ///:~
//: innerclasses/Contents.java
public interface Contents {
int value();
} ///:~
//: 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(或者protected)后,只能有外部类的方法才能访问得到。无法通过内部类来创建内部类对象。private内部类给设计者提供一种路径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏实现的细节。此外从客户端看,由于无法访问任何新增加的,原本不属于接口的公共接口方法,所以拓展接口是没有价值的。
10.5 在方法和作用域的内部类
使用内部类的场景:
- 实现某个类型的接口,可以创建并返回对其的引用。
- 解决一个复杂的问题,需要创建一个类来加以解决,但又不想这个类公共可用。
10.5.1 局部内部类
可以在任何作用域内创建局部类,并且可以在任意类的某个内部类中取相同的名字,并不会发生命名冲突。
10.6 匿名内部类
10.6.1 匿名类的使用
如下例:创建了一个实现Contents接口的匿名类。在返回值时,发生了向上转型。
public class Parcel7 {
public Contents contents() {
return new Contents() { // Insert a class definition
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
} ///:~
10.6.2 匿名类中的初始化
如果实现的接口/基类需要参数,那么只需要给相应的构造器传参数即可。
在匿名类定义字段时,还能够对其初始化,但是将参数引用设置成final。
匿名类没有构造器,但是可以通过实力初始化达到构造器的效果。
// /: innerclasses/Parcel9.java
// An anonymous inner class that performs
// initialization. A briefer version of Parcel5.java.
public class Parcel9 {
// Argument must be final to use inside
// anonymous inner class:
public Destination destination(String dest, int i, Object ob) {
return new Destination() {
private String label = dest;
private int lv = i;
private Object obj = ob;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania", 10, new Object());
}
} ///:~
10.6.3 再访工厂方法
匿名类使工厂方法变得美妙。因为可以隐式的在工厂方法中定义匿名类并返回。
10.7 嵌套类
声明为static的内部类通常被称为内部类。
- 要创建嵌套类,并不需要其外部对象。
- 不能从嵌套类中访问非静态的外部变量。
- 在普通内部类中,通过特殊关键字this可以访问到外围对象,但静态内部类没有这一关键字。
10.7.1接口的内部类
正常情况下,不能再接口里面放置任何代码,但是嵌套类可以作为接口的一部分。放在接口中的任何类都是public与static的。因为类是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!
*///:~
10.7.2 从多重嵌套类中访问外部类成员
一个内部类被嵌套所少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员。
10.8 为什需要内部类
内部类最吸引人的原因:每个类内部都能独立地继承自一个(接口的)实现,所以无论外围类时都已经继承了某个(接口的)实现。对于内部类都是没有影响的。
内部类还可以无障碍的访问外部类成员。
类解决了多重继承的问题
当类实现了两个接口时,可以使用继承或者内部类。但是当拥有的是抽象或者具体的类时(不是接口),此时只能继承一个类,那就只能使用内部类才能实现多重继承。
集成多个接口
//: 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());
}
} ///:~
使用内部类的其他特性
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围对象的信息相互独立。
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
- 创建内部类对象的时刻并不依赖与外围类对象的创建。
- 内部类没有令人迷惑的“is-a”关系;它就是一个独立的实体。
10.8.1 闭包与回调
闭包:是一个可调用的对象,它记录了一些信息,这些信息来自创建它的作用域。
内部类可以看作是类的闭包。他不仅包含万为对象的信息,还自动包含一个指向外围类对象的引用,在作用域内,内部类有权操作所有成员,包括private成员。
人们认为Java应该包含某种类似的机制,以允许回调。通过糊掉,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。Java因指针安全考虑,没有在语言里包含指针。
10.8.2 内部类与控制框架
内部类使得控制框架变得方便:
(1)控制框架的完整实现是由单个类创建,从而使的实现的细节被封装起来。内部类用来表示解决问题所需的各种不同的action()。
(2)内部类能够很容易地访问外围的任意成员,所以可以避免这种实现变得笨拙。
10.9 内部类的继承
暂pass。
//: 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);
}
} ///:~
10.0 内部类会被覆盖吗
下例中,如果发生覆盖,则第二行应该输出的是覆盖后的Yolk版本。当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化,这两个内部类完全是不同的个体,各自在自己的命名空间里。如果明确继承某个内部类是可以的。
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()
*///:~
//: innerclasses/BigEgg2.java
// Proper inheritance of an inner class.
import static net.mindview.util.Print.*;
class Egg2 {
protected class Yolk {
public Yolk() { print("Egg2.Yolk()"); }
public void f() { print("Egg2.Yolk.f()");}
}
private Yolk y = new Yolk();
public Egg2() { print("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() { print("BigEgg2.Yolk()"); }
public void f() { print("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()
*///:~
10.11 局部内部类–对比匿名内部类
局部内部类对比匿名内部类的区别在于:需要一个已命名的构造器,或者需要重载构造器,而匿名类只能用于实例初始化。
10.12 内部类与标识符
普通的类在编译后会产生.class文件。而内部类与匿名类也会产生,并且有严格的命名规则:
- 内部类:[外部类名]$[内部类名].class
- 匿名类:[外部类名]$[数字].class
- 嵌套:加载其外围标识符与"$"后面
10.13 总结
Java中接口和内部类结合,解决多继承的问题。
转载:https://blog.csdn.net/nianmaodu/article/details/105968414