java8 以后我们在某些地方见到类似的代码
new LambdaQueryWrapper<User>().eq(User::getUsername, user.getUsername())
通过lambda的方法引用为User对象的字段赋值,原理是什么呢?
接下来我们通过一个demo来解释
首先创建一个java工程,编写如下接口和类
package lambda;
public class Person {
private int id;
private String name;
// 有参和无参构造函数
// geeter and setter
}
package lambda;
import java.io.Serializable;
@FunctionalInterface
public interface IGetter<T> extends Serializable {
Object get(T source);
}
package lambda;
import java.io.Serializable;
@FunctionalInterface
public interface ISetter<T, U> extends Serializable {
void set(T t, U u);
}
package lambda;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
public class BeanUtils {
public static void main(String[] args) throws Exception {
// 保存jvm运行过程中生成的lambda字节码文件到指定路径
System.getProperties().put("jdk.internal.lambda.dumpProxyClasses", "F:/lambda");
String getName = BeanUtils.convertToFieldName(Person::getId);
String setName = BeanUtils.convertToFieldName(Person::setName);
System.out.println(getName);
System.out.println(setName);
}
/**
* 通过getter的方法引用获取字段名
*/
public static <T> String convertToFieldName(IGetter<T> fn) throws Exception {
SerializedLambda lambda = getSerializedLambda(fn);
String methodName = lambda.getImplMethodName();
String prefix = null;
if (methodName.startsWith("get")) {
prefix = "get";
} else if (methodName.startsWith("is")) {
prefix = "is";
}
if (prefix == null) {
System.out.println("无效的getter方法: " + methodName);
}
return toLowerCaseFirstOne(methodName.replace(prefix, ""));
}
/**
* 通过setter的方法引用获取字段名
*/
public static <T, U> String convertToFieldName(ISetter<T, U> fn) throws Exception {
SerializedLambda lambda = getSerializedLambda(fn);
String methodName = lambda.getImplMethodName();
if (!methodName.startsWith("set")) {
System.out.println("无效的setter方法:" + methodName);
}
return toLowerCaseFirstOne(methodName.replace("set", ""));
}
/**
* 关键在于这个方法
*/
public static SerializedLambda getSerializedLambda(Serializable fn) throws Exception {
Method method = fn.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
SerializedLambda lambda = (SerializedLambda) method.invoke(fn);
return lambda;
}
/**
* 字符串首字母转小写
*/
public static String toLowerCaseFirstOne(String field) {
if (Character.isLowerCase(field.charAt(0)))
return field;
else {
char firstOne = Character.toLowerCase(field.charAt(0));
String other = field.substring(1);
return new StringBuilder().append(firstOne).append(other).toString();
}
}
}
运行结果
id
name
其中最难理解的应该是下面这句代码,writeReplace方法是什么
Method method = fn.getClass().getDeclaredMethod("writeReplace");
接下来我们通过jvm运行时保存的lambda字节码来一探究竟
首先运行完成后会生成方法引用的字节码文件
反编译以后
// Person::getId
package lambda;
import java.lang.invoke.SerializedLambda;
final class BeanUtils$$Lambda$1 implements IGetter {
@Hidden
public Object get(Object obj) {
return Integer.valueOf(((Person) obj).getId());
}
private final Object writeReplace() {
return new SerializedLambda(lambda/BeanUtils, "lambda/IGetter", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", 5, "lambda/Person", "getId", "()I", "(Llambda/Person;)Ljava/lang/Object;", new Object[0]);
}
private BeanUtils$$Lambda$1() {
}
}
// Person::setName
package lambda;
import java.lang.invoke.SerializedLambda;
final class BeanUtils$$Lambda$2 implements ISetter {
@Hidden
public void set(Object obj, Object obj1) {
((Person) obj).setName((String) obj1);
}
private final Object writeReplace() {
return new SerializedLambda(lambda / BeanUtils, "lambda/ISetter", "set", "(Ljava/lang/Object;Ljava/lang/Object;)V", 5, "lambda/Person", "setName", "(Ljava/lang/String;)V", "(Llambda/Person;Ljava/lang/String;)V", new Object[0]);
}
private BeanUtils$$Lambda$2() {
}
}
原理一目了然
总结
- 要得到
Lambda
表达式中方法引用的方法名,目前已知的方式是通过SerializedLambda
; -
SerializedLambda
是对Lambda
表达式进行描述的对象,在Lambda
表达式可序列化的时候(函数式接口继承Serializable
)才能得到; - 函数式接口继承
Serializable
时,编译器在编译Lambda
表达式时,生成了一个writeReplace
方法,这个方法会返回SerializedLambda
,可以反射调用这个方法;