我们知道内部类使用外部变量时,需要的是一个不可变的量(final),但有时因为 final 限制显得并不太方便,如:我们想要在一个封闭作用域中设计一个计时器,如我们想要计算排序过程中的调用 compareTo的次数,那该怎么解决,一般我们是这么写:
public static void main(String[] args) {
int counter = 0;
Date[] dates = new Date[100];
for (int i = 0; i < dates.length; ++i){
dates[i] = new Date() {
@Override
public int compareTo(Date other) {
counter++; // error
return super.compareTo(other);
}
};
}
Arrays.sort(dates);
System.out.println(counter);
}
很简单的逻辑,但这种写法是错误的,在内部类种,不可引用可变的量,由于我们清楚的知道 counter 是需要更新的,所以不能讲 counter 声明为 final, 由于 Interger 对象也是不可变,所以并不能用Integer代替它,虽然我们可以设计一个类来存储一个Integer值,但这显得多余,补救的方法就是使用一个长度为1的数组:
public static void main(String[] args) {
final int[] counter = {0};
Date[] dates = new Date[100];
for (int i = 0; i < dates.length; ++i){
dates[i] = new Date() {
@Override
public int compareTo(Date other) {
counter[0]++;
return super.compareTo(other);
}
};
}
Arrays.sort(dates);
System.out.println(counter[0]);
}
为什么可以这样做呢,因为数组其实也是实现了Object对象,数组本身就是一个对象,couter 在这里实际是存放着地址单位,因为数组的地址一旦创建除非手动改动,之后都是不会放松变化,所以可以用数组来代替。
实际上在 java8 中 Collectors 类新增的大量汇聚方法中都运用了此类解决方法,因为在 lamdba 表达式中,捕获的变量必须实际上是最终变量
public static <T> Collector<T, ?, Integer>
summingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<>(
() -> new int[1],
(a, t) -> { a[0] += mapper.applyAsInt(t); },
(a, b) -> { a[0] += b[0]; return a; },
a -> a[0], CH_NOID);
}
这里说明一下,lambda可以替代内部类,但 lamdba 与 内部类 本质上是两个不同的东西。