可以把一个类的定义放在另一个类的定义内部,这就是内部类。
Java最晦涩的部分之一。
内部类看起来就像是一种代码隐藏机制,将类只与其他类的内部。但远不止如此,内部类了解外部类,并能与之通信。
10.1 创建内部类
创建内部类的方式就如同你想的一样——把类的定义置于外围类的里边
10.2 链接到外部类
- 当生成一个内部类的对象时,此对象与制造它的外部对象之间就有了一种联系,所以它能访问其外围对象的所有成员。
- 内部类还拥有其外围类的所有元素的访问权。当某个外部类当对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外部类对象当引用。当你访问外部类成员时,就是用那个引用来选择外部类当成员。
10.3 使用.this与.new
-
如果你需要生成对外类对象的引用,可以使用外部类
.this
。这样产生的引用自动地具有正确类型,这一点在编译期就被知晓并收到检查,因此没有任何运行开销。 -
通过外部类的对象创建内部类(非static内部类)对象,可以通过外部类
.new
创建内部类。
public class Outer {
void func() {
System.out.println("Test");
}
class Inner {
void func() {
System.out.println("Inner");
// .this语法
Outer.this.func();
}
}
public static void main(String[] args) {
// .new语法
new Outer().new Inner().func();
}
}
10.4 内部类与向上转型
内部类的优点:可以更好的隐藏细节
特点:
- 外部类可以访问内部类的所有元素,无论什么修饰符。
- 普通内部类内不能有
static
域和方法。 - 一个内部类可以被嵌套多层,而且可以访问所有外围类的成员。
10.5 局部内部类
可以在一个方法或任意作用域内定义内部类,成为局部内部类。这么做的理由:
- 你实现了某类型的接口,于是可以创建并返回对其的引用
- 你要解决一个复杂的问题,想创建一个类来复制你的解决方案,但又不希望这个类是公共可用的。
public class Outer {
public void func() {
// 方法内部的内部类
class InnerMethod {
void func() {
System.out.println("class in method");
}
}
new InnerMethod().func();
}
public void f() {
if(true) {
// 作用域内部的内部类
class InnerScope {
void func() {
System.out.println("class in scope");
}
}
new InnerScope().func();
}
}
public static void main(String[] args) {
new Outer().func();
new Outer().f();
}
}
特点:
- 局部内部类类似方法的局部变量,所以在类外或者类的其他方法中不能访问这个内部类。但这并不代表局部内部类的实例和定义了它的方法中的局部变量具有相同的生命周期。
- 可以在同一个子目录(包)下起一个跟局部内部类相同的类,不会有冲突。
-
InnerScope
类被嵌套到if
语句中,这并不是说该类到创建是有条件的,他跟其他的类一样被编译过了。 - 因为不存在外部可见性,局部内部类不能用权限修饰符。
- 不能在局部内部类中使用可变的局部变量,可以使用
final
的局部变量。 - 可以访问外围类的成员变量。如果是
static
方法,则只能访问static
修饰的成员变量。 - 可以使用
final
或abstract
修饰。
10.6 匿名内部类
简介:
-
inner()
方法将返回值的生成与表示这个返回值的类定义结合在一起,而且这个类没有名字。 - 创建一个继承某个类(或者实现某个接口)的匿名类对象。
public class Outer {
private final String outerStr = "Outer";
class Inner {
public Inner(String str) {
System.out.println("Inner Constructor " + str);
}
public void func() {
System.out.println("Inner");
}
}
public Inner inner() {
return new Inner("Dota") {
{
// 跟构造方法一样初始化
str3 = "LOL";
}
private String str1 = Outer.this.outerStr;
private String str2 = outerStr;
private String str3;
@Override
public void func() {
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
}
};
}
public static void main(String[] args) {
new Outer().inner().func();
}
}
细节:
- 返回的类型被自动向上转型成
Inner
的引用。 - 如果构造方法带参数,也可以在
new Inner()
中传递参数给基类的构造器。 - 在匿名内部类定义字段时,可以初始化。
- 在匿名内部类使用外部类的对象时,只能使用
final
的。 -
str1
和str2
是一样的 - 匿名内部类没有名字,也就没有构造器,但是可以通过实例初始化模拟构造器。但是你不能重载实例初始化方法,所以只能有一个这样的构造器。
- 变量
str
不要求是final
的,因为str
是传递给基类的构造器的,匿名内部类无法使用。 - 匿名内部类可以继承类或者实现接口,但不能两者兼得。
10.6.1 再访工厂方法
代码更加简洁
10.7 嵌套类(静态内部类)
嵌套类:
如果不需要内部类对象与其外围对象之间有联系,那么可以将内部类声明为static
。
特点:
- 普通内部类对象隐式的保存了外部类对象,但嵌套类并非如此。
- 要创建嵌套类的对象,并不需要外部类的对象
- 不能从嵌套类对象中访问外部类的非静态对象。
- 普通内部类不能有
static
域和static
方法,但嵌套类可以有。
10.7.1 接口内部的类
- 嵌套类可以作为接口的一部分,还可以实现其外部接口。
- 如果你想创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会很方便。
- 可以使用嵌套类的
main
方法来实现调试。
10.7.2 多层嵌套
- 一个内部类可以嵌套多层
- 一个嵌套类也可以被嵌套多层。
10.8 为什么需要内部类
- 外部类可以有多个内部类,每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
- 接口解决类部分“多重继承”,内部类补充的实现了“多重继承。
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象的信息相互独立。
- 再单个外围类中,可以让多个内部类以不同的方式实现同一个接口。
- 创建内部类对象的时候并不一定依赖外部类对象的创建。
- 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
10.8.1 闭包与回调
闭包
- 闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
- 通过上述定义,可以看出内部类就是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有成员。
- 通过内部类实现闭包的功能是优良的解决方案,它比指针更灵活、更安全。
回调
回调函数的定义:在计算机程序设计中,回调函数是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。
非回调函数的场景:一个程序B
有一个方法b()
,要调用程序A
中的另一个方法a()
。这个很简单,只需要在程序B
的方法b()
中new A().a()
就可以了。
回调函数:跟上述一样,但是程序A
中的方法a()
在完成任务后,还会调用一个预定义好的回调函数;B
在方法b()
中,可以按照预定义好的回调函数接口实现相关逻辑,然后把这段逻辑传递给A
,这样在B.b()
调用A.a()
的时候,就会执行这段逻辑。
// A定义好的回调接口
interface Callback {
void callback();
}
// A定义
public class A {
Callback callback;
public A(Callback callback) {
this.callback = callback;
}
public void a() {
System.out.println("a");
callback.callback();
}
}
class B {
public static void main(String[] args) {
A a = new A(new Callback() {
@Override
public void callback() {
System.out.println("callback");
}
});
a.a();
}
}
// Output:
a
callback
10.8.2 内部类与控制框架
应用程序框架
- 应用程序框架就是被设计用来解决某类特定问题的一个或者一组类。
- 要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案(这是模板方法的一个例子)。
控制框架
控制框架是一类特殊的应用程序框架,他用来解决响应事件的需求。主要用来响应事件的系统被称为事件驱动系统。
public class Test {
private boolean light;
private boolean water;
class LightEvent extends SwitchEvent {
@Override
public void on() {
light = true;
}
@Override
public void off() {
light = false;
}
}
class WaterEvent extends SwitchEvent {
@Override
public void on() {
water = true;
}
@Override
public void off() {
water = false;
}
}
}
abstract class SwitchEvent {
public abstract void on();
public abstract void off();
}
上述代码描述了一个开关事件的抽象类,和两个继承该抽象类的内部类。这些内部类能够*地访问Test
类中的字段,无需任何条件。
命令设计模式
记得看!!!
10.9 内部类的继承
public class Test extends Outer.Inner {
// 如果没有下面的构造方法会编译失败
public Test(Outer outer) {
outer.super();
}
}
class Outer {
class Inner {}
}
- 可以看到
Test
只继承了内部类Inner
,而不是外部类。 - 当要生成一个构造器时,必须要增加这样一段代码
outer.super();
- 解释:内部类的构造器必须连接到指向外部类对象的引用,而在内部类的子类中不再存在可连接的默认对象。所以需要在子类的构造器中包含指向外部类的引用,必须是带参数的,而且参数类型是外部类。说白了就是,内部类的对象依赖外部类的对象,内部类的子类的对象,也仍旧是依赖外部类的对象的。
10.10 内部类可以被覆盖吗
public class Test extends Outer {
class Inner {}
}
class Outer {
class Inner {}
}
上述代码中:Test
继承了Outer
并“覆盖”了Inner
,但这没有用;这两个Inner
是完全毫不相干但两个类,各自活在各自的命名空间里。
public class Test extends Outer {
class Inner extends Outer.Inner {
@Override
void func() {
System.out.println("Test.Inner.func()");
}
}
public Test() {
setInner(new Inner());
}
public static void main(String[] args) {
new Test().getInner().func();
}
}
class Outer {
private Inner inner;
class Inner {
void func() {
System.out.println("Outer.Inner.func()");
}
}
public Inner getInner() {
return inner;
}
public void setInner(Inner inner) {
this.inner = inner;
}
}
上述代码中:Test
继承了Outer
,Test.Inner
继承了Outer.Inner
。此时如果覆盖Inner中的方法,当构造器调用setInner(new Inner());
的时候,是把Test.Inner
向上转型为Outer
中的引用inner
。
10.11 局部内部类
(见10.5)
前面提到过,可以在代码块里创建内部类,典型的方式是在方法体内。
局部内部类不能有访问说明符,因为他不是外部类的一部分。
局部内部类可以访问当前代码块内的常量,以及此外围类的成员。
局部内部类OR匿名内部类
- 局部内部类可以有构造器以及重载构造器,而匿名内部类只能用于实例初始化。
- 局部内部类可以创建多个对象,而匿名内部类最多有一个
10.12 内部类标识符
每个类都会产生一个.class文件,其中包含了如何创建该类的对象的全部信息(此信息产生一个“meta-class”,叫做Class;对象),内部类也是如此。
类文件的命名规则
- 外部类的名字:外部类名.class
- 普通内部类:外部类名
$
内部类名.class - 匿名内部类:外部类名
$
编译器分配的数字.class - 多层嵌套:按从外到内用
$
分割.class
小问题
对于Unix shell而言,$
是一个元字符,所以在列出.class文件的时候,有时会有问题。
10.13 总结
内部类涉及内容相对复杂,多花点时间吧~