在某些接口上限制lambdas

时间:2017-11-30 04:38:11

标签: java jvm jvm-bytecode

假设我有几个接口只有一个抽象方法。有了这些接口,我可以用它声明lambdas:

interface A {
    int c();
}

interface B {
    int c();
}

public class Main {
    public static void main(String... args) {
        A a = () -> 42;
        B b = () -> 42;
    }
}

简短的问题:是否有一些技巧或黑客限制使用接口A为lambda,并尝试这样做失败?任何暗示,无论是否肮脏,都是受欢迎的(肮脏的'我的意思是编译/字节码级别的黑客攻击 - 不会影响来源,最好是公共合同)。

长篇小说:对于某些接口实现者,我考虑将equals/hashCode定义为合同的一部分。另外,我会在构建时自动为它们生成equals/hashCode

在这种情况下,lambdas是麻烦制造者。对于接口A的普通和匿名实现者,我可以找到.class文件并在构建时检测其字节码。对于lambdas,有一个VM-anonymous类,在运行时生成。在构建时似乎不可能影响这样的类,所以我需要至少禁止这些场合用于特定的接口集。

2 个答案:

答案 0 :(得分:2)

从玩一下,看起来desc调用的invokedynamic字段包含正在实现的界面。例如,当我创建一个简单的() -> {} Runnable然后通过ASM's Bytecode Outline插件传递它时," ASM-ified"电话看起来像:

mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle...

因此,如果您能够在呼叫站点上执行构建时黑客攻击(而不是以某种方式将注释本身标记为非lambda-able,我认为您无法做到)那么你应该能够首先编译一组不允许的接口,然后检查invokedynamic对该集合的描述。

答案 1 :(得分:2)

请看一下我的解决方案:

package com.example.demo;

public class LambdaDemo {

    public static void main(String[] args) {
        //doesn't compile
        //LambdaRestrictedInterface x = () -> {};
        LambdaRestrictedInterface y = new Test();
        y.print();
    }

    private static class Test implements LambdaRestrictedInterface {
        @Override
        public void print() {
            System.out.println("print");
        }
    }

    public interface MyInterface {
        void print();
    }

    public interface LambdaRestrictedInterface extends MyInterface {
        @Override
        default void print() {
            //hack prevents lambda instantiating
        }
    }
}

https://dumpz.org/2708733/

想法是使用默认impl

覆盖父接口

从发起人编辑:经过一番考虑后,我决定接受这个答案,(因为它最符合我的需求并且实施起来相当便宜),并附带一些正式的补充。事实上,人们意识到,足以阻止接口被用作lambda类型的 minimal 检测只是将默认实现添加到其抽象方法中。