限制某些接口上的 lambda
Restrict lambdas on certain interfaces
假设我有几个接口只有一个抽象方法。有了这些接口,我可以用它来声明 lambda:
interface A {
int c();
}
interface B {
int c();
}
public class Main {
public static void main(String... args) {
A a = () -> 42;
B b = () -> 42;
}
}
简短的问题:是否有一些技巧或 hack 来限制对 lambdas 使用接口 A
并在尝试这样做时构建失败?欢迎任何暗示,无论是否肮脏('dirty' 我的意思是 compilation/bytecode 级别的黑客攻击 - 这不会影响来源,最好是 public 合同)。
长话短说:对于某些接口实现者,我考虑将 equals/hashCode
定义为契约的一部分。另外,我在构建时自动为他们生成 equals/hashCode
。
在这种情况下,lambda 是麻烦制造者。对于接口 A
的普通和匿名实现者,我可以找到一个 .class
文件并在构建时检测其字节码。对于 lambda,有 VM-anonymous class,在 运行 时间生成。在构建时影响这样的 class 似乎是不可能的,所以我至少需要为一组特定的接口禁止这种情况。
经过一番尝试,invokedynamic
调用的 desc
字段似乎包含正在实现的接口。例如,当我创建一个简单的 () -> {}
Runnable 然后通过 ASM's Bytecode Outline 插件传递它时,"ASM-ified" 调用看起来像:
mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle...
因此,如果您能够在调用站点上执行构建时 hack(而不是以某种方式将注释本身标记为 non-lambda-able,我认为您做不到),那么您应该能够首先编译一组不允许的接口,然后根据该组检查 invokedynamic
的描述。
请看看我的解决方案:
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
}
}
}
想法是用默认实现覆盖父接口
来自发起人的编辑: 经过一番考虑,我决定接受这个答案(因为它最适合我的需要并且实施起来相当便宜)并添加了一些正式的内容。事实上,已经意识到足以防止接口被用作 lambda 类型的 minimal 检测只是将默认实现添加到其抽象方法。
假设我有几个接口只有一个抽象方法。有了这些接口,我可以用它来声明 lambda:
interface A {
int c();
}
interface B {
int c();
}
public class Main {
public static void main(String... args) {
A a = () -> 42;
B b = () -> 42;
}
}
简短的问题:是否有一些技巧或 hack 来限制对 lambdas 使用接口 A
并在尝试这样做时构建失败?欢迎任何暗示,无论是否肮脏('dirty' 我的意思是 compilation/bytecode 级别的黑客攻击 - 这不会影响来源,最好是 public 合同)。
长话短说:对于某些接口实现者,我考虑将 equals/hashCode
定义为契约的一部分。另外,我在构建时自动为他们生成 equals/hashCode
。
在这种情况下,lambda 是麻烦制造者。对于接口 A
的普通和匿名实现者,我可以找到一个 .class
文件并在构建时检测其字节码。对于 lambda,有 VM-anonymous class,在 运行 时间生成。在构建时影响这样的 class 似乎是不可能的,所以我至少需要为一组特定的接口禁止这种情况。
经过一番尝试,invokedynamic
调用的 desc
字段似乎包含正在实现的接口。例如,当我创建一个简单的 () -> {}
Runnable 然后通过 ASM's Bytecode Outline 插件传递它时,"ASM-ified" 调用看起来像:
mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle...
因此,如果您能够在调用站点上执行构建时 hack(而不是以某种方式将注释本身标记为 non-lambda-able,我认为您做不到),那么您应该能够首先编译一组不允许的接口,然后根据该组检查 invokedynamic
的描述。
请看看我的解决方案:
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
}
}
}
想法是用默认实现覆盖父接口
来自发起人的编辑: 经过一番考虑,我决定接受这个答案(因为它最适合我的需要并且实施起来相当便宜)并添加了一些正式的内容。事实上,已经意识到足以防止接口被用作 lambda 类型的 minimal 检测只是将默认实现添加到其抽象方法。