Java 当我没有';我不知道它是否';前面有一只羔羊吗?

Java 当我没有';我不知道它是否';前面有一只羔羊吗?,java,reflection,lambda,java-8,cglib,Java,Reflection,Lambda,Java 8,Cglib,我们有一些遗留的反射代理生成代码,如果您将其视为一个黑匣子,它基本上是这样工作的: Object someObject = new Anything(); Object debugObject = ProxyUtils.wrapWithDebugLogging(someObject); wrapWithDebugLogging接受任何对象并覆盖它可以覆盖的任何方法(如果您正在扩展一个真实的类,那么最终的方法显然是不可扩展的),截取它来记录关于调用的消息,然后调用真实的方法 在内部,它使用cgl

我们有一些遗留的反射代理生成代码,如果您将其视为一个黑匣子,它基本上是这样工作的:

Object someObject = new Anything();
Object debugObject = ProxyUtils.wrapWithDebugLogging(someObject);
wrapWithDebugLogging接受任何对象并覆盖它可以覆盖的任何方法(如果您正在扩展一个真实的类,那么最终的方法显然是不可扩展的),截取它来记录关于调用的消息,然后调用真实的方法

在内部,它使用cglib来完成工作,并且在构造代理之前有一些保护逻辑,因为匿名类是最终的,但可以通过使用它们实现的超类或单个接口来处理:

Class<?> clazz = someObject.getClass();
Class<?> interfaces = clazz.getInterfaces();

// Anonymous classes are final so you can't extend them, but we know they only have one
// superclass or one interface.
if (clazz.isAnonymousClass()) {
    clazz = interfaces.length > 0 ?
            interfaces[0] : primaryType.getSuperclass();
}

Enhancer enhancer = new Enhancer();

if (clazz.isInterface()) {
    interfaces.add(clazz);
} else {
    enhancer.setSuperclass(primaryType);
}
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
Class clazz=someObject.getClass();
类接口=clazz.getInterfaces();
//匿名类是最终类,所以您不能扩展它们,但我们知道它们只有一个
//超类或一个接口。
if(clazz.isAnonymousClass()){
clazz=interfaces.length>0?
接口[0]:primaryType.getSuperclass();
}
增强子增强子=新增强子();
if(clazz.isInterface()){
接口。添加(clazz);
}否则{
增强子。设置超级类(primaryType);
}
setInterfaces(interfaces.toArray(新类[interfaces.size()]);
问题是Java 8的“lambda”类对于isAnonymousClass()返回false。但是我们想把它们和匿名类完全一样对待

一直以来,没有办法“通过设计”确定一个类是lambda类。但对我来说,这更像是反射API中缺少的东西,而且这肯定不是Java第一次“忘记”在新API中添加明显的东西


那么,有没有一种明智的方法来区分lambda和非lambda,而不必在API中使用此功能呢?我可以看到isSynthetic()返回true,但对于其他所有类型的事情,它也可能返回true。

下面是一个快速实验的结果。
foo
方法只输出其参数的
getClass().toString()
。(
getClass().getName()
输出相同的结果。)

输出:
Test32$1

    foo (() -> System.out.println("run"));
输出:
Test32$$Lambda$1/640070680


无法保证它的可移植性。

我认为这一限制(如果你想这么说的话)不应该成为问题

如果您在适当的地方正确地编程到接口,那么让代理具有超类(而不是
对象
)就变得不必要了。当代理的超类可用时设置它(无
private
构造函数,无
final
等)

在所有情况下,您所需要做的就是捕获代理对象,即目标,截获它上的所有方法调用,进行日志记录,然后将调用委托或路由到它

public static Object wrapWithDebugLogging(Object target) {
    ... // prepare the enhancer as described above
    enhancer.setCallback(new MethodInterceptor() {        
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            logger.debug("Some useful logging message.");
            return method.invoke(target, args);            
        }
    });
    return enhancer.create();
}

无论调用的方法是否为
final
,您都没有试图覆盖它,只是在拦截它。

您不应该根据是否为lambda生成类的问题来创建条件代码。毕竟,只有类的属性才重要

该类是final,因此您不能将其子类化。即使它不是
final
,子类也是不可能的,因为它只有
private
构造函数。它实现了一个
接口
。这些是类的相关属性

在没有任何lambda表达式的情况下遇到相同的场景并非不切实际:

final class NotALambda implements Function<String,String> {

    public static final Function<String,String> INSTANCE=new NotALambda();

    private NotALambda() {}

    public String apply(String t) {
        return t.toLowerCase();
    }
}
final类NotALambda实现函数{
public static final Function INSTANCE=new NotALambda();
私有NotALambda(){}
公共字符串应用(字符串t){
返回t.toLowerCase();
}
}
为什么要将此类与通过

函数f=String::toLowerCase?它在创建代理时具有相同的属性和相同的障碍。在你的评论中,你说你想根据方法是否被声明为final的问题做出改变。这就没有什么意义了,因为我可以在上面的示例中为方法添加一个
final
修饰符,而不改变任何内容,也不改变创建代理时所面临的语义和困难。

为什么不使用
modifier.isFinal(..)检查
Class
是否为
final
?如果我们对所有final类都使用此技巧,您可以找到一个final类,该类具有在final类本身上定义的final方法。在这种情况下,该方法不会由代理实现,因此如果有人调用它,您将得到一个NoSuchMethodError。出于这个原因,我们对最终的课程是保守的。也许这意味着我们应该检查所有方法都存在于超类或其中一个接口上的最终类。我不确定这是否是一个好的启发。不,这不是“遗忘”。它被故意忽略,因为它会违反语言的抽象。@BrianGoetz忽略它会违反反射API。所以你赢了一些,你输了一些,这是没有道理的。您不能对任何
final
类进行子类化,因此询问
final
类是否有
final
方法是毫无意义的。您不能覆盖其中任何一个。另一方面,当子类化超类或实现
final
类的接口时,无论当时不相关的
final
类是否将方法声明为
final
,都可以重新声明方法。如果你坚持自己的逻辑,很容易构造出既不匿名也不破坏代码的示例。这种输出将来可能会改变,也可能会被编译器或JVM实现改变。不要依赖它,我也考虑过toString()黑客,但JRE的未来版本可能会打破它。再一次,JRE的未来版本也破坏了isAnonymousClass(),所以谁知道什么是安全的…@Tre
final class NotALambda implements Function<String,String> {

    public static final Function<String,String> INSTANCE=new NotALambda();

    private NotALambda() {}

    public String apply(String t) {
        return t.toLowerCase();
    }
}