本文是对《On Java 8》即《Java编程思想》第五版内部类知识点汇总整理,仅供学习分享。
内部类
- 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。
- 如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体地指明这个对象的类型:OuterClassName.InnerClassName。(在外部类的静态方法中也可以直接指明类型 InnerClassName,在其他类中需要指明 OuterClassName.InnerClassName。)
- 当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。
- 内部类拥有其外围类的所有元素的访问权。
- 当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用
- 需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 this。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。
- 需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 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"
}
}
public Inner inner() { return new Inner(); }
public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}
- 想要告知某些其他对象,去创建其某个内部类的对象。必须在 new 表达式中提供对其他外部类对象的引用,需要使用 .new 语法
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
- 如果创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
- private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。
- 由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给 Java 编译器提供了生成高效代码的机会。
内部类方法和作用域
-
可以在一个方法里面或者在任意的作用域内定义内部类。两个理由:
- 你实现了某类型的接口,于是可以创建并返回对其的引用。
- 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。
-
可以实现:
- 一个定义在方法中的类。
- 一个定义在作用域内的类,此作用域在方法的内部。
- 一个实现了接口的匿名类。
- 一个匿名类,它扩展了没有默认构造器的类。
- 一个匿名类,它执行字段初始化。
- 一个匿名类,它通过实例初始化实现构造(匿名内部类不可能有构造器)。
-
在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部。
例如:
public class Parcel5 {
/**
*PDestination 类是 destination() 方法的一部分,而不是 Parcel5 的一部分。所以,在 destination() 之外不能访问 *PDestination
*/
public Destination destination(String s) {
final 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");
}
- 在任意的作用域内嵌入一个内部类:
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();
}
//在定义 Trackingslip 的作用域之外,它是不可用的
// 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();
}
}
匿名内部类
- 如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 final 的(也就是说,它在初始化后不会改变,所以可以被当作 final)
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");
}
}
-
在匿名类中不可能有命名构造器,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果
abstract class Base { Base(int i) { System.out.println("Base constructor, i = " + i); } public abstract void f(); } public class AnonymousConstructor { //不要求变量一定是 final 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。 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(); } }
-
在匿名类内部使用的参数必须是 final 的,即使不加 final, Java 8 的编译器也会为我们自动加上 final,以保证数据的一致性
-
对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。
嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 static,这通常称为嵌套类。
-
普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象
-
当内部类是 static 即嵌套类时意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象。
- 不能从嵌套类的对象中访问非静态的外围类对象。
-
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西
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");
}
}
-
嵌套类可以作为接口的一部分。放到接口中的任何类都自动地是 public 和 static 的
-
可以在内部类中实现其外围接口,就像下面这样:
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(); } } }
-
一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员
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();
//".new"语法能产生正确的作用域,所以不必在调用构造器时限定类名
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
为什么需要内部类
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
-
内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。
-
内部类实现一个接口与外围类实现一个接口的区别:后者不是总能享用到接口带来的方便,有时需要用到接口的实现
-
内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”
-
如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承
-
使用内部类,还可以获得其他一些特性:
-
内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
-
在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
-
创建内部类对象的时刻并不依赖于外围类对象的创建
-
内部类并没有令人迷惑的"is-a”关系,它就是一个独立的实体。
-
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 private 成员。
-
在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为,并且语法更加优雅和简洁
-
通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。
-
通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活、更安全
-
回调的价值在于它的灵活性-可以在运行时动态地决定需要调用什么方法
-
应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决你的特定问题
-
控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作事件驱动系统
-
内部类允许:
- 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的
action()
。 - 内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。
- 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的
- 可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建
- 局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员
- 使用局部内部类而不使用匿名内部类的一个理由就是,需要不止一个该内部类的对象。
- 由于编译后每个类都会产生一个**.class** 文件,其中包含了如何创建该类型的对象的全部信息,内部类也必须生成一个**.class** 文件以包含它们的 Class 对象信息,且命名有严格的规则:外围类的名字,加上“$",再加上内部类的名字
- 如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“$”的后面。