0%

[ ] 结合 Filling containers 章节复习 Adapter 设计模式

前言

本章需要些许 Generic 章节的知识,所以看之前最好过一遍前章。

Full container taxonomy 分类学

Java5 中新加的内容:

  • Queue 接口以及对应的实现 PriorityQueue,BlockingQueue 将在 Concurrency 章节介绍
  • ConcurrentMap 以及对应实现 ConcurrentHashMap 也放到 Concurrency
  • CopyOnWriteArrayList, CopyOnWriteArraySet 同上
  • EnumSet and EnumMap, special implementations of Set and Map for use with enums, and shown in the Enumerated Types chapter.
  • Collectipns 中的一些单元方法

你可以看到这个整个集合体系中有几个类是以 Abstract 开头的,这些用虚线框包裹的类表示抽象类。他们实现了对应接口的部分功能,比如当你想要实现一个 Set 接口的时候, 你不会想要实现 Set 接口并且实现里面的所有方法。一般来说,我们会通过继承 AbstractSet 类做一个最小实现。但是说实话现在集合类基本上能满足你的需求了,一般不需要自己做扩展。

Filling containers

类似于 Arrays 这个 util 类,针对集合类,也有一个 util 类叫做 Collections。它里面有一个 fill() 方法可以将一个对象复制填充满整个容器,执行结果返回一个 List, 我们可以将整个 list 传给其他构造函数或者调用 addAll() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class StringAddress {
private String s;

public StringAddress(String s) {
this.s = s;
}

public String toString() {
return super.toString() + " " + s;
}
}

public class FillingLists {
public static void main(String[] args) {
List<StringAddress> list = new ArrayList<>(
Collections.nCopies(4, new StringAddress("Hello")));
System.out.println(list);
Collections.fill(list, new StringAddress("World!"));
System.out.println(list);
}
}

// output:
// [reading.container.StringAddress@7c53a9eb Hello, reading.container.StringAddress@7c53a9eb Hello, reading.container.StringAddress@7c53a9eb Hello, reading.container.StringAddress@7c53a9eb Hello]
// [reading.container.StringAddress@ed17bee World!, reading.container.StringAddress@ed17bee World!, reading.container.StringAddress@ed17bee World!, reading.container.StringAddress@ed17bee World!]

如上例所示,给出了两种方法填充集合,一种是调用 Collections.nCopies() 另一种是调用 Collections.fill(),第一种扩展性更好。第二种方式只支持替换,不能扩容。

A Generator solution

Collection 子类都有一个接收其他 Collection 的构造器。为了实验方便,我们创建一个构造器,接收类型和数量做参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.ArrayList;

interface Generator<T> { T next(); }

public class CollectionData<T> extends ArrayList<T> {
public CollectionData(Generator<T> gen, int quantity) {
for (int i = 0; i < quantity; i++)
add(gen.next());
}

// A generic convenience method:
public static <T> CollectionData<T> list(Generator<T> gen, int quantity) {
return new CollectionData<>(gen, quantity);
}
}

通过上面的代码我们可以构造一个任意容量的容器,创建的对象也可以作为参数传给任意 Collection 子类。同时结合自带的 addAll() 方法也可以用于填充容器。

CollectionData is an example of the Adapter design pattern;1 it adapts a Generator to the constructor for a Collection.

Here’s an example that initializes a LinkedHashSet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.LinkedHashSet;
import java.util.Set;

class Government implements Generator<String> {
String[] foundation = ("strange women lying in ponds " +
"distributing swords is no basis for a system of " +
"government").split(" ");
private int index;

public String next() {
return foundation[index++];
}
}

public class CollectionDataTest {
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<>(
new CollectionData<>(new Government(), 15));
// Using the convenience method:
set.addAll(CollectionData.list(new Government(), 15));
System.out.println(set);
}
}

// output:
// [strange, women, lying, in, ponds, distributing, swords, is, no, basis, for, a, system, of, government]

由于 LinkedHashSet 的关系,所以容器中元素的顺序不变。

TODO: 后面的例子需要 Arrays 章节的内容,没看过,直接继续感觉很不爽,我回头看看先。Arrays 只有 30+ pages 应该挺快的

想要解决的问题:

  1. 了解集合类的大致情况,包括名称,类关系
  2. HashMap 和 Collection 的关系
  3. 自己画一个关系图并和 TIJ4 做对比
  4. 为什么 ArrayList 在继承了 AbstractList 之后还要 impl List 接口?意义上不是重复了吗

Answers:

  1. 这样做,语义上没有改变,便于阅读,省的你再去一层层的去父类找接口实现, Stackoverflow 上是这么说的

基本上能将这一簇类的关系图画出来即可

更抽象的命名

今天在写一个 Java bean 的时候,为了 merge bean 的属性,特意给这个 bean 写了一个 mergeUpdatedProperties(Properties props) 方法,同时 review 之后提出,直接用 merg(Bean bean) 的方式会更有扩展性,深以为然。

最近在使用 Class 这个类的时候遇到一些问题,顺便记录一下这个类中方法的使用案例

isAssignFrom

简单来说就是测试传入的 Class 是不是前面的 Class 本身或子类, 同时适用于接口实现的情况。

1
2
3
4
5
6
7
8
9
10
System.out.println("Number isAssignableFrom Number.class: " + Number.class.isAssignableFrom(Number.class));
System.out.println("Number isAssignableFrom Integer.class: " + Number.class.isAssignableFrom(Integer.class));
System.out.println("Integer.class isAssignableFrom Number: " + Integer.class.isAssignableFrom(Number.class));
System.out.println("Collection.class isAssignableFrom ArrayList.class: " + Collection.class.isAssignableFrom(ArrayList.class));

// output:
// Number isAssignableFrom Number.class: true
// Number isAssignableFrom Integer.class: true
// Integer.class isAssignableFrom Number: false
// Collection.class isAssignableFrom ArrayList.class: true

前述

Runtime type information(RTTI) allows you to discover and use type information while a program is running.

两种使用方式:

  1. 传统模式,假定你在编译期就知道所有用到的类型
  2. 反射模式,你只在运行时才知道类信息

The need for RTTI

简单的继承关系示例:

基类:Shape 包含方法 draw(), 子类:Circle, Square, Triangle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.Arrays;
import java.util.List;

abstract class Shape {
void draw() {
System.out.println(this + ".draw()");
}

abstract public String toString();
}

class Circle extends Shape {
public String toString() {
return "Circle";
}
}

class Square extends Shape {
public String toString() {
return "Square";
}
}

class Triangle extends Shape {
public String toString() {
return "Triangle";
}
}

public class Shapes {
public static void main(String[] args) {
List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
for (Shape shape : shapeList) shape.draw();
}
}
// output:
// Circle.draw()
// Square.draw()
// Triangle.draw()

在上面的例子中,我本将子类结合 List 强转成父类,然后统一做操作,这种做法更易读,容易维护。这也是面向对象的目标之一,但是如果我想在运行时得知这个对象的具体类型,应该怎么做?

The Class object

在 Java 中有一个神奇的类他叫 Class 类,所有创建类实例的行为都和他有关。 Java 的 RTTI 特性也是通过它来实现的。当你编译一个类的时候,JVM 会通过 class 创建一个对应的 Class 类来存储对应的信息。

类加载器由一组 class loaders 组成,但是已有一个 primordial class loader,他是 JVM 的一部分,他会加载所有的 trusted classes,这写 trusted class 包括 Java API classes, 比如本地磁盘上的 classes。通常你不需要自己新加 class loader 但是如果有特殊需要,想加也是可以的。

只有当第一次使用的时候,JVM 才会加载对应的 class。这个行为发生在类第一次关联到 static 实体, 构造函数也是一个特殊的 static method,换句话说,当我们 new 一个对象的时候,加载器就会加载对应的 class。

Java 中只允许 Class 加载一次,加载完成之后,以后所有这个 class 对应的实体都是通过它来创建的。

PS:这里翻译很生硬,缺少很多类加载的相关知识,可以看过 JVM 那本书之后,再来完善一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Candy {
static {
System.out.println("Loading Candy");
}
}

class Gum {
static {
System.out.println("Loading Gum");
}
}

class Cookie {
static {
System.out.println("Loading Cookie");
}
}

public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try {
Class.forName("Gum");
} catch (ClassNotFoundException e) {
System.out.println("Couldn’t find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
// output:
// inside main
// Loading Candy
// After creating Candy
// Couldn’t find Gum
// After Class.forName("Gum")
// Loading Cookie
// After creating Cookie

当各个类在第一次调用时对应的静态代码块就会被调用,输出我们定制的信息。上例有一个比较特殊的语法 forName() 我们可以通过这个方法拿到对应的 Class 引用,当然如果找不到会抛 ClassNotFoundExcepiton。如果实体类已经创建了,你也可以通过 Object.getClass() 来拿到对应的类应用。

上例中通过 forName 调用 Gum 类的代码段,按理说是不会报错的,可能是例子中没有给全路径的关系。示例中应该写成连续调用两次,但是 log 只打印一次这样的形式可能更好。

下面这个示例展示了部分 Class 中的常用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package samples;

interface HasBatteries {
}

interface Waterproof {
}

interface Shoots {
}

class Toy {
// Comment out the following default constructor
// to see NoSuchMethodError from (*1*)
Toy() {
}

Toy(int i) {
}
}

class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
FancyToy() {
super(1);
}
}

public class ToyTest {
static void printlnInfo(Class cc) {
System.out.println("Class name: " + cc.getName() +
" is interface? [" + cc.isInterface() + "]");
System.out.println("Simple name: " + cc.getSimpleName());
System.out.println("Canonical name : " + cc.getCanonicalName());
}

public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("samples.FancyToy");
} catch (ClassNotFoundException e) {
System.out.println("Can’t find FancyToy");
System.exit(1);
}
printlnInfo(c);
for (Class face : c.getInterfaces())
printlnInfo(face);
Class up = c.getSuperclass();
Object obj = null;
try {
// Requires default constructor:
obj = up.newInstance();
} catch (InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch (IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
printlnInfo(obj.getClass());
}
}
// output:
// Class name: samples.FancyToy is interface? [false]
// Simple name: FancyToy
// Canonical name : samples.FancyToy
// Class name: samples.HasBatteries is interface? [true]
// Simple name: HasBatteries
// Canonical name : samples.HasBatteries
// Class name: samples.Waterproof is interface? [true]
// Simple name: Waterproof
// Canonical name : samples.Waterproof
// Class name: samples.Shoots is interface? [true]
// Simple name: Shoots
// Canonical name : samples.Shoots
// Class name: samples.Toy is interface? [false]
// Simple name: Toy
// Canonical name : samples.Toy
  • getSimpleName(): 输出类名
  • getCanonicalName(): 输出全路径名
  • islnterface(): 是否是接口
  • getlnterfaces(): 拿到类实现的接口
  • getSuperclass(): 拿到父类 Class 引用
  • newlnstance(): 创建实例,但是这个方法要求对应的类必须有默认构造函数

Class literals(字面量)

除了上面的方法,你还可以通过使用类的字面量来拿到 Class 引用,相比与 forName() 的形式,它更简单,安全,不需要 try-catch 块,效率也更高。

普通类,接口,数组和基本数据类型都可以使用这个语法,对于包装类,它内部有一个 TYPE field 可以指向对应的 Class。

primitive wrapper
boolean.class Boolean.TYPE
char.class Char.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE

通常建议使用 ‘.class’ 的这种语法,它和我们平时的使用方式更统一。调用 ‘.class’ 的时候并不会自动初始化一个 Class 对象。在初始化 Class 时有三个步骤:

  1. Loading, which is performed by the class loader. 找到生成 Class 对应的字节码
  2. Linking. 验证字节码,为静态变量分配空间,解决依赖问题
  3. Initialization. 如果还有父类,父类会先初始化,然后执行静态构造器和代码块

初始化会延期,直到确定第一个静态方法(构造函数是一个隐式的静态方法)或非常量的静态 field 引用:

示例说明:

我们声明了三个 Initabl 类做测试,每个类都包含一个 static 代码段答应测试 log 来显示类初始化是否被执行。

  • Initable 包含两个常量,第一个静态常量,第二个是计算后才能得到的值。通过 class 调用变量一时,类不会被初始化。调用第二个变量时,会调用到静态方法,类初始化被触发
  • 在调用 Initable2.staticNonFinal 时,由于他是一个静态非常量,所以初始化被触发
  • Initable3 测试时,通过 forName 调用,初始化必定被触发

final 即 常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package review;

import java.util.Random;

class Initable {
static final int staticFinal = 47;
static final int staticFinal2 =
ClassInitialization.rand.nextInt(1000);

static {
System.out.println("Initializing Initable");
}
}

class Initable2 {
static int staticNonFinal = 147;

static {
System.out.println("Initializing Initable2");
}
}

class Initable3 {
static int staticNonFinal = 74;

static {
System.out.println("Initializing Initable3");
}
}

public class ClassInitialization {
public static Random rand = new Random(47);

public static void main(String[] args) throws Exception {
Class initable = Initable.class;
System.out.println("After creating Initable ref");
// Does not trigger initialization:
System.out.println(Initable.staticFinal);
// Does trigger initialization:
System.out.println(Initable.staticFinal2);
// Does trigger initialization:
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("review.Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
}
// output:
// After creating Initable ref
// 47
// Initializing Initable
// 258
// Initializing Initable2
// 147
// Initializing Initable3
// After creating Initable3 ref
// 74

实际上,初始化会尽可能晚的执行。从 initable 的例子可以看出,调用 ‘.class’ 语法并不会导致一个 Class 的初始化,但是从 initable3 可以看出 Class.forName() 会直接导致初始化。

如果调用的是 static final 这种编译期常量,如 Initable.staticFinal 所示,那么该值也可以在类为初始化时就可用。

如果变量不是 final 类型的,那么在访问之前就需要进行 link 和 initialization 动作,如 Initable2.staticNonFinal 所示。

Generic class references

具体的 Class 对象包含了静态变量,方法等创建一个对象所需要的所有必要元素。但是在 Java 5 之前这种 reference 都是 object 类型的,但是 Java 5 之后,通过引入泛型,我们可以用一种更特殊的方式指代它。

实例说明:

同样是持有 int 的 class reference,如果没有采用泛型,reference 之间可以随便关联,如果带有泛型则会进行类型检测。

1
2
3
4
5
6
7
8
9
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class; // Same thing
intClass = double.class;
// genericIntClass = double.class; // Illegal
}
}

如果你想要更宽松的类型检测,可以使用类似 Class<? extends Number> genericNumberClass = int.class; 的语法。

在 Java 5 中 Class<?> 效果上和 Class 等价,但是前者没有 warning, 因语义上他更清晰的表明,这个 Class 不是一个 non-specific 的类。

Class 声明中加入泛型语法的唯一作用就是在编译期进行类型检测。

实例说明:

简单的泛型使用案例,就是没什么逻辑,只是使用,看起来感觉没什么目的,比较难记忆。CountedInteger 的 counter 属性是一个静态变量,充当实例的 id 的角色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class CountedInteger {
private static long counter;
private final long id = counter++;

public String toString() {
return Long.toString(id);
}
}

public class FilledList<T> {

private Class<T> type;

public FilledList(Class<T> type) {
this.type = type;
}

public List<T> create(int nElements) {
List<T> result = new ArrayList<T>();
try {
for (int i = 0; i < nElements; i++)
result.add(type.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}

public static void main(String[] args) {
FilledList<CountedInteger> fl =
new FilledList<CountedInteger>(CountedInteger.class);
System.out.println(fl.create(15));
}
}
// output:
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

注意点:

  1. CountedInteger 必须有默认的无参构造函数,不然嗲用 nweInstance 会抛错
  2. 如果带有泛型,newInstance 直接返回对应的对象,而不是 Object 对象

对应下面的例子,up 在指定类型的时候用的是 <? super FancyToy> (FancyToy 的超类),并不能表示为直接父类,所以下面的 newInstanc() 对应的类型为 Object。

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericToyTest {
public static void main(String[] args) throws Exception {
Class<FancyToy> ftClass = FancyToy.class;
// Produces exact type:
FancyToy fancyToy = ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
// This won’t compile:
// Class<Toy> up2 = ftClass.getSuperclass();
// Only produces Object:
Object obj = up.newInstance();
}
}

New cast syntax

Java 5 中还为 Class 添加了 cast(object) 方法用于将参数强转为 Class 对应的类实例。

1
2
3
4
5
6
7
8
9
10
11
12
class Building {}

class House extends Building {}

public class ClassCasts {
public static void main(String[] args) {
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b);
h = (House) b; // ... or just do this.
}
}

使用括号的那种强转方式要比调用方法的方便很多,需要注意的是,一开始 new 的时候对应的实现是 House 才能在后面进行 b 强转成 a 的,如果你一开始声明为 new Building() 而且两边的方法有出入,运行时会抛异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Building {}

class House extends Building {
public void method01() {
System.out.println("m1...");
}
}

public class ClassCasts {
public static void main(String[] args) {
Building b = new Building();
((House)b).method01();
}
}

// output:
// Exception in thread "main" java.lang.ClassCastException
// at review.ClassCasts.main(ClassCasts.java:15)

Checking before a cast

父类强转到子类的过程叫做 downcast, java 中如果没有显示的 check 的话,这种强转是不允许的。这里就要提到 RTTI 的第三种模式 instanceof 语法。if (x instanceof Dog) ((Dog)x).bark();

实例说明:

  1. Individual 具体 code 在 Containers in Depth 那一个章节,只需要知道它里面有一个 id() 方法可以给每个继承这个累的对象一个唯一的值做 id, 构造函数的参数则是自定义的 name, 可以重复的
  2. 定义了一大串有继承关系的类,继承关系如下
  3. 定义一个抽象构造器 PetCreator 创建声明的这些类
  4. 实现这个抽象构造器 ForNameCreator,其实就是将声明的类通过 Class.forName 拿到引用再塞到 type() 方法的返回值中
  5. 便携测试程序 PetCount, 创建一个内部类 PetCounter 统计 pet 的出现次数,有一个静态方法 countPets 传入 PetCreater 用来随机创建 Pet 对象

individual -> person
|-> pet -> dog - mutt(串串)
| |-> pug(哈巴狗)
|-> cat -> EgyptianMau (埃及猫)
| |-> Manx(曼岛猫) -> Cymric(威尔士猫)
|-> Rodent 啮齿动物 -> Rat 鼠 -> Mouse 小鼠
|-> Hamster 仓鼠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
class Individual implements Comparable<Individual> {
private static long counter = 0;
private final long id = counter++;
private String name;

public Individual(String name) {
this.name = name;
}

// ‘name’ is optional:
public Individual() {
}

public String toString() {
return getClass().getSimpleName() +
(name == null ? "" : " " + name);
}

public long id() {
return id;
}

public boolean equals(Object o) {
return o instanceof Individual &&
id == ((Individual) o).id;
}

public int hashCode() {
int result = 17;
if (name != null)
result = 37 * result + name.hashCode();
result = 37 * result + (int) id;
return result;
}

public int compareTo(Individual arg) {
// Compare by class name first:
String first = getClass().getSimpleName();
String argFirst = arg.getClass().getSimpleName();
int firstCompare = first.compareTo(argFirst);
if (firstCompare != 0)
return firstCompare;
if (name != null && arg.name != null) {
int secondCompare = name.compareTo(arg.name);
if (secondCompare != 0)
return secondCompare;
}
return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
}
}

class Person extends Individual {
public Person(String name) {
super(name);
}
}

class Pet extends Individual {
public Pet(String name) {
super(name);
}

public Pet() {
super();
}
}

class Dog extends Pet {
public Dog(String name) {
super(name);
}

public Dog() {
super();
}
}

class Mutt extends Dog {
public Mutt(String name) {
super(name);
}

public Mutt() {
super();
}
}

class Pug extends Dog {
public Pug(String name) {
super(name);
}

public Pug() {
super();
}
}

class Cat extends Pet {
public Cat(String name) {
super(name);
}

public Cat() {
super();
}
}

class EgyptianMau extends Cat {
public EgyptianMau(String name) {
super(name);
}

public EgyptianMau() {
super();
}
}

class Manx extends Cat {
public Manx(String name) {
super(name);
}

public Manx() {
super();
}
}

class Cymric extends Manx {
public Cymric(String name) {
super(name);
}

public Cymric() {
super();
}
}

class Rodent extends Pet {
public Rodent(String name) {
super(name);
}

public Rodent() {
super();
}
}

class Rat extends Rodent {
public Rat(String name) {
super(name);
}

public Rat() {
super();
}
}

class Mouse extends Rodent {
public Mouse(String name) {
super(name);
}

public Mouse() {
super();
}
}

class Hamster extends Rodent {
public Hamster(String name) {
super(name);
}

public Hamster() {
super();
}
}

abstract class PetCreator {
private Random rand = new Random(47);

// 模版方法 模式
// The List of the different types of Pet to create:
public abstract List<Class<? extends Pet>> types();

public Pet randomPet() { // Create one random Pet
int n = rand.nextInt(types().size());
try {
return types().get(n).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public Pet[] createArray(int size) {
Pet[] result = new Pet[size];
for (int i = 0; i < size; i++)
result[i] = randomPet();
return result;
}

public ArrayList<Pet> arrayList(int size) {
ArrayList<Pet> result = new ArrayList<Pet>();
Collections.addAll(result, createArray(size));
return result;
}
}

class ForNameCreator extends PetCreator {
private static List<Class<? extends Pet>> types =
new ArrayList<Class<? extends Pet>>();
// Types that you want to be randomly created:
private static String[] typeNames = {
"review.Mutt",
"review.Pug",
"review.EgyptianMau",
"review.Manx",
"review.Cymric",
"review.Rat",
"review.Mouse",
"review.Hamster"
};

@SuppressWarnings("unchecked")
private static void loader() {
try {
for (String name : typeNames)
types.add((Class<? extends Pet>) Class.forName(name));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

static {
loader();
}

public List<Class<? extends Pet>> types() {
return types;
}
}

public class PetCount {
static class PetCounter extends HashMap<String,Integer> {
public void count(String type) {
Integer quantity = get(type);
if(quantity == null)
put(type, 1);
else
put(type, quantity + 1);
}
}
public static void countPets(PetCreator creator) {
PetCounter counter= new PetCounter();
for(Pet pet : creator.createArray(20)) {
// List each individual pet:
System.out.println(pet.getClass().getSimpleName() + " ");
if(pet instanceof Pet)
counter.count("Pet");
if(pet instanceof Dog)
counter.count("Dog");
if(pet instanceof Mutt)
counter.count("Mutt");
if(pet instanceof Pug)
counter.count("Pug");
if(pet instanceof Cat)
counter.count("Cat");
if(pet instanceof Manx)
counter.count("EgyptianMau");
if(pet instanceof Manx)
counter.count("Manx");
if(pet instanceof Manx)
counter.count("Cymric");
if(pet instanceof Rodent)
counter.count("Rodent");
if(pet instanceof Rat)
counter.count("Rat");
if(pet instanceof Mouse)
counter.count("Mouse");
if(pet instanceof Hamster)
counter.count("Hamster");
}
// Show the counts:
System.out.println();
System.out.println(counter);
}
public static void main(String[] args) {
countPets(new ForNameCreator());
}
}

// output:
// Rat
// Manx
// Cymric
// Mutt
// Pug
// Cymric
// Pug
// Manx
// Cymric
// Rat
// EgyptianMau
// Hamster
// EgyptianMau
// Mutt
// Mutt
// Cymric
// Mouse
// Pug
// Mouse
// Cymric

// {EgyptianMau=7, Pug=3, Rat=2, Cymric=7, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}

提示:当你的程序中充满了大量的 instanceof 判断,那么你的成程序很可能有缺陷

Using class literals

如果我们使用 PetCreator 的类字面量(.class)来重构它的实现,省去了 try-catch block, 而且表达的语义更清晰, 程序会更明了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class LiteralPetCreator extends PetCreator {
// No try block needed.
@SuppressWarnings("unchecked")
public static final List<Class<? extends Pet>> allTypes =
Collections.unmodifiableList(Arrays.asList(
Pet.class, Dog.class, Cat.class, Rodent.class,
Mutt.class, Pug.class, EgyptianMau.class, Manx.class,
Cymric.class, Rat.class, Mouse.class, Hamster.class));
// Types for random creation:
private static final List<Class<? extends Pet>> types =
allTypes.subList(allTypes.indexOf(Mutt.class), allTypes.size());

public List<Class<? extends Pet>> types() {
return types;
}

public static void main(String[] args) {
System.out.println(types);
}
}
// output:
// [class review.Mutt, class review.Pug, class review.EgyptianMau, class review.Manx, class review.Cymric, class review.Rat, class review.Mouse, class review.Hamster]

新建一个 Pets 工具类用来创建创建 pet, 书上管这种方式叫做 Facade 模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.ArrayList;

public class Pets {
public static final PetCreator creator =
new LiteralPetCreator();

public static Pet randomPet() {
return creator.randomPet();
}

public static Pet[] createArray(int size) {
return creator.createArray(size);
}

public static ArrayList<Pet> arrayList(int size) {
return creator.arrayList(size);
}
}

This also provides indirection to randomPet( ), createArray( ) and arrayList( ).
Because PetCount.countPets( ) takes a PetCreator argument, we can easily test the
LiteralPetCreator (via the above Facade):

1
2
3
4
5
public class PetCount2 {
public static void main(String[] args) {
PetCount.countPets(Pets.creator);
}
}

A dynamic instanceof

新建一个 PetCount 继承自 LinedHashMap, 这种从现成的 Map 对象继承的做法我以前到是没怎么见过,也没怎么用过,长见识了,而且用起来挺方便的。

Class.isInstance() 效果上和 instanceof 等价,继承 map 之后通过调用 entrySet() 拿到所有的 entry, 然后通过范型遍历,节省了很多代码,和之前那一串 forName 相比干净了很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class PetCount3 {
static class PetCounter extends LinkedHashMap<Class<? extends Pet>,Integer> {
public PetCounter() {
super(LiteralPetCreator.allTypes.stream().collect(Collectors.toMap(Function.identity(), x->0)));
}
public void count(Pet pet) {
// Class.isInstance() eliminates instanceof:
for(Map.Entry<Class<? extends Pet>, Integer> pair : entrySet())
if(pair.getKey().isInstance(pet))
put(pair.getKey(), pair.getValue() + 1);
}
public String toString() {
StringBuilder result = new StringBuilder("{");
for(Map.Entry<Class<? extends Pet>,Integer> pair
: entrySet()) {
result.append(pair.getKey().getSimpleName());
result.append("=");
result.append(pair.getValue());
result.append(", ");
}
result.delete(result.length()-2, result.length());
result.append("}");
return result.toString();
}
}
public static void main(String[] args) {
PetCounter petCount = new PetCounter();
for(Pet pet : Pets.createArray(20)) {
System.out.println(pet.getClass().getSimpleName() + " ");
petCount.count(pet);
}
System.out.println();
System.out.println(petCount);
}
}

Counting recursively

除了 Class.isInstance() 还可以使用 isAssignFrom 来做类型判断,示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class TypeCounter extends HashMap<Class<?>,Integer> {
private Class<?> baseType;
public TypeCounter(Class<?> baseType) {
this.baseType = baseType;
}
public void count(Object obj) {
Class<?> type = obj.getClass();
if(!baseType.isAssignableFrom(type))
throw new RuntimeException(obj + " incorrect type: "
+ type + ", should be type or subtype of "
+ baseType);
countClass(type);
}
private void countClass(Class<?> type) {
Integer quantity = get(type);
put(type, quantity == null ? 1 : quantity + 1);
Class<?> superClass = type.getSuperclass();
if(superClass != null &&
baseType.isAssignableFrom(superClass))
countClass(superClass);
}
public String toString() {
StringBuilder result = new StringBuilder("{");
for(Map.Entry<Class<?>,Integer> pair : entrySet()) {
result.append(pair.getKey().getSimpleName());
result.append("=");
result.append(pair.getValue());
result.append(", ");
}
result.delete(result.length()-2, result.length());
result.append("}");
return result.toString();
}
}

public class PetCount4 {
public static void main(String[] args) {
TypeCounter counter = new TypeCounter(Pet.class);
for(Pet pet : Pets.createArray(20)) {
System.out.println(pet.getClass().getSimpleName() + " ");
counter.count(pet);
}
System.out.println();
System.out.println(counter);
}
}

这几个示例其实就说明了一个点,使用 isInstance() 和 isAssignFrom() 可以绕开 forName 使得代码整洁,好看很多。整洁好看也就意味着更少的维护成本。

Registered factories

上面的例子有一个问题,就是每次你新建一个 Pets 的子类,你必须去 LiteralPetCreator 中将这个新建的 Class 手动添加进去,未免有点累赘。这里有两种解决方案,一种就是新写一个工具类遍历代码,找到 Pets 的子类统一处理,另一种方案就是将所有的类放到一个地方统一管理,基类就是很好的一个地方,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

interface Factory<T> { T create(); }

class Part {
public String toString() {
return getClass().getSimpleName();
}

static List<Factory<? extends Part>> partFactories =
new ArrayList<>();

static {
// Collections.addAll() gives an "unchecked generic
// array creation ... for varargs parameter" warning.
partFactories.add(new FuelFilter.Factory());
partFactories.add(new AirFilter.Factory());
partFactories.add(new CabinAirFilter.Factory());
partFactories.add(new OilFilter.Factory());
partFactories.add(new FanBelt.Factory());
partFactories.add(new PowerSteeringBelt.Factory());
partFactories.add(new GeneratorBelt.Factory());
}

private static Random rand = new Random(47);

public static Part createRandom() {
int n = rand.nextInt(partFactories.size());
return partFactories.get(n).create();
}
}

class Filter extends Part {
}

class FuelFilter extends Filter {
// Create a Class Factory for each specific type:
public static class Factory implements review.Factory<FuelFilter> {
public FuelFilter create() {
return new FuelFilter();
}
}
}

class AirFilter extends Filter {
public static class Factory
implements review.Factory<AirFilter> {
public AirFilter create() {
return new AirFilter();
}
}
}

class CabinAirFilter extends Filter {
public static class Factory
implements review.Factory<CabinAirFilter> {
public CabinAirFilter create() {
return new CabinAirFilter();
}
}
}

class OilFilter extends Filter {
public static class Factory
implements review.Factory<OilFilter> {
public OilFilter create() {
return new OilFilter();
}
}
}

class Belt extends Part {
}

class FanBelt extends Belt {
public static class Factory
implements review.Factory<FanBelt> {
public FanBelt create() {
return new FanBelt();
}
}
}

class GeneratorBelt extends Belt {
public static class Factory
implements review.Factory<GeneratorBelt> {
public GeneratorBelt create() {
return new GeneratorBelt();
}
}
}

class PowerSteeringBelt extends Belt {
public static class Factory
implements review.Factory<PowerSteeringBelt> {
public PowerSteeringBelt create() {
return new PowerSteeringBelt();
}
}
}

public class RegisteredFactories {
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
System.out.println(Part.createRandom());
}
}

// output:
// GeneratorBelt
// CabinAirFilter
// GeneratorBelt
// AirFilter
// PowerSteeringBelt
// CabinAirFilter
// FuelFilter
// PowerSteeringBelt
// PowerSteeringBelt
// FuelFilter

instanceof vs. Class equivalence

遇到 Iterator 相关的问题,重新看一遍 Holding Your Objects 章节

想要解决的问题:

  • 这个章节具体讲了什么东西
  • iterable/iterator/forEach 之前的关系和区别

前述

如果程序中只包含长度一定的,生命周期可知的对象,那这个程序确实足够简单了。

Array 是持有对象的最高效的方式,但是长度限制死了。

Java 中使用 ‘collection classes’ 来解决可变长容器的问题,因为 Collection 在 Java 中已经有对应的类了,所以这个概念又被叫做容器(Container)。

这章只是介绍基本用法,后面有一节 Containers in Depth 会深入介绍

Generics and type-safe containers

Basic concepts

Adding groups of elements

Printing containers

List

有序的一个数据序列,在 Collection 的基础上添加了一些方法来达到在 list 中间插入,删除元素的效果。

  • ArrayList: 注重随机读写,但是插入删除性能比较慢
  • LinkedList: 注重顺序读写,插入删除很快,随机读写很慢,功能上比 ArrayList 多

Iterator

容器设计出来的主要作用:持有对象

Iteractor 是集合中的一个轻量级对象,可以很方便的在容器类之间做兼容,常见用法:

  1. 用 Collection 对象调用 iterator() 方法拿到 Iterator 对象,它已经可以为你返回第一个对象了。
  2. 调用 next() 返回下一个对象
  3. 查看是否有跟多的对象过 hasNext()
  4. 调用 remove() 删除之前的使用的对象

这个章节虽然简单,但是例子都是在 Type Information 里面的,得先看这个,不然看的没什么头绪。

最近看 Code 经常看到有使用 final 参数的例子,但是对这点没有系统的认识,重新认真读一遍 Think in Java 4th 相关章节并做笔记。

想要解决的问题:

  • local inner class 中如果用到方法中的参数,为什么要用 final 修饰?

A1: java 编译器在实现 Q1 中描述的问题时,用的是值拷贝,而不是 reference 拷贝,为了防止内外值不一致,只能强制用 final 把它定为一个常量,不改变他的值

The final keyword

Java 里面 final 这个关键字的含义会根据上下文不同而有所区别,但是大体上来说,他都会表达出一个 ‘不允许改变’ 的含义。你会出于两种目的阻止他改变,一种是设计上另一种是效率上。这两种目的很不一样,所以可能存在误用的情况。

接下来我们会例举三种 final 的使用场景:data, method, class

final data

在两种情况下你以将变量声明为常量:

  1. 编译期常量,不能被改变
  2. 在运行时赋值并且不能被改变

编译时常量有一个好处是在编译期间就将常量相关的运算做了,可以节省运行时的计算时间。这种情况下,对应的常量类型必须是 final 修饰的 primitive 类型,声明变量时就得赋值。

static + final 表明系统中只有一块内存空间存储相应的值。这种变量是有命名规范的,全部大些,中间用下划线分隔。

通过使用 final 修饰 class 表明这个类的 reference 是一个常量。

示例说明:

声明一个 Value class 用做演示 final 修饰对象情况的素材。

valueOne: 演示 final 修饰的基本数据类型不能改变值

VALUE_TWO/VALUE_THREE: 演示 static final 的常见用法和命名规范

i4/INT_5: final 修饰的变量可以通过表达式赋值,不一定需要直接赋值

v1/v2/VAL_3: final 修饰的对象 reference 不能改,但对应的对象可以改变

a: 数组也是一种对象,符合上一条行为规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.Random;

class Value {
int i; // Package access

public Value(int i) {
this.i = i;
}
}

public class FinalData {
private static Random rand = new Random(47);
private String id;

public FinalData(String id) {
this.id = id;
}

// Can be compile-time constants:
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// Typical public constant:
public static final int VALUE_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
// Arrays:
private final int[] a = {1, 2, 3, 4, 5, 6};

public String toString() {
return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
}

public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
//! fd1.valueOne++; // Error: can’t change value
fd1.v2.i++; // Object isn’t constant!
fd1.v1 = new Value(9); // OK -- not final
for (int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn’t constant!
//! fd1.v2 = new Value(0); // Error: Can’t
//! fd1.VAL_3 = new Value(1); // change reference
//! fd1.a = new int[3];
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
System.out.println(fd1);
System.out.println(fd2);
}
}

public so they’re usable outside the package, static to emphasize that there’s only one, and final to say that it’s a constant.

public: 包外可访问;static:强调只有一份空间;final:常量

Blank finals

变量声明为 final 类型但是没有给初始值的情况叫做 Blank finals。但是在这个变量使用前,它必须被初始化。

归结为两种情况为 final 变量赋值,一种就是声明时赋值,另一种是构造函数内赋值。不做的话会有编译错误。提供第二种赋值方式之后,对于同一个变量,每个类都可以有自己不同 final 变量值了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Poppet {
private int i;

Poppet(int ii) {
i = ii;
}
}

public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference

// Blank finals MUST be initialized in the constructor:
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}

public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}

public static void main(String[] args) {
new BlankFinal();
new BlankFinal(47);
}
}

final arguments

你还可以在方法的参数列表中,将变量类型指定为 final,表示在方法体内你不能改变参数的 reference。

示例说明:

with/without: 表明 final 修饰的对象参数 reference 不能被改变

f()/g(): 表明 final 修饰的基本数据类型值不能被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class Gizmo {
public void spin() {
}
}

public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
}

void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}

// void f(final int i) { i++; } // Can’t change
// You can only read from a final primitive:
int g(final int i) {
return i + 1;
}

public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
}

final methods

fianl 修饰 method 有两种作用,一种是表达了你不想被修饰的方法在子类中被重写而改变语义;另一种是提升执行效率。但是第二种功能在 Java 5/6 时已经包含在 JVM 优化中了,所以现在只推荐在第一种意图是使用该语法。

1
2
3
4
5
6
7
class MyFinal {
public final void method01(){};
}

public class MyFinalTest extends MyFinal{
// ! public final void method01(){}; // compile error
}

final and private

类中的所有 private 方法其实都是默认有 final 修饰的,只不过你显示的加了也没什么额外的作用。

这里说的默认在编译的字节码上并不会显示的表现出来,下面的示例中 testMethod02 和 testMethod03 意义上是一样的,但是编译的自己码还是不同的

1
2
3
4
5
6
7
8
9
public class AccessIdentifierTest {
public void testMethod01(){};
private void testMethod02(){};
private final void testMethod03(){};

public static void main(String[] args) {
AccessIdentifierTest test = new AccessIdentifierTest();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// access flags 0x2
private testMethod02()V
L0
LINENUMBER 5 L0
RETURN
L1
LOCALVARIABLE this Linnerclass/AccessIdentifierTest; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1

// access flags 0x12
private final testMethod03()V
L0
LINENUMBER 6 L0
RETURN
L1
LOCALVARIABLE this Linnerclass/AccessIdentifierTest; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1

private 方法代表的意思不就是外部不能访问,当然也不能修改这个方法吗,没毛病。

示例说明:

下面的例子中,我们在基类中声明了两个方法 f()/g() 分别显示和隐示的加上 final 关键字。虽然你可以在它的子类中重写这个方法,但是只有在最末端的子类中可以调用,且调用的还是子类自己的实现。

如果你在子类的实现上加上 Override 标签,还会有编译错误,因为 private 方法自带 final, 表明的含义就是不能被重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class WithFinals {
// Identical to "private" alone:
private final void f() {
System.out.println("WithFinals.f()");
}

// Also automatically "final":
private void g() {
System.out.println("WithFinals.g()");
}
}

class OverridingPrivate extends WithFinals {
private final void f() {
System.out.println("OverridingPrivate.f()");
}

private void g() {
System.out.println("OverridingPrivate.g()");
}
}

class OverridingPrivate2 extends OverridingPrivate {
public final void f() {
System.out.println("OverridingPrivate2.f()");
}

public void g() {
System.out.println("OverridingPrivate2.g()");
}
}

public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 = new OverridingPrivate2();
op2.f();
op2.g();
// You can upcast:
OverridingPrivate op = op2;
// But you can’t call the methods:
//! op.f();
//! op.g();
// Same here:
WithFinals wf = op2;
//! wf.f();
//! wf.g();
}
}
// output:
// OverridingPrivate2.f()
// OverridingPrivate2.g()

If a method is private, it isn’t part of the base-class interface. It is just some code that’s hidden away inside the class, and it just happens to have that name.

私有方法并不是基类的一部分,它是该类中的隐藏代码,只不过恰巧有了名字。

final class

final 修饰的 class 表明,不管出于什么目的,你不想你的这个 class 被继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SmallBrain {
}

final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();

void f() {
}
}

//! class Further extends Dinosaur {}
// error: Cannot extend final class ‘Dinosaur’
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
}

final class 的 field 可以不是 final 的,但是 final class 里面的 method 都隐示为 final method。因为 final class 就是为了防止被继承,都不被继承了,对应的方法都不能重写也是合理的。

和前面的章节一样,这里的默认 final 也是语义上的,并不会在字节码中体现出来。

final caution

例举了一些老的 Java lib 实现 Vector 和 Hashtable 说明,使用 final 修饰方法的时候要谨慎,你完全不知道其他人会怎样使用你的代码。

简单泛型

多态是一种面向对象思想的泛化机制。你将方法参数设置为基类,这样方法就能接受任何派生类作为参数。不过拘泥于单一继承体系太过局限,如果以接口而不是类作为参数,限制就宽松多了。但即便是接口也有诸多限制,一旦制定了接口,就要求你的代码使用特定接口。Java 5 引入了泛型,实现了参数化类型的效果。泛型这个术语的含义是适用于很多类,初衷是通过结偶类或方法与所使用的类型之间的约束,使得类或方法具备最宽泛的表达力。

泛型出现的最主要动机之一是为了创建集合类。一个集合中存储多种不同类型的对象的情况很少见,通常我们只会用集合存储同一种类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。

泛型类基本语法,通过类型参数限定使用的类:

1
2
3
4
5
6
// class 类名称 <泛型标识:可以是任意标识符>{
// private 泛型标识 var;
// .....
// }
// }
public class ArrayList<E> extends AbstractList<E> {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Holder3<T> {
private T a;

public Holder3(T a) {
this.a = a;
}

public void set(T a) {
this.a = a;
}

public T get() {
return a;
}

public static void main(String[] args) {
Holder3<Automobile> h3 = new Holder3<Automobile>(new Automobile());
Automobile a = h3.get(); // No cast needed
// h3.set("Not an Automobile"); // Error
// h3.set(1); // Error
}
}

现在你在创建 Holder 的时候必须在尖括号中指定你想要的类型,当你从容器中取值的时候,jvm 会自动帮你完成类型转化。

A tuple library

Java 语法限制一个 method 只能返回一个值,那么如果你想返回多个,怎么办? 这种情况下我们可以定一个对象里面持有多个值,并且只读不能写。这种对象有个名字,叫做 Data Transfer Object(DTO)/Message 也叫元组

元组长度可以是任意的,但是类型必须是确定的,这里我们可以用泛型绕过去,对于多个元素的问题,我们可以创建不同的元组来做兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TwoTuple<A, B> {
public final A first;
public final B second;

public TwoTuple(A a, B b) {
first = a;
second = b;
}

public String toString() {
return "(" + first + ", " + second + ")";
}
}

精髓:通过 final 关键字 代替 getXXX method, 代码更简单明了,如果你想要一个三个变量的元组,你可以继承这个 class

1
2
3
4
5
6
7
8
9
10
11
12
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
public final C third;

public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}

public String toString() {
return "(" + first + ", " + second + ", " + third + ")";
}
}

更多变量的元组以此类推, 测试如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Amphibian {}

public class TupleTest {
static TwoTuple<String, Integer> f() {
// Autoboxing converts the int to Integer:
return new TwoTuple<String, Integer>("hi", 47);
}

static ThreeTuple<Amphibian, String, Integer> g() {
return new ThreeTuple<Amphibian, String, Integer>(
new Amphibian(), "hi", 47);
}

public static void main(String[] args) {
TwoTuple<String, Integer> ttsi = f();
System.out.println(ttsi);
// ttsi.first = "there"; // Compile error: final
System.out.println(g());
}
}
// output:
// (hi, 47)
// (generic.Amphibian@7c53a9eb, hi, 47)

通过泛型我们可以很轻松的指定 tuple 中成员的类型,通过 new 来新建对象还是略显繁琐,后面有改进型。

A stack class

这里回顾了一下 Holding Your Objects 章节的 LinkedList 例子,然并卵我并没有看过 ╮( ̄▽ ̄””)╭

下面是我们自己实现的带有 linked 存储机制的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class LinkedStack<T> {
private static class Node<U> {
U item;
Node<U> next;

Node() {
item = null;
next = null;
}

Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}

boolean end() {
return item == null && next == null;
}
}

private Node<T> top = new Node<>(); // End sentinel

public void push(T item) {
top = new Node<>(item, top);
}

public T pop() {
T result = top.item;
if (!top.end())
top = top.next;
return result;
}

public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<>();
for (String s : "Phasers on stun!".split(" "))
lss.push(s);
String s;
while ((s = lss.pop()) != null)
System.out.println(s);
}
}
// output
// stun!
// on
// Phasers

这里通过内部静态类创建了一个 Node class 代表一个节点。这个带泛型的 Node 节点是一种很经典的数据结构,将数据通过泛型封装,结构体现在 Node 中。

LinkedStack 初始化时会声明一个内容为空的节点,在后续的 pop 方法中,通过判断节点的这两个内容是不是空来断定容器是否为空。

RandomList

设计一个数据结构,每次调用 list 的 select 方法的时候会随机返回一个元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RandomList<T> {
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(47);

public void add(T item) {
storage.add(item);
}

public T select() {
return storage.get(rand.nextInt(storage.size()));
}

public static void main(String[] args) {
RandomList<String> rs = new RandomList<String>();
for (String s : ("The quick brown fox jumped over " +
"the lazy brown dog").split(" "))
rs.add(s);
for (int i = 0; i < 11; i++)
System.out.print(rs.select() + " ");
}
}

// output
// brown over fox quick quick dog brown The brown lazy brown

数据结构很简单,随机性由 Random 对象提供 random.nextInt(x) 可以给出 0-x 返回内的整数。RandomList 里面新建一个 ArrayList 作为数据存储容器。

Generic interfaces

接口也可以由泛型配置。Generator(生成器) 是一种特殊的工厂方法,他可以在不接受任何参数的情况下,创建你需要的对象。这里我们为产生对象的方法取名为 next()

示例说明:

  1. 声明一个 Generator 接口带有泛型参数,只有一个方法 next 返回类型为泛型
  2. 创建产品基类 Coffee 并创建对应的实体类
  3. 创建生成器实体类 CoffeeGenerator,他实现了 Generator 接口和 Iterable 接口,前者用于一次生成一个的模式,后者用于一次性生成多个的模式,有了 Iterable 就可以支持 foreach 语法了

这里面唯一我想不到的是他通过 Class.newInstance() 直接生成对象的,就感觉很突然,很直球 (´Д` )

而且他在实现里使用 Iterable + 内部类实现 Iterator 的方式,我对这个也听陌生的,虽然知道有这种用法。。。感觉又可以开坑了 (; ̄ェ ̄)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public interface Generator<T> {T next();}

public class Coffee {
private static long counter = 0;
private final long id = counter++;

public String toString() {
return getClass().getSimpleName() + " " + id;
}
}

class Latte extends Coffee {}

class Mocha extends Coffee {}

class Cappuccino extends Coffee {}

class Americano extends Coffee {}

class Breve extends Coffee {}

public class CoffeeGenerator
implements Generator<Coffee>, Iterable<Coffee> {
private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class,};
private static Random rand = new Random(47);

public CoffeeGenerator() {
}

// For iteration:
private int size = 0;

public CoffeeGenerator(int sz) {
size = sz;
}

public Coffee next() {
try {
return (Coffee)types[rand.nextInt(types.length)].newInstance();
// Report programmer errors at run time:
} catch (Exception e) {
throw new RuntimeException(e);
}
}

class CoffeeIterator implements Iterator<Coffee> {
int count = size;

public boolean hasNext() {
return count > 0;
}

public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}

public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
}

public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}

public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator();
for (int i = 0; i < 5; i++)
System.out.println(gen.next());
for (Coffee c : new CoffeeGenerator(5))
System.out.println(c);
}
}
// output
// Americano 0
// Latte 1
// Americano 2
// Mocha 3
// Mocha 4
// Breve 5
// Americano 6
// Latte 7
// Cappuccino 8
// Cappuccino 9

下面是使用泛型接口实现斐波那契额的例子

算法这一块,不是我吹逼,我真的太弱了 (; ̄ェ ̄) 老是忘记

这里 class 内部持有一个 count 变量,每次调用 next() 方法,都会使得 count+1, 第 n 次调用就相当于打印 fib(n) 的值,fib 是一个基本的递归函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Fibonacci implements Generator<Integer> {
private int count = 0;

public Integer next() {
return fib(count++);
}

private int fib(int n) {
if (n < 2) return 1;
return fib(n - 2) + fib(n - 1);
}

public static void main(String[] args) {
Fibonacci gen = new Fibonacci();
for (int i = 0; i < 18; i++)
System.out.print(gen.next() + " ");
}
}
// output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584

泛型参数不支持基本数据类型,必须是包装型的。

下面我们用 Iterable 接口 + adapter 模式扩展一下上面的斐波那契数列,说实话,这个 adapter 模式和我印象中的不一样,又得复习一下对应的那块设计模式 code 了 (; ̄ェ ̄)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class IterableFibonacci extends Fibonacci implements Iterable<Integer> {
private int n;

public IterableFibonacci(int count) {
n = count;
}

@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
public boolean hasNext() {
return n > 0;
}

public Integer next() {
n--;
return IterableFibonacci.this.next();
}

public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}

public static void main(String[] args) {
for (int i : new IterableFibonacci(18))
System.out.print(i + " ");
}
}
// output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584

泛型方法

类本身可能是泛型的,也可能不是,不过这与她的方法是否是泛型的并没有关系。泛型方法独立于类而改变方法,尽可能使用泛型方法,通常将单个方法泛型化要比将整个类泛型化更清晰易懂。

泛型方法定义:将泛型参数列表放置在返回值之前

泛型擦除

这个擦除好像只和集合有关系。

在泛型代码的内部,无法获取任何有关泛型参数类型的信息。你可以知道泛型参数标识符和泛型边界的信息,但无法知道实际类型参数从而创建特定的实例。

泛型只有在类型参数比某个具体类型更加泛化,代码能跨多个类工作时才有用。类型参数和他们在有用的泛型代码中的应用,通常比简单的类替换更加复杂。但不能因此认为使用 形式就是有缺陷的。比如某个类有一个返回 T 的方法,此时泛型就帮你检测了返回值的类型。

Java 的泛型不是一种语言特性,而是一种妥协。为了提供迁移兼容性。

泛型类型只有在静态类型监测期间才出现,此后程序中的所有泛型类型都将被擦出,替换为他们的非泛型上界。

泛型的所有动作都发生在边界处,即入参的编译器检查和对返回值的转型。

补偿擦除

为了创建泛型实例,书中的实例通过在构造函数中传入类型的 class 来解决这个问题, 但只对对象有无参构造是有用。这样的话一些错误不能在编译时捕获,语言创建团队建议使用显示工厂(Suplier)并约束类型

我们无法创建泛型数组,通用解决办法是使用 ArrayList 代替

这里很多关于数组类型的讲解,概念感觉很模糊,什么类型不能改变之类的,我觉得,这些定义作者可能在之前的数组篇章里有介绍,有机会看一看。

边界

边界潜在更重要的效果是我们可以在绑定的类型中调用方法

通配符

flist 现在是 List<? extends Fruit>, 可以读作 ‘一个具有任何从Fruit集成的类型的列表’,然而这并不意味着这个List将持有任何类型的Fruit。通配符引用的是明确的类型,因此它意味着 ‘某种flist引用没有指定的具体类型’

1
2
3
4
5
6
// 表示 任何Fruit子类的list
List<? extends Fruit> flist = new ArrayList<Apple>();
// 不能确定具体子类,所以不能 add, null 除外
// flist.add(new Apple());
// 可以 get
Fruit f = flist.get(0);

逆变

<? super MyClass> 的这种表现形式

1
2
3
4
5
6
// 表示 任何Fruit超类的list
List<? super Fruit> flist = new ArrayList<Fruit>();
// 所以至少是个 Fruit,不确定具体类型,不能 get, 可以 add
flist.add(new Apple());
// 不可以 get
// Fruit f = flist.get(0);

这个和上面的 extend 贼TM绕

无界通配符

读起来很懵逼,找不到重点

使用确切类型来代替通配符类型的好处是,可以用泛型参数来做更多的事情。使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况的权衡利弊,找到更适合你的需求的方法。

捕获转换

TBD

问题

基本类型不能作为类型参数

类似 List 的写法是不允许的。解决办法,可以使用包装类型,自动装箱机制将自动实现类型转化

实现参数化接口

1
2
3
4
5
6
interface Payable<T> {}

class Employee2 implements Payable<Employee2> {}

// 编译失败
class Hourly extends Employee2 implements Payable<Hourly> {}

这样的声明会有编译错误,第三局声明中,由于类型擦出,导致重复声明

转型和警告

重载

由于擦除机制,下面两条语句语意是相同的,所以不能编译,通过修改方法名可以修复这个问题

1
2
3
4
public class UseList<W, T> {
void f(List<T> v) {}
void f(List<W> v) {}
}

基类劫持接口

比如常见的 Comparable 接口,我们如果想将泛型范围限制到更小的范围,但是 Cat 的声明会有编译错误

1
2
3
4
5
6
7
8
9
10
public class ComparablePet implements Comparable<ComparablePet> {
@Override
public int compareTo(ComparablePet o) {
return 0;
}
}

// 由于泛型指定的类型不同,不能编译。
//class Cat extends ComparablePet implements Comparable<Cat> {
//}

自限定的类型

自限定指的是 class SelfBounded<T extends SelfBounded<T>> { 这样的定义,强调的是当 extends 关键字用于边界

古怪的循环泛型

自限定

他可以保证类型参数必须与被自定义的类相同。如果使用自限定,这个累所用的类型参数将与使用这个参数的类具有相同的基本类型。这回强制要求使用这个累的每个人都要遵循这种形式。

自限定还可以用于泛型方法。

参数协变

对缺乏潜在类型机制的补偿

反射

这个我之前在自己代码中就用过,就是在方法调用中通过 method name 来调用

将一个方法应用于序列

反射将类型监测移到了运行时,但是我们通常更希望在编译期就实现类型检测,更早的发现问题。

看到网上有一段代码通过 CSS 把 ul+li 块渲染成目录树结构,很赞,加入收集。源代码链接: bootsnipp

PS: 这个 post 最好是在这段代码下面嵌入一个页面显示效果,但是目前没时间做这方面的 re-search,以后如果有很多 html 示例的化可以考虑一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">

<title>My Tree</title>
<link href="css/tree3.css" rel="stylesheet" id="bootstrap-css">
</head>

<body>
<ul id="tree1" class="tree">
<li>Node1
<ul>
<li>Node11</li>
<li>Node12</li>
<li>Node13</li>
</ul>
</li>
<li>Node2
<ul>
<li>Company Maintenance</li>
<li>Employee
<ul>
<li>Reports
<ul>
<li>Report1</li>
<li>Report2</li>
<li>Report3</li>
</ul>
</li>
<li>Employee Maint</li>
</ul>
</li>
<li>Human Resources</li>
</ul>
</li>
</ul>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
ul {
margin: 0;
padding: 0;
list-style: none;
}

.tree ul {
margin-left: 1em;
/* 画一条最外层 ul 边框的辅助线 */
/* border: 1px solid red; */
position: relative
}

/* 树状结构竖线部分 */
.tree ul:before {
/* 伪类选择器,会选中 ul 的第一个元素 */
content: "";
display: block;
width: 0;
position: absolute;
top: 0;
bottom: 0;
left: 0;
border-left: 1px solid;
}

.tree li {
margin: 0;
padding: 0 1em;
line-height: 2em;
color: #369;
font-weight: 700;
position: relative;
/* font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; */
}

/* 树状结构横线部分 */
.tree ul li:before {
content: "";
display: block;
width: 10px;
height: 0;
border-top: 1px solid;
margin-top: -1px;
position: absolute;
top: 1em;
left: 0
}

/* 覆盖最后一个节点多余的半截竖线 */
.tree ul li:last-child:before {
background: #fff;
height: auto;
top: 1em;
bottom: 0;
}

最近在看 Spring Core 文档, 刚好遇到一个 Inner Class 相关的问题, 回忆一下突然发现对他基本没有什么很深入的理解, 特此重新阅读一下 Think in Java 4th 相关章节看看能不能有什么特别的收获.

想要解决的问题:

  • 什么是内部类 - 将 class 定义嵌入另一个 class 内部, 我们就得到了一个内部类
  • 静态/非静态内部类有什么区别 - 前者可以单独使用, 后者需要持有外部类(enclosing class)的实例才能使用
  • 内部类有什么用 - 更好的闭包
  • 字节码层面是怎么表现的 - class 分别编译, 外层会持有内层的 class reference

Intro

Java 语法是支持在一个 class 内部再放入另一个 class 的定义的, 这种做法叫做 内部类(Inner Class).

Inner class 是一个很有价值的功能, 他让你可以把两个逻辑上共存的 class 放到一起, 并让他们之间有可见性控制的特性.

Creating inner classes

如果想要创建一个内部类你只需要直接将内部类的定义放到外部类里面就行了. 外部类一般会有一些方法用来返回内部类引用, 比如下面例子中的 to()contents() 方法. 如果是 非静态 内部类, 你需要先新建外部类, 然后才能创建内部类. 如果是 静态 内部类, 则你可以直接通过 class 引用创建内部类对象.

示例说明:

创建静态/非静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 类结构
public class OuterClass {
public static class NestedClass {}
public class InnerClass {}
}

public class TestInner {
public static void main(String[] args) {
// 静态内部类的情况
OuterClass.NestedClass nestedClass = new OuterClass.NestedClass();

// 非静态内部类的情况
OuterClass outer = new OuterClass();
OuterClass.InnerClass innerClass = outer.new InnerClass();

// 或者合二为一
OuterClass.InnerClass innerClass2 = new OuterClass().new InnerClass();
}
}

示例说明:

parcel = 包裹, 该示例以包裹运输为场景, 用包裹数量, 目的地等属性展示了内部类的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Parcel2 {
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 Destination to(String s) {
return new Destination(s);
}

public Contents contents() {
return new Contents();
}

public void ship(String dest) {
Contents c = contents();
Destination d = to(dest);
System.out.println(d.readLabel());
}

public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tasmania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("Borneo");
}
}

// output: Tasmania

官方给的例子有点不太好记忆, 本人更倾向于简单的直接 Outer/Inner 这中名字来命名 class

1
2
3
4
5
6
7
8
9
10
11
public class Outer {
class Inner {}

public Inner getInner() {
return new Inner();
}

public static void main(String[] args) {
Outer.Inner inner = new Outer().getInner();
}
}

内部类最显著的特点: Inner class 创建的时候会持有一个外部类的引用, 概念上类似指针, 这使得他能没有限制的访问外部类成员变量和方法.

示例说明:

我们声明了一个接口 Selector, 它定义了三个方法, 表示类似游标的能力, 这个接口可以让我们

  1. 得到当前量
  2. 判断是不是最后一个元素
  3. 移动到下一个元素

Sequence(次序) 是一个可变长的数组容器, 实现了构造函数和 add() 方法. 通过构造函数我们可以指定他的容量, 通过 add() 方法可以向容器中添加元素.

Sequence 中我们声明了一个内部类 SequenceSelector 实现了 Selector 接口. 通过这种组合方式, 我们把删选能力和容器分隔开, 避免让 Sequence 直接实现 Selector 这种在语义上有歧义的做法.

重点: SequenceSelector 可以访问 Sequence 的私有变量而不受限制

PS: 看了上面的这些描述, 这绝逼就是 Iterator 的概念, Iterator Pattern 章节的内容还历历在目 (●°u°●)​ 」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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();
}
}
}

每次我们调用 selector() 方法时都会产生一个内部类的实体, 而且各个实体之间是相互独立的, 很赞.

自己写的内部类持有外部引用的例子, 光这个点的话, 官方的例子有点累赘, 不过这种设计思路很喜欢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Outer {
private String name = "outer";
class Inner {
public String getOuterName() {
return Outer.this.name;
}
}

public Inner getInner() {
return new Inner();
}

public static void main(String[] args) {
Outer.Inner inner = new Outer().getInner();
System.out.println(inner.getOuterName());
}
}
// output: outer

Using .this and .new

内部类中, 你可以使用 外部类.this 的方式得到外部类的引用. 下面的例子中, inner class 的 outer() 通过 DotThis.this 返回了外部类的引用, 并调用 f() 打印结果.

这个例子中的调用链有点别扭, 但是主旨是为了说明我们可以通过 outer.this 这个关键字拿到外部类的引用, 仅此而已.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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()

如果你想创建内部类, 那么你可以通过 外部类实例.new 的形式创建.

创建时你不需要为 Inner() 指定前缀 class, 这个挺方便的. 本来还以为需要用 dn.new DotNew.Inner(); 的语法, 后来试过发现编译会报错.

1
2
3
4
5
6
7
8
public class DotNew {
public class Inner {}

public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}

PS: [Attention] 书上将非静态内部类叫做 inner class, 静态内部类叫做 nested class 或 static inner class, 有点意思

想要创建内部类你必须要先创建外部类, 这是因为创建内部类需要外部类的引用, 这更像是一个先决条件. 如果想脱钩, 可以使用 nested class(静态内部类).

Inner classes and upcasting

标题可以理解为 内部类和强转. Inner class 和接口结合, 可以达到隐藏自己实现的目的, 这个特性很厉害.

示例说明:

下面的例子里 Parcel4 声明了两个内部类, 分别实现 Destination 和 Contents 接口, 而且类修饰符为 private 和 protect 限制的包外的访问.

然后按照常用套路, 为这两个内部类创建了方法(destination(str) 和 contents())返回对应的实现. 但是由于方法返回的是接口类型的, 所以包外压根就不知道他的实现细节. 这就很 imba, 起到了很强的隔离效果 (; ̄ェ ̄)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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();
}
}

interface Destination {
String readLabel();
}

interface Contents {
int value();
}

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();
}
}

Exc8: 确认下外部类是否能访问内部类变量? Determine whether an outer class has access to the private elements of its inner class.
看调用方式, 如果是外部类方法直接调用内部类成员变量, 不能, 外部类实例化后, 内部类可能压根就没有实例化, 访问个毛线
如果是实例化了, 就可以调用, 习题答案如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Outer8 {    
class Inner {
private int ii1 = 1;
private int ii2 = 2;
private void showIi2() { System.out.println(ii2); }
private void hi() { System.out.println("Inner hi"); }
}
// Need to create objects to access private elements of Inner:
int oi = new Inner().ii1;
void showOi() { System.out.println(oi); }
void showIi2() { new Inner().showIi2(); }
void outerHi() { new Inner().hi(); }
public static void main(String[] args) {
Outer8 out = new Outer8();
out.showOi();
out.showIi2();
out.outerHi();
}
}

Inner classes in methods and scopes

前面那些例子都很直白易懂, 但是 Inner class 还有一些变种, 格式很放飞自我, 该变种适用如下情况

  1. 你只是想要实现某个接口, 并返回这个接口引用
  2. 你在解决某个复杂问题时, 临时需要创建一个 class 以解决问题, 但是不想暴露它的实现

下面我们会将前面的 Parcel 例子转化为以下几种方式:

1.A class defined within a method - 在方法体内定义类
2.A class defined within a scope inside a method - 在方法的某个更小的 scope 中声明类, 比如方法的 if 条件语句中
3.An anonymous class implementing an interface - 匿名内部类实现接口
4.An anonymous class extending a class that has a non-default constructor - 匿名内部类继承抽象类 + 自定义构造函数
5.An anonymous class that performs field initialization - 匿名内部类 + field 初始化
6.An anonymous class that performs construction using instance initialization (anonymous inner classes cannot have constructors) - 匿名内部类 + 构造代码块

对应 item1: A class defined within a method, 我们将 class 创建在方法体内部, 这种做法也叫 本地内部类(local inner class):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination {
private String label;

private PDestination(String whereTo) {
label = whereTo;
}

public String readLabel() {
return label;
}
}
return new PDestination(s);
}

public static void main(String[] args) {
Parcel5 p = new Parcel5();
Destination d = p.destination("Tasmania");
}
}

PDestination 在 destination() 方法内而不在 Parcels 内, 所以 PDestination 只在方法体 destination() 内可见. 这种用法还允许你在这个类的其他方法中创建同名的内部类而没有冲突.

对应 item2: A class defined within a scope inside a method, 在方法内更小的 scope 中创建内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
}
// 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();
}
}

TrackingSlip 嵌在 if 语句中, 只在 if 里生效, 出了这个范围就失效了, 除此之外和其他内部类没什么区别.

Anonymous inner classes

对应 item3: An anonymous class implementing an interface 定义匿名内部类 和 item4: An anonymous class extending a class that has a non-default constructor 使用默认构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Contents {
int value();
}

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();
}
}

contents() 将类定义和 return 结合在了一起. 除此之外, 该类还是匿名的, 返回时该类自动转换为基类类型. 上面的实现和下面的是等价的, 不过上面的更简洁.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Parcel7b {
class MyContents implements Contents {
private int i = 11;

public int value() {
return i;
}
}

public Contents contents() {
return new MyContents();
}

public static void main(String[] args) {
Parcel7b p = new Parcel7b();
Contents c = p.contents();
}
}

看文章顺序这个应该是对应 item4: An anonymous class extending a class that has a non-default constructor 的但是总感觉他这种说法不贴切, 可能是我笔记有问题, 按理说, 下面的 instance initialization 更贴切才对.

上面例子中, 内部类使用默认构造函数实例化, 如果你需要一个特殊的构造函数, 你可以参考下面的例子. Wrapping 是一个普通的类, 我们在 Parcel8 中的 wrapping 方法中调用了 Wrapping 的带参构造函数, 并且返回时重写了其中的 value 方法. 和之前的那些返回内部类的方式异曲同工.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Parcel8 {
public Wrapping wrapping(int x) {
// Base constructor call:
return new Wrapping(x) {
// Pass constructor argument.
public int value() {
return super.value() * 47;
}
}; // Semicolon required
}

public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
System.out.println(wrapping.value());
}
}

public class Wrapping {
private int i;

public Wrapping(int x) {
i = x;
}

public int value() {
return i;
}
}

// output: 470

对应 item5: An anonymous class that performs field initialization 你可以在内部类中定义, 使用 field, field 如果是作为参数传入, 必须是 final 类型的:

再看一遍才发现, 他的特殊之处是内部类有一个 field 声明, 对应的值是直接从方法参数里面拿的!!这种用法以前没注意到过 (; ̄ェ ̄)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Parcel9 {
// Argument must be final to use inside
// anonymous inner class:
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;

public String readLabel() {
return label;
}
};
}

public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}

If you’re defining an anonymous inner class and want to use an object that’s defined outside the anonymous inner class, the compiler requires that the argument reference be final, as you see in the argument to destination(). If you forget, you’ll get a compile-time error message.

内部匿名类会调用基类的构造器, 但是如果你在实例里需要定制一些行为, 但是由于你没有名字, 没有自己的构造器, 那该怎么办?

对应 item6: An anonymous class that performs construction using instance initialization, 这种情况下, 你可以使用 构造代码块(instance initializaiton) 实现通用的功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract class Base {
public 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");
}

public void f() {
System.out.println("In anonymous f()");
}
};
}

public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}

// output:
// Base constructor, i = 47
// Inside instance initializer
// In anonymous f()

上例中 i 作为构造器参数传入, 但是并没有在内部类中被直接使用, 使用他的是基类的构造函数. 所以不用像前面的 local inner class 那样, 使用 final 修饰.

Note that the arguments to destination() must be final since they are used within the anonymous class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;

public String readLabel() {
return label;
}
};
}

public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Tasmania", 101.395F);
}
}
// output: Over budget!

在内部类的使用中, 代码块可以看作是内部类的构造函数

和其他普通的类相比, 你可以使用匿名内部类来扩展类或接口, 但只能选其一, 而且数量只能是一个.

Factory Method revisited

这部分是使用 inner class 重构之前 factory/interface 相关的代码, 有机会回头再瞅一眼

Look at how much nicer the interfaces/Factories.java example comes out when you use anonymous inner classes:

示例说明:

通过内部类实现工厂方法, 并且在示例中将 outer 类的构造函数设置成 private, 将新建的动作限制到只能通过 factory 实现, 6 的飞起.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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() {
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() {
public Service getService() {
return new Implementation2();
}
};
}

public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}

public static void main(String[] args) {
serviceConsumer(Implementation1.factory);
// Implementations are completely interchangeable:
serviceConsumer(Implementation2.factory);
}
}

// output:
// Implementation1 method1
// Implementation1 method2
// Implementation2 method1
// Implementation2 method2

通过为 Factory 提供 inner class 的实现, 我们可以将上例中的 Implementation1 和 Implementation2 的构造函数设置成私有, 缩小了 Service 实现的作用域. 同时不需要为工厂类提供单独的实现. 从语法上这样的解决方案更合理.

interfaces/Games.java 的例子也可以使用 inner class 做类似的优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
interface Game {
boolean move();
}

interface GameFactory {
Game getGame();
}

class Checkers implements Game {
private Checkers() {
}

private int moves = 0;
private static final int MOVES = 3;

public boolean move() {
System.out.println("Checkers move " + moves);
return ++moves != MOVES;
}

public static GameFactory factory = new GameFactory() {
public Game getGame() {
return new Checkers();
}
};
}

class Chess implements Game {
private Chess() {
}

private int moves = 0;
private static final int MOVES = 4;

public boolean move() {
System.out.println("Chess move " + moves);
return ++moves != MOVES;
}

public static GameFactory factory = new GameFactory() {
public Game getGame() {
return new Chess();
}
};
}

public class Games {
public static void playGame(GameFactory factory) {
Game s = factory.getGame();
while (s.move()) ;
}

public static void main(String[] args) {
playGame(Checkers.factory);
playGame(Chess.factory);
}
}

Remember the advice given at the end of the last chapter: Prefer classes to interfaces. If your design demands an interface, you’ll know it. Otherwise, don’t put it in until you are forced to.

这个建议是从上一章节 Interface 那边出来了, 具体得完那一章才知道. 建议就是先用 class, 等你完全定下来再 refactor 成 interface, 现在 interface 一般都是被滥用的.

Nested classes

如果你不想要内部类和外部类的关系, 你可以把内部类静态化, 这种做法叫 nested class(静态内部类). 普通的内部类会持有一个外部类的引用, 静态内部类则不会. 静态内部类有如下特点:

  1. You don’t need an outer-class object in order to create an object of a nested class. 独立于外部类实例存在
  2. You can’t access a non-static outer-class object from an object of a nested class. 不能通过它访问非静态的外部类

除此之外的区别还有, 普通内部类还不能持有静态变量, 方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Parcel11 {
private static class ParcelContents implements Contents {
private int i = 11;

public int value() {
return i;
}
}

protected static class ParcelDestination implements Destination {
private String label;

private ParcelDestination(String whereTo) {
label = whereTo;
}

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() 中调用时就可以直接 call 方法而不用外部类实例了.

Classes inside interfaces

一般来说, 在 interface 里放 class 是不允许的, 但是 nested class 是个例外. 任何放到 interface 里的 code 都会有 public 和 static 的属性, 所以下面代码中声明的 class class Test implements ClassInInterface 其实就是一个静态内部类. You can even implement the surrounding interface in the inner class, like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!

通过这种方式我们可以很方便的在接口使用方分享一些公用代码.

在这本书的前面几章, 有建议说在每个 class 里面加一个 main() 方法来存放测试代码, 但是这会增加需要编译的代码量. 这里我们可以将测试放到 nested class 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}
}
// output f()

编译之后测试会放到单独的 class TestBed$Tester 中, 它可以用来测试, 当要部署到产品环境时, 可以把这部分代码 exclude 掉.

现在应该不用了, 我们都是通过在测试 folder 下新建测试 UT 来完成这部分功能的

Reaching outward from a multiply nested class

不管 inner class 嵌套的有多深, 内部类都可以不受限制的访问外部类, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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();
}
}

上例中MNAAB 可以访问外部的私有方法 g(), f(). 同时也演示了, 在 main() 中你如果要新建内部类, 需要先实例化他的外部类.

Why inner classes?

为什么 Java 要支持 inner class 这种语法?

从典型的使用方式上看, 内部类会继承 class 或者 实现接口, 然后操作外部类的属性. 所以我们可以说内部类提供了一个外部类的访问窗口.

Inner class 存在的最合理的解释:

内部类都可以独立的实现一个继承. 即不管外部类是否已经继承了一个实现这对 inner class 毫无影响.

换个角度看, inner class 可以看作是多重继承的一种解决方案. 在这方面, interface 可以解决一部分问题, 但是 inner class 效率更高.

就上面的问题, 下面我们举例子来说明, 比如我们想要在一个类里实现两个接口, 你有两种选择, 一个 class + 2interface 或者 class + inner class + 1interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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());
}
}

示例中我们有 A, B 两个接口, X 实现两个接口, Y 实现一个接口 + 一个 inner class. X, Y 虽然实现方式不太一样, 但是目的都达到了, 两个接口都实现了.

但是, 如果是抽象类或者实体类, 多重继承就会受到限制.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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());
}
}

作者这里的继承说的是具有基类的某种能力, 而不是限制在继承类的语法表现, 这个对我理解继承还是有点启发的. 通过内部类我可以得到基类的实例, 说我继承了它, 也说的过去.

通过 inner class, 你可以具备以下附加功能:

  1. 内部类可以有多个实例, 并且相互独立, 和外部类也相互独立
  2. In a single outer class you can have several inner classes, each of which implements the same interface or inherits from the same class in a different way. An example of this will be shown shortly.
  3. The point of creation of the inner-class object is not tied to the creation of the outer-class object.
  4. There is no potentially confusing “is-a” relationship with the inner class; it’s a separate entity.

就第四点, 可以那前面的 Sequence.java 为例. Sequence 语义上来说是一个容器, 而 Selector 接口代表了选择这种能力. 我们通过内部创建一个 SequenceSelector 实现这中能力, 在语义上会更合理.

Closures & callbacks

Closure(闭包) 即一个可调用对象, 保留了创建它的作用域的信息. Inner class 就是 OO 概念上的一个闭包, 他持有外部类的引用, 访问不受限.

Java 支持部分指针机制, 其中之一就是 callback(回调). 在回调中, 一些对象给出自身的一部分信息(引用), 通过这部分信息, 其他对象可以操作这个对象.

inner class 的闭包特性比之与指针, 扩展性更强, 更安全.

示例说明:

下面这个例子只为了一个目的, 就是凸显出内部类可以拿到外部类的引用(Callee2.this), 并且没有任何访问限制.

我们声明一个 Incremnentable 接口, 其中有一个方法 increment(), Callee1 实现了 Incrementable.

再声明 MyIncrement 基类, 也有一个 increment() 方法, 然后声明 Callee2 继承了 MyIncrement 那么相应的他就自带了 increment() 方法, 无法再实现 Incrementable 接口, 这里通过内部类 Closure 实现接口, 在通过 getCallbackReference() 拿到引用, 变相的达到了多重继承的效果. 在主函数中, Caller 通过构造函数统一对 Incrementable 做操作.

PS:个人感觉这个例子中 MyIncrement 这个类对说明 callback 这个特性反而起了误导的作用, 让整个示例反觉更繁琐了. 整个例子只需要保留 Callee2 + Caller 部分即. 我们可以通过 caller 的 go() 方法调用 Callee2 中的方法, 改变内部变量值.

PPS: 这个例子确实多余, 这里表现出来的特性不就是 Outer.this 这个属性吗, 绕了一大圈.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
interface Incrementable {
void increment();
}

// Very simple to just implement the interface:
class Callee1 implements Incrementable {
private int i = 0;

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 {
public void increment() {
// Specify outer-class method, otherwise
// you’d 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();
}
}

// output
// Other operation
// 1
// 1
// 2
// Other operation
// 2
// Other operation
// 3

Inner classes & control frameworks

List (pronounced “List of Event”) 原来带类型的 collection 这么发音的吗, 学到了同时感觉很合理

本章主要例子中用到了 Command pattern 不过我已经忘了那是个什么东西了, 又要复习了 (; ̄ェ ̄)

control framework 是一种用于处理 event 的应用框架. 下面是书中 GreenHouse 的例子. 我们先创建一个 abstract 的类代表我们要处理的 event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Event {
private long eventTime;
protected final long delayTime;

public Event(long delayTime) {
this.delayTime = delayTime;
start();
}

public void start() { // Allows restarting
eventTime = System.nanoTime() + delayTime;
}

public boolean ready() {
return System.nanoTime() >= eventTime;
}

public abstract void action();
}

start() 单独抽离, 方便以后实现 restart 功能, ready() 即判断是否已经可以执行事件, action() 是我们要执行事件的内容.

以下是 Controller 代码, Controller 实体持有事件列表, 然后通过 while 遍历 event 并执行. 处理时将变量 list 备份以防止动态改变 list 的值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Controller {
// A class from java.util to hold Event objects:
private List<Event> eventList = new ArrayList<Event>();

public void addEvent(Event c) {
eventList.add(c);
}

public void run() {
while (eventList.size() > 0)
// Make a copy so you’re not modifying the list
// while you’re selecting the elements in it:
for (Event e : new ArrayList<Event>(eventList))
if (e.ready()) {
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}

在遍历 event 时, 我们并不知道 event 具体是什么, 这正是框架的目的, 我们并不关心某个具体的对象. 而这恰恰是 inner class 擅长的地方. 通过使用它我们可以在两方面优化上面的代码.

  1. 我们可以把 event 和 controller 合二为一, 将各个 event 特有的 action() 封装在内部类中
  2. 内部类让你的实现对外不可见

使用内部类实现代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class GreenhouseControls extends Controller {
private boolean light = false;

public class LightOn extends Event {
public LightOn(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here to
// physically turn on the light.
light = true;
}

public String toString() {
return "Light is on";
}
}

public class LightOff extends Event {
public LightOff(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here to
// physically turn off the light.
light = false;
}

public String toString() {
return "Light is off";
}
}

private boolean water = false;

public class WaterOn extends Event {
public WaterOn(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here.
water = true;
}

public String toString() {
return "Greenhouse water is on";
}
}

public class WaterOff extends Event {
public WaterOff(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here.
water = false;
}

public String toString() {
return "Greenhouse water is off";
}
}

private String thermostat = "Day";

public class ThermostatNight extends Event {
public ThermostatNight(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here.
thermostat = "Night";
}

public String toString() {
return "Thermostat on night setting";
}
}

public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
super(delayTime);
}

public void action() {
// Put hardware control code here.
thermostat = "Day";
}

public String toString() {
return "Thermostat on day setting";
}
}

// An example of an action() that inserts a
// new one of itself into the event list:
public class Bell extends Event {
public Bell(long delayTime) {
super(delayTime);
}

public void action() {
addEvent(new Bell(delayTime));
}

public String toString() {
return "Bing!";
}
}

public class Restart extends Event {
private Event[] eventList;

public Restart(long delayTime, Event[] eventList) {
super(delayTime);
this.eventList = eventList;
for (Event e : eventList)
addEvent(e);
}

public void action() {
for (Event e : eventList) {
e.start(); // Rerun each event
addEvent(e);
}
start(); // Rerun this Event
addEvent(this);
}

public String toString() {
return "Restarting system";
}
}

public static class Terminate extends Event {
public Terminate(long delayTime) {
super(delayTime);
}

public void action() {
System.exit(0);
}

public String toString() {
return "Terminating";
}
}
}

代码结构很简单, 分别声明了一些事件类型 lightOn/Off, waterOn/Off 等, 内部类继承 Event, 实现个则的抽象方法即可.

Bell 和 Restart 有别于其他的 event 内部类, 它还会调用 Outer class 的其他方法.

以下是 GreenhouseController 执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
// Instead of hard-wiring, you could parse
// configuration information from a text file here:
gc.addEvent(gc.new Bell(900));
Event[] eventList = {
gc.new ThermostatNight(0),
gc.new LightOn(200),
gc.new LightOff(400),
gc.new WaterOn(600),
gc.new WaterOff(800),
gc.new ThermostatDay(1400)
};
gc.addEvent(gc.new Restart(2000, eventList));
gc.addEvent(new GreenhouseControls.Terminate(new Integer(5000)));
gc.run();
}
}
// output:
// Bing!
// Thermostat on night setting
// Light is on
// Light is off
// Greenhouse water is on
// Greenhouse water is off
// Thermostat on day setting
// Restarting system
// Terminating

Inheriting from inner classes

如果想要继承一个内部类, 语法稍微有点特殊, 由于内部类需要借助外部类才能实例化, 所以构造函数中需要调用 outer.super() 实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}

InheritInner 继承自内部类, 在构造函数中需要外部类实体做参数. 内部类是以外部类为基础的, 所以这样做也挺合理.

Can inner classes be overridden?

内部类并不能像方法那样被重写. 我们准备一个 class Egg, 里面声明一个内部类 Yolk 并在构造函数中调用它. 我们再新建一个类 GigEgg 继承 Egg, 在里面声明一个同名的内部类, 试图用类似方法重写的方式覆盖他. 示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()

默认的构造函数会在编译时指定调用基类中的 Yolk 对象. 这个例子表明 JVM 在处理内部类时并没有做什么特殊的操作, 基类和子类中的内部函数时完全隔离的.

This example shows that there isn’t any extra inner-class magic going on when you inherit
from the outer class. The two inner classes are completely separate entities, each in its own
namespace. However, it’s still possible to explicitly inherit from the inner class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}

public void f() {
System.out.println("Egg2.Yolk.f()");
}
}

private Yolk y = new Yolk();

public Egg2() {
System.out.println("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() {
System.out.println("BigEgg2.Yolk()");
}

public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}

public BigEgg2() {
insertYolk(new Yolk());
}

public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
}

// output
// Egg2.Yolk() <- 初始化子类时调用基类构造, 先初始化基类中的 field
// New Egg2() <- 基类构造
// Egg2.Yolk() <- 子类 new Yolk() 先调用 基类 中的 Yolk 构造
// BigEgg2.Yolk() <- 子类构造调用
// BigEgg2.Yolk.f() <- 子类调用 g 方法

在上面的例子里面, 我们显示的指定 BigEgg2 中的 Yolk 继承自 Egg2 中的 Yolk, 然后基类中还提供了一个 insertYolk() 来修改基类中内部类的引用.

Local inner classes

内部类可以创建在代码块中, 一般常见的是创建在方法里面. 我们无法访问方法体里面的内部类, 因为他并不是 outer class 的一部分. 但是这个内部类还是可以毫无限制的访问外部类的各种信息.

下面是 local inner class 和匿名内部类的对比例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
interface Counter {
int next();
}

public class LocalInnerClass {
private int count = 0;

Counter getCounter(final String name) {
// A local inner class:
class LocalCounter implements Counter {
public LocalCounter() {
// Local inner class can have a constructor
System.out.println("LocalCounter()");
}

public int next() {
System.out.print(name); // Access local final
return count++;
}
}
return new LocalCounter();
}

// The same thing with an anonymous inner class:
Counter getCounter2(final String name) {
return new Counter() {
// Anonymous inner class cannot have a named
// constructor, only an instance initializer:
{
System.out.println("Counter()");
}

public int next() {
System.out.print(name); // Access local final
return count++;
}
};
}

public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter
c1 = lic.getCounter("Local inner "),
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());
}
}

// output
// LocalCounter()
// Counter()
// Local inner 0
// Local inner 1
// Local inner 2
// Local inner 3
// Local inner 4
// Anonymous inner 5
// Anonymous inner 6
// Anonymous inner 7
// Anonymous inner 8
// Anonymous inner 9

上面的例子中, Counter 接口会依次返回 count 值. local inner class 和 匿名内部类都实现了这个接口. 两个内部类逻辑和功能也都一样, 唯一区别是, 匿名内部类他是没有构造函数的, 需要用代码块代替.

如果你需要创建多个实例的话, 你也要使用 local inner class, 你用 anonymous 是建不出来多个实例的.

Inner-class identifiers

每个类在编译后都会生成一个 .class 文件保存对应的类信息. 内部类也一样, 格式为 外部类$内部类 下面是 LocalInnerClass.java 编译后的文件:

1
2
3
4
Counter.class
LocalInnerClass$l.class
LocallnnerClassSlLocalCounter.class
LocallnnerClass.class

如果是内部匿名类, 类名由数字代替. 如果是多层嵌套的内部类, 类名间链接多个 $ 符号.

Summary

接口和内部类是 Java 特有的, 你在 C++ 中找不到类似的概念, 他们帮助我们实现多重继承的问题而且实现上要比 C++ 的优雅.

实践出真知

2021-04-22 想要在代码中使用简化版的 Builder 模式,但是发现 nested static class 中声明的对象不能调用 set method. 为啥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
private String name;

public void setName(String name) {
this.name = name;
}

public static class Builder {
Person p = new Person();
//? p.name = "jack"; 不能操作

public Builder setName(String name) {
p.name = name; // 可操作
// p.setName(name); 可操作
return this;
}

public Person build() {
return p;
}
}
}