Java 8 引入了许多新特性,其中方法引用和 Lambda 表达式对外部变量的访问是两个非常重要的功能。在这篇博客中,我们将详细讲解这两个特性,并通过代码示例和源码分析来深入理解它们的实现原理。
方法引用
方法引用是 Java 8 中引入的一种新的语法,允许我们使用 ::
关键字来传递方法或者构造函数引用。方法引用是 Lambda 表达式的简写,它提供了一种更简洁、更易读的方式来表达代码逻辑。方法引用的返回类型必须是一个 Functional Interface(函数式接口)。
方法引用的四种类型
- 静态方法引用 -
ClassName::staticMethodName
- 实例方法引用 -
instance::instanceMethodName
- 超类方法引用 -
super::superMethodName
- 构造方法引用 -
ClassName::new
我们将通过具体的代码示例来详细讲解这些引用方式。
示例代码及讲解
首先,我们定义一个函数式接口 LambdaInterface
:
java
@FunctionalInterface
public interface LambdaInterface {
void execute();
}
然后,我们定义一个基类 LambdaClassSuper
和子类 LambdaClass
:
java
public class LambdaClassSuper {
LambdaInterface sf() {
return () -> System.out.println("Super class method called");
}
}
java
public class LambdaClass extends LambdaClassSuper {
public static LambdaInterface staticF() {
return () -> System.out.println("Static method called");
}
public LambdaInterface f() {
return () -> System.out.println("Instance method called");
}
void show() {
// 1. 调用静态方法引用,返回类型必须是函数式接口
LambdaInterface t = LambdaClass::staticF;
t.execute(); // 输出:Static method called
// 2. 实例方法引用
LambdaClass lambdaClass = new LambdaClass();
LambdaInterface lambdaInterface = lambdaClass::f;
lambdaInterface.execute(); // 输出:Instance method called
// 3. 调用超类的方法引用
LambdaInterface superf = super::sf;
superf.execute(); // 输出:Super class method called
// 4. 构造方法引用
Supplier<LambdaClassSuper> supplier = LambdaClassSuper::new;
LambdaClassSuper instance = supplier.get();
instance.sf().execute(); // 输出:Super class method called
}
public static void main(String[] args) {
new LambdaClass().show();
}
}
详细讲解
-
静态方法引用
java
LambdaInterface t = LambdaClass::staticF; t.execute(); // 输出:Static method called
这里的
LambdaClass::staticF
是对静态方法staticF
的引用。LambdaInterface
是一个函数式接口,其唯一的抽象方法execute
对应于staticF
返回的 Lambda 表达式。 -
实例方法引用
java
LambdaClass lambdaClass = new LambdaClass(); LambdaInterface lambdaInterface = lambdaClass::f; lambdaInterface.execute(); // 输出:Instance method called
lambdaClass::f
是对实例方法f
的引用。这里的lambdaClass
是LambdaClass
的实例,f
方法返回一个 Lambda 表达式。 -
超类方法引用
java
LambdaInterface superf = super::sf; superf.execute(); // 输出:Super class method called
super::sf
是对超类方法sf
的引用。由于LambdaClass
继承了LambdaClassSuper
,它可以直接引用超类的方法。 -
构造方法引用
java
Supplier<LambdaClassSuper> supplier = LambdaClassSuper::new; LambdaClassSuper instance = supplier.get(); instance.sf().execute(); // 输出:Super class method called
LambdaClassSuper::new
是对构造方法的引用。Supplier<LambdaClassSuper>
是一个函数式接口,其get
方法返回一个新的LambdaClassSuper
实例。
访问变量
在 Java 8 中,Lambda 表达式允许引用外部的局部变量,但这些变量被隐式地视为 final
。这意味着这些变量在 Lambda 表达式中只能读取,而不能修改。如果尝试修改这些变量,编译时会报错。
示例代码及讲解
我们通过一个具体的例子来解释这一特性:
java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class LambdaVariableAccess {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 定义一个局部变量
int i = 0;
// 使用 Lambda 表达式引用外部变量 i
Collections.sort(numbers, (Integer o1, Integer o2) -> o1 - i);
// 尝试修改变量 i
// i = 3; // 取消注释这一行将会导致编译错误
System.out.println(numbers);
}
}
代码解释
-
定义局部变量
int i = 0;
这里定义了一个局部变量
i
,它的值是0
。 -
使用 Lambda 表达式引用外部变量
java
Collections.sort(numbers, (Integer o1, Integer o2) -> o1 - i);
在 Lambda 表达式中,我们引用了外部变量
i
。此时,i
被隐式地视为final
,即i
的值不能再被修改。 -
尝试修改变量
// i = 3; // 取消注释这一行将会导致编译错误
如果尝试在 Lambda 表达式之后修改变量
i
的值,会导致编译错误。因为在 Lambda 表达式中引用的外部变量必须是不可变的。
编译错误示例
如果我们取消注释 i = 3;
这一行,将会得到如下编译错误:
txt
LambdaVariableAccess.java:14: error: local variables referenced from a lambda expression must be final or effectively final
i = 3;
^
1 error
替代方案
如果不想使用 final
声明外部变量,或者希望在 Lambda 表达式之后修改变量的值,有几种替代方案可以考虑:
1. 使用数组或对象封装变量
可以使用数组或自定义对象封装这些变量,因为数组和对象的引用是不可变的,但其内容是可变的。
示例代码
java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class LambdaVariableAccess {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 使用数组封装变量
int[] i = { 0 };
// 使用 Lambda 表达式引用数组中的变量
Collections.sort(numbers, (Integer o1, Integer o2) -> o1 - i[0]);
// 修改数组中的变量
i[0] = 3;
System.out.println(numbers);
}
}
2. 使用自定义对象封装变量
除了使用数组,还可以使用自定义对象封装变量。
示例代码
java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class LambdaVariableAccess {
static class Wrapper {
int value;
Wrapper(int value) {
this.value = value;
}
}
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 使用自定义对象封装变量
Wrapper wrapper = new Wrapper(0);
// 使用 Lambda 表达式引用对象中的变量
Collections.sort(numbers, (Integer o1, Integer o2) -> o1 - wrapper.value);
// 修改对象中的变量
wrapper.value = 3;
System.out.println(numbers);
}
}
3. 使用 AtomicInteger
或其他原子类
Java 提供了 java.util.concurrent.atomic
包,其中包含了 AtomicInteger
、AtomicBoolean
、AtomicLong
等原子类。可以利用这些类来封装变量,并确保线程安全。
示例代码
java
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class LambdaVariableAccess {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 使用 AtomicInteger 封装变量
AtomicInteger atomicInteger = new AtomicInteger(0);
// 使用 Lambda 表达式引用 AtomicInteger 中的变量
Collections.sort(numbers, (Integer o1, Integer o2) -> o1 - atomicInteger.get());
// 修改 AtomicInteger 中的变量
atomicInteger.set(3);
System.out.println(numbers);
}
}
原子类的原理
Java 的 java.util.concurrent.atomic
包提供了一系列的原子类(如 AtomicInteger
, AtomicLong
, AtomicBoolean
等),这些类提供了一些基本类型的线程安全操作。它们利用了底层的硬件支持(如 CPU 的原子指令)来实现高效的线程安全操作,而不需要使用锁机制。
原子类的基本原理
- 无锁算法(Lock-Free Algorithms):原子类通过无锁算法实现线程安全,避免了传统锁机制带来的性能开销。
- CAS 操作(Compare-And-Swap):原子类广泛使用了 CAS 操作,CAS 是一种硬件级别的原子操作,用于实现无锁算法。CAS 操作包括三个操作数:内存位置(V)、预期值(A)、新值(B)。CAS 操作的逻辑是:如果内存位置 V 的值等于预期值 A,则将其更新为新值 B;否则,不进行任何操作。
原子类示例与源码分析
以下是 AtomicInteger
的示例代码及其实现原理:
示例代码
java
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
// 获取当前值
int currentValue = atomicInteger.get();
System.out.println("Current Value: " + currentValue);
// 设置新值
atomicInteger.set(5);
System.out.println("New Value after set: " + atomicInteger.get());
// 获取并递增
int oldValue = atomicInteger.getAndIncrement();
System.out.println("Old Value: " + oldValue);
System.out.println("New Value after increment: " + atomicInteger.get());
// CAS 操作
boolean isUpdated = atomicInteger.compareAndSet(6, 10);
System.out.println("CAS operation successful: " + isUpdated);
System.out.println("Value after CAS: " + atomicInteger.get());
}
}
在这个示例中,我们展示了 AtomicInteger
的一些常见操作,包括获取值、设置值、递增操作以及 CAS 操作。
源码分析
以下是 AtomicInteger
类的部分源码,解释其工作原理:
java
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// 使用 Unsafe 类进行底层操作
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 获取 value 字段的内存偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
解释:
-
Unsafe 类:
AtomicInteger
使用了sun.misc.Unsafe
类来实现底层的原子操作。Unsafe
类提供了直接操作内存和线程的能力。 -
valueOffset:通过
Unsafe
类获取value
字段的内存偏移量。这允许Unsafe
类直接操作value
字段。 -
volatile 修饰的 value 字段:
value
字段被声明为volatile
,确保对该字段的读写操作是线程安全的。 -
getAndIncrement 方法:使用
Unsafe
类的getAndAddInt
方法来实现原子递增操作。这个方法通过 CAS 操作实现无锁的线程安全递增。 -
compareAndSet 方法:使用
Unsafe
类的compareAndSwapInt
方法来实现 CAS 操作。如果当前值等于预期值,则将其更新为新值;否则,不进行任何操作。
总结
Java 8 中的 Lambda 表达式和方法引用提供了一种简洁且高效的编码方式,极大地提升了代码的可读性和可维护性。在 Lambda 表达式中引用外部变量时,需要注意这些变量必须是 final
或 effectively final。如果不想使用 final
声明外部变量,可以使用数组、自定义对象或 AtomicInteger
等原子类来封装变量。