内部类的基础知识
内部类的定义
将一个类Inner的定义放在另一个类Outer的定义内部. 则Inner的具体类型为Outer.Inner
如果要引用Inner类型, 我们需要Outer.Inner, 即在类层次上, Inner是寄生于Outer的, 任何关于Inner的操作(如构造, 调用其方法)都需要通过Outer的实例对象生成一个Inner的对象(这样才能跟Outer.Inner类型关联起来)来进行操作.
public class Outer {
public String s;
Outer(String s) {
this.s = s;
}
class Inner {
public void show() {
System.out.println(s);
System.out.println("Inner show");
}
}
public Inner inner() {
return new Inner();
}
public static void main(String[] args) {
Outer o = new Outer("???");
Outer.Inner i = o.inner();
i.show();
System.out.println(o.s);
// ERROR
// Outer.Inner i1 = new Outer.Inner();
Outer.Inner i1 = o.new Inner();
i1.show();
}
}
就像是任何类方法内部可以使用this来调用此类的所有成员一样. 内部类也同样使用隐式的"this指针"来访问外部类的所有成员,而不需要任何特殊条件(类似动态语言如Python,JavaScript的闭包原理).
.this与.new
如果要生成对外部类的引用, 需要.this
public class DotThis {
void f() {
System.out.println("DotThis.f()");
}
public class Inner {
public DotThis outer() {
return DotThis.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();
}
}
这里不能使用return DotThis. 是因为DotThis是一个类, 而DotThis.this是一个引用对象,指的是当前内部类所引用的外部对象.
如果要创建内部类的对象, 则需要.new. 在拥有外部类对象之前不可能创建内部类对象的,这是因为内部类对象会暗暗的连接到创建它的外部类对象上.
public class DotNew {
public class Inner {
public void show() {
System.out.println("Inner show");
}
}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
dni.show();
}
}
内部类与向上转型
内部类的一个用途在于: 实现一个接口. 这样内部类可向上转型为一个接口的对象:
interface A {
String toString();
}
public class C {
private class B implements A {
public String toString() {
return getClass().getName();
}
}
public static void main(String[] args) {
C c = new C();
C.B b = c.new B();
System.out.println(b);
}
}
这种设计符合组合思想,接口的实现类成为具体类的内部对象,从而很好的隐藏其实现细节.
内部类的使用
定义在方法中的类/定义在作用域中的类
定义在方法或作用域中的类, 主要为了解决以下情况: 方法/作用域的逻辑过于复杂, 我们需要创建一个类来辅助解决, 但又不希望这个类是公共可用的.
以下笔记主要基于下例几点:
1. 一个定义在方法中的类.
2. 一个定义在作用域内的类,此作用域在方法的内部.
3. 一个实现了接口的匿名类.
4. 一个匿名类,它扩展了有非默认构造器的类.
5. 一个匿名类,它执行字段初始化.
6. 一个匿名类,它通过实例初始化实现构造.
定义在方法中的类
interface A {
String toString();
}
public class C {
public A show() {
class B implements A {
public String toString() {
return getClass().getName();
}
}
return new B();
}
public static void main(String[] args) {
C c = new C();
// C$1B
System.out.println(c.show());
}
}
定义在作用域内中的内部类
interface A {
String toString();
}
public class C {
public String show(boolean b) {
if (b) {
class B implements A {
public String toString() {
return getClass().getName();
}
}
A a = new B();
return a.toString();
} else {
return "error";
}
}
public static void main(String[] args) {
C c = new C();
System.out.println(c.show(true));
System.out.println(c.show(false));
}
}
匿名内部类
interface Contents {
int value();
}
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();
System.out.println(c.value());
}
}
对于匿名内部类的语法解析如下: new代表新建一个对象, 调用的是Contents()构造器, 其后增加的是类的实际定义.
备注: 这跟动态语言, 如Python/JavaScript的闭包一样.
上述的匿名内部类语法就是下述形式的简化形式:
interface Contents {
int value();
}
public class Parcel7 {
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) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
System.out.println(c.value());
}
}
如果匿名内部类使用了外部的参数, 那么其参数必须命名为final禁止被修改. 而在匿名类中同样可以使用实例初始化(即下例代码中大括号{}部分)来达到类似构造器的效果:
interface Contents {
int value();
}
public class Parcel7 {
public Contents contents(final int value) {
return new Contents() {
private int i;
{
i = value;
}
public int value() { return i; }
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents(42);
System.out.println(c.value());
}
}
使用匿名内部类可以优化其工厂方法:
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
private Implementation1() {}
public void method1() {
System.out.println("Implementation1 method1.");
}
public void method2() {
System.out.println("Implementation1 method2.");
}
public static ServiceFactory factory =
new ServiceFactory() {
@Override
public Service getService() {
return new Implementation1();
}
};
}
class Implementation2 implements Service {
private Implementation2() {}
public void method1() {
System.out.println("Implementation2 method1.");
}
public void method2() {
System.out.println("Implementation2 method2.");
}
public static ServiceFactory factory =
new ServiceFactory() {
@Override
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsume(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsume(Implementation1.factory);
serviceConsume(Implementation2.factory);
}
}
嵌套类
使用static声明的内部类为嵌套类, 及它跟外围类的实例对象并没有任何的关联.
当内部类为static时,意味着:
1. 要创建嵌套类的对象, 并不需要其外围类的对象.
2. 不能从嵌套类的对象中访问非静态的外围类对象.
interface A {
String toString();
}
public class C {
public static class B implements A {
public String toString() {
return getClass().getName();
}
}
public static void main(String[] args) {
A a = new C.B();
System.out.println(a);
}
}
假设我们需要在接口中编写通用的公用代码, 用于不同接口实现的类所公用, 那么在接口中内嵌类是非常好的方法:
interface A{
void howdy();
class Test implements A{
public void howdy() {
System.out.println("Howdy");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
public class ClassInInterface implements A{
public void howdy() {}
public static void main(String[] args) {
A.Test t = new A.Test();
t.howdy();
}
}
我们通常在类中编写main来测试这个类. 如果嫌麻烦我们可以使用嵌套类来实现测试代码:
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();
}
}
}
执行: java TestBed$Tester即可测试.
为什么要使用内部类
主要原因在于: 每个内部类都能独立的继承自一个(接口的)实现, 所以无论外围类是否已经继承了某个(接口的)实现, 对于内部类都没有影响.
针对多重继承, 接口只解决了部分问题, 内部类使之得到完善.
考虑以下场景: 即必须在一个类中以某种方式实现两个接口. 这时候, 我们有两个选择: 要么使用单一类(全部implements两个接口), 要么使用内部类:
interface A {
void show();
}
interface B {
void func();
}
class X implements A, B {
public void show() {
System.out.println("X show");
}
public void func() {
System.out.println("X func");
}
}
class Y implements A {
public void show() {
System.out.println("Y show");
}
B makeB() {
// 返回一个匿名类
return new B() {
public void func() {
System.out.println("Y func");
}
};
}
}
public class MultiInterfaces {
static void takeA(A a) {
a.show();
}
static void takeB(B b) {
b.func();
}
public static void main(String[] args) {
X x = new X();
Y y = new Y();
takeA(x);
takeA(y);
takeB(x);
takeB(y.makeB());
}
}
但如果拥有抽象类或具体类,而不是接口, 则只能使用内部类才能实现多重继承.
interface A {
void show();
}
abstract class B {
abstract void func();
}
class Y implements A {
public void show() {
System.out.println("Y show");
}
B makeB() {
// 返回一个匿名类
return new B() {
public void func() {
System.out.println("Y func");
}
};
}
}
public class MultiInterfaces {
static void takeA(A a) {
a.show();
}
static void takeB(B b) {
b.func();
}
public static void main(String[] args) {
Y y = new Y();
takeA(y);
takeB(y.makeB());
}
}
1. 内部类可以有多个实例, 每个实例都有自己的状态信息, 并且与其外围类对象的信息相互独立.
2. 在单个外围类中, 可以让多个内部类以不同的方式实现同一个接口, 或继承同一个类.
3. 创建内部类对象并不依赖于外围类对象的创建.
4. 内部类是独立的实体.
闭包与回调
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域. 通过此定义,可以看出内部类是面向对象的闭包, 因为它不仅包含外围类对象(创建内部类的作用域)的信息, 还自动拥有一个指向此外围类对象的引用, 在此作用域内, 内部类有权操作所有的成员, 包括private成员.
内部类的继承
在继承内部类的时候, 由于内部类关联一个外部类的实例, 所以大概格式如下:
class WithInner {
class Inner{}
}
public class InheritInner extends WithInner.Inner {
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
内部类的覆盖
两个内部类是独立的两个实体,各自在自己的命名空间, 它们需要具体的外部类实例进行引用.
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 Egg()
//Egg.Yolk()
new BigEgg();
}
}
局部内部类
我们可以在一个方法体的里面创建一个内部类,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,以及此外围类的所有成员.
下例对局部内部类与匿名内部类的创建进行了比较:
interface Counter {
int next();
}
public class LocalInnerClass {
private int count = 0;
Counter getCounter(final String name) {
class LocalCounter implements Counter {
public LocalCounter() {
System.out.println("LocalCounter()");
}
public int next() {
System.out.print(name);
return count++;
}
}
return new LocalCounter();
}
Counter getCounter2(final String name) {
return new Counter() {
{
System.out.println("Counter");
}
@Override
public int next() {
System.out.print(name);
return count++;
}
};
}
public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter c1 = lic.getCounter("Local inner ");
Counter c2 = lic.getCounter2("Anonymous inner");
for (int i = 0; i < 5; i++)
System.out.println(c1.next());
for (int i = 0; i < 5; i++)
System.out.println(c2.next());
}
}
输出:
LocalCounter()
Counter
Local inner 0
Local inner 1
Local inner 2
Local inner 3
Local inner 4
Anonymous inner5
Anonymous inner6
Anonymous inner7
Anonymous inner8
Anonymous inner9
使用局部类而不是匿名内部类的唯一理由是: 我们需要一个已命名的构造器,或者需要重载构造器, 而匿名内部类只能用于实例初始化.