如何为Fields创建function接口实现?
考虑Animal
类中的字段weight
。 我希望能够创建一个getter和setterfunction接口对象来操作这个字段。
class Animal { int weight; }
我目前的方法类似于用于方法的方法:
public static Supplier getter(Object obj, Class cls, Field f) throws Exception { boolean isstatic = Modifier.isStatic(f.getModifiers()); MethodType sSig = MethodType.methodType(f.getType()); Class dCls = Supplier.class; MethodType dSig = MethodType.methodType(Object.class); String dMthd = "get"; MethodType dType = isstatic? MethodType.methodType(dCls) : MethodType.methodType(dCls, cls); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle fctry = LambdaMetafactory.metafactory(lookup, dMthd, dType, dSig, lookup.unreflectGetter(f), sSig).getTarget(); fctry = !isstatic && obj!=null? fctry.bindTo(obj) : fctry; return (Supplier)fctry.invoke(); }
但是这会产生以下错误:
java.lang.invoke.LambdaConversionException: Unsupported MethodHandle kind: getField x.Animal.weight:()int
UPDATE
我正在尝试创建一个实现interface Map
ObjectMap
类,它基本上试图将对象 表示为Map
,其中对象可以是任何类型。 当前正在使用Field.set()
Field.get()
和Field.set()
来操作get()
和put()
方法中的字段,并使用上面提到的方法来创建用于调用getter和setter方法的Supplier
和Consumer
对象。 我想知道我是否可以将两种不同的方法合并为一种。
可以通过ObjectMap
用作Map
示例类:
public class ThisCanBeAnything { /* fields */ public String normalField; private int hiddenFiled; private String hiddenReadonlyField; /* getters and setters */ public int hiddenField() { return hiddenField; } public void hiddenField(int v) { System.out.println("set: hiddenField="+v); hiddenField = v; } public String hiddenReadonlyField() { return hiddenReadonlyField; } }
这是预期的用法:
Object o = new ThisCanBeAnything(); Map m = new ObjectMap(o); m.put("normalField", "Normal"); System.out.println(m.get("normalField")); // Normal m.put("hiddenField", 1); // set: hiddenField=1 System.out.println(m.get("hiddenField")); // 1 m.put("hiddenReadonlyField", 1); // does not do anything System.out.println(m.get("hiddenReadonlyField")); // null
您可以直接编写lambda,根本不需要LambdaMetafactory
:
public static Supplier getter(Object obj, Field f) { return () -> { try { return f.get(obj); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }; }
或运行时类型安全版本:
public static Supplier getter(Object obj, Class fieldClass, Field f) { if (!fieldClass.isAssignableFrom(f.getType())) throw new RuntimeException("Field is not of expected type"); return () -> { try { return (T) f.get(obj); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }; }
例如:
private class X { public int a; } @Test public void supplier_getter_test() throws NoSuchFieldException { X a = new X(); aa = 5; Supplier sup = getter(a, int.class, X.class.getField("a")); assertEquals(5, sup.get().intValue()); }
你需要做的太难了。 如果有Field
,可以直接在查找工厂上调用MethodHandle
来检索MethodHandle
:
生成一个方法句柄,提供对reflection字段的读访问权限。 方法句柄的类型将具有字段值类型的返回类型。 如果该字段是静态的,则方法句柄不带参数。 否则,它的单个参数将是包含该字段的实例。
public static Supplier
这将返回该字段值的供应商。 根据字段的可访问性,您可能需要调用setAccessible(true)
。
请注意,方法句柄和reflectionAPI 在性能方面也有所不同,可能会更快。
您不能MethodHandle
字段直接访问的MethodHandle
绑定到函数接口实例,但您可以绑定Field
实例的访问器方法:
public static Supplier getter(Object obj, Class> cls, Field f) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle get=lookup.findVirtual(Field.class,"get",MethodType.genericMethodType(1)); MethodHandle fctry = LambdaMetafactory.metafactory(lookup, "get", get.type().changeReturnType(Supplier.class), MethodType.genericMethodType(0), get, MethodType.genericMethodType(0)).getTarget(); return (Supplier)fctry.invoke(f, Modifier.isStatic(f.getModifiers())? null: obj); }
虽然在此特定示例中您可以考虑生成IntSupplier
:
public static IntSupplier getter(Object obj, Class> cls, Field f) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle get=lookup.findVirtual(Field.class, "getInt", MethodType.methodType(int.class, Object.class)); MethodHandle fctry = LambdaMetafactory.metafactory(lookup, "getAsInt", get.type().changeReturnType(IntSupplier.class), MethodType.methodType(int.class), get, MethodType.methodType(int.class)).getTarget(); return (IntSupplier)fctry.invoke(f, Modifier.isStatic(f.getModifiers())? null: obj); }
…
final Animal animal = new Animal(); IntSupplier s=getter(animal, Animal.class, Animal.class.getDeclaredField("weight")); animal.weight=42; System.out.println(s.getAsInt());
function风格让您以新的方式思考这些事情。 而不是像基于reflection的方法
Supplier getter(Object obj, Class> cls, Field f){...}
尝试类似的东西
static Supplier getter(O obj, Function extractor) { return () -> extractor.apply(obj); }
你会调用它
Supplier getWeight = getter(animal, a -> a.weight); Integer weight = getWeight.get();
是a -> a.weight
比通过reflection提出Field
更难吗?
一个优点是您可以根据需要使用字段或方法,例如,如果您添加了重量的吸气剂,
Supplier getWeight = getter(animal, Animal::getWeight);
类似的二传手工厂可能是
static Consumer setter(O obj, BiConsumer modifier) { return field -> modifier.accept(obj,field); }
像这样调用
Consumer setWeight = setter(animal, (a, w) -> a.weight = w); setWeight.accept(90);
我知道这是一个迟到的答案,但我已经开发了一个库,您可以使用MethodHandle
任何MethodHandle
转换为lambda函数。 性能与手动实现具有直接访问function的function相同。
impl基于以下事实:静态最终MethodHandle
被内联到与直接访问一样快的程度。 有关这方面的更多信息可以在这里找到: 如何提高Field.set的性能(使用MethodHandles进行perhap)?
该库可以在这里找到: https : //github.com/LanternPowered/Lmbda 。 现在您将不得不使用Jitpack来访问它(小型库,因此编译时间不会太长): https ://jitpack.io/#LanternPowered/Lmbda
在对象上设置字段的示例:
import org.lanternpowered.lmbda.LmbdaFactory; import org.lanternpowered.lmbda.LmbdaType; import org.lanternpowered.lmbda.MethodHandlesX; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.function.ObjIntConsumer; public class LambdaSetterTest { public static void main(String... args) throws Exception { final MethodHandles.Lookup lookup = MethodHandlesX.privateLookupIn(TestObject.class, MethodHandles.lookup()); final MethodHandle methodHandle = lookup.findSetter(TestObject.class, "data", int.class); final ObjIntConsumer setter = LmbdaFactory.create(new LmbdaType>() {}, methodHandle); final TestObject object = new TestObject(); System.out.println(100 == object.getData()); setter.accept(object, 10000); System.out.println(10000 == object.getData()); } public static class TestObject { private int data = 100; int getData() { return this.data; } } }
从对象获取字段:
import org.lanternpowered.lmbda.LmbdaFactory; import org.lanternpowered.lmbda.LmbdaType; import org.lanternpowered.lmbda.MethodHandlesX; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.util.function.ToIntFunction; public class LambdaSetterTest { public static void main(String... args) throws Exception { final MethodHandles.Lookup lookup = MethodHandlesX.privateLookupIn(TestObject.class, MethodHandles.lookup()); final MethodHandle methodHandle = lookup.findGetter(TestObject.class, "data", int.class); final ToIntFunction getter = LmbdaFactory.create(new LmbdaType>() {}, methodHandle); final TestObject object = new TestObject(); System.out.println(100 == getter.applyAsInt(object)); object.setData(10000); System.out.println(10000 == getter.applyAsInt(object)); } public static class TestObject { private int data = 100; void setData(int value) { this.data = value; } } }