3

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

interface A {
    int c();
}

interface B {
    int c();
}

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

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

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

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

4

2 回答 2

2

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

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

因此,如果您能够在调用站点上进行构建时破解(而不是以某种方式将注释本身标记为非 lambda-able,我认为您不能这样做),那么您应该能够首先编译一组不允许的接口,然后invokedynamic对照该组检查 's desc。

于 2017-11-30T06:21:57.917 回答
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 类型的最小工具就是将默认实现添加到其抽象方法中。

于 2017-11-30T06:37:26.290 回答