2016-05-03 82 views
3

我试图在某些高级包装器中使用Java接口作为mixin类型D使用反射为泛型类型变量生成lambda类

interface WrapsD { 
    D getWrapped(); 
} 

interface FeatureA extends WrapsD { 
    default ... 
} 

interface FeatureB extends WrapsD { 
    default ... 
} 

abstract class DWrapperFactory<T extends WrapsD> { 
    protected T doWrap(D d) { 
     return() -> d; // <- does not work 
    } 
} 

interface FeatureAB extends FeatureA, FeatureB { 
} 

class ProducingDWithFeatureAB extends DWrapperFactory<FeatureAB> { 
    protected FeatureAB doWrap(D d) { 
     return() -> d; // <- has to repeat this 
    } 
} 

如图ProducingDWithFeatureAB看出,doWrap具有即使身体相同每个子类来实现。 (为什么Java泛型是真正打破再举一个例子。)

既然我已经需要建立像ProducingDWithFeatureAB其他原因具体类和存在于JRE代码sythesize拉姆达类,应该可以写doWrap只有一次使用反射。我想知道它是如何做到的。

doWrap用于使用匿名内部类实现接口,这更加biolderplate来实现。)

+0

泛型类型是在由于类型擦除编译时间删除。因此,在这种情况下,您所需的通用方法/ lambda可能不可行。 – callyalater

+0

@callyalater我已经必须为每个不同的'T'创建一个子类,因此类型可以通过反射来保证。我只是试图避免一遍又一遍地复制'fun()'的实现,只是为了使类型工作... –

+0

你想要替换的匿名本地类中的代码是什么? – user140547

回答

5

你要了解的第一件事,就是表单

public Function<X,Y> fun() { 
    return arg -> expr; 
} 

的方法脱就等同于:

public Function<X,Y> fun() { 
    return DeclaringClass::lambda$fun$0; 
} 
private static Y lambda$fun$0(X arg) { 
    return expr; 
} 

而类型XY衍生自目标界面的功能签名。虽然功能接口的实际实例是在运行时生成的,但您需要执行的物化目标方法,这是由编译器生成的。

您可以反射性地为单个目标方法生成不同接口的实例,但仍需要所有这些功能接口具有相同的功能签名,例如,从XY的映射,这降低了动态解决方案的实用性。在你的情况下,所有目标接口确实具有相同的功能签名,这是可能的,但我必须强调,整个软件设计看起来对我来说是可疑的。

为了实现动态生成,我们必须如上所述去除lambda表达式,并将捕获的变量d作为附加参数添加到目标方法中。由于您的特定功能没有参数,它使拍摄d的唯一方法参数:

protected T doWrap(D d) { 
    Class<T> type=getActualT(); 
    MethodHandles.Lookup l=MethodHandles.lookup(); 
    try 
    { 
     MethodType fType = MethodType.methodType(D.class); 
     MethodType tType = fType.appendParameterTypes(D.class); 
     return type.cast(LambdaMetafactory.metafactory(l, "getWrapped", 
      tType.changeReturnType(type), fType, 
      l.findStatic(DWrapperFactory.class, "lambda$doWrap$0", tType), fType) 
      .getTarget().invoke(d)); 
    } 
    catch(RuntimeException|Error t) { throw t; } 
    catch(Throwable t) { throw new IllegalStateException(t); } 
} 
private static D lambda$doWrap$0(D d) { 
    return d; 
} 

您必须实现方法getActualT()这应该回到正确的类的对象,这是可能的,如果的DWrapperFactory实际子正如你所说的那样,它是一种适当的可调整类型。然后,方法doWrap将动态生成一个合适的实例T,调用desugared lambda表达式的方法,捕获的值为d -all假设类型T确实是一个功能接口,这在编译时不能被证明。

注意,即使在运行时,LambdaMetafactory不检查不变量是否持有,你可能会在以后的时间抛出的错误,如果T不是一个适当的功能接口(和WrapsD子类)。


现在比较只是重复的方法

protected SubtypeOfWrapsD doWrap(D d) { 
    return() -> d; 
} 

在具有存在反正每一reifiable类型...

+0

非常感谢您提供解决方案和建议。我正在重新评估这个架构。可悲的是,这是对调用代码进行最少更改的一个... –

7

此无关泛型;你的通用例子只是混淆了真正的问题。

这是问题的核心:lambda表达式需要一个目标类型这是一个函数接口,并且该目标类型必须是编译器静态知道的。你的代码没有提供。例如,下面的代码将得到同样的错误,出于同样的原因:

Object o = arg -> expr; 

在这里,对象不是功能性的接口,和lambda表达式只能在其类型为一个上下文中使用的(兼容)功能界面。

泛型的使用使得它更混乱(我认为你也混淆了泛型如何工作),但最终这将是最终的底线。

+0

是的,我明白这一切。问题是没有办法告诉编译器泛型类型变量'T'是一个功能接口。这个问题的根源在于Java泛型是使用类型擦除而不是模板来实现的。我可以保证'T'是一个函数接口,所以应该可以在运行时使用relfection生成正确的合成类。我只想知道如何去做。 –

+1

不,这不是真正的问题 - 它只是你绊倒的下一件事。即使有一种方法可以将'T'约束为'T extends FunctionalInterface',这只会将问题转移一步 - 编译器仍然必须知道目标类型_statically_才能生成正确的实例化代码。而且这些信息仍然被删除。 –

+0

是的,我也可以。正如我在问题的纪念和修改后的问题中所提到的,我不希望编译器能够为lambda生成字节码,但是我会在运行时使用反射和类元数据合成lambda类,将T作为具体类型。 –