这是一个最小的工作示例(需要 Java 22 或更高版本):
(仅使用 libcfree
来详细说明行为)
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
public class Main {
public static final Linker nativeLinker = Linker.nativeLinker();
public static final SymbolLookup stdlibLookup = nativeLinker.defaultLookup();
public static final SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
private static final FunctionDescriptor DESCRIPTOR$free = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS);
private static final MethodHandle HANDLE$free =
loaderLookup.find("free")
.or(() -> stdlibLookup.find("free"))
.map(symbolSegment -> nativeLinker.downcallHandle(symbolSegment, DESCRIPTOR$free))
.orElseThrow(() -> new RuntimeException("libc function free not found but y?"));
public static void free(MemorySegment address) {
try {
// I want to allow user code to use Java null and MemorySegment.NULL (C NULL) interchangeably
HANDLE$free.invokeExact(address != null ? address : MemorySegment.NULL);
} catch (Throwable throwable) {
throwable.printStackTrace(System.err);
}
}
public static void main(String[] args) {
free(null); // free(MemorySegment.NULL) will produce same result
}
}
运行程序,会出现异常:
java.lang.invoke.WrongMethodTypeException: handle's method type (MemorySegment)void but found (Object)void
at java.base/java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:521)
at java.base/java.lang.invoke.Invokers.checkExactType(Invokers.java:530)
at Main.free(Main.java:19)
at Main.main(Main.java:26)
Java 抱怨调用时使用的参数invokeExact
是Object
,而不是MemorySegment
。但是,表达式
address != null ? address : MemorySegment.NULL
当然应该有类型MemorySegment
。如果我们对代码进行一点修改:
public static void free(MemorySegment address) {
try {
MemorySegment temp = address != null ? address : MemorySegment.NULL;
HANDLE$free.invokeExact(temp);
} catch (Throwable throwable) {
throwable.printStackTrace(System.err);
}
}
它工作正常。
还
HANDLE$free.invoke(address != null ? address : MemorySegment.NULL)
作品HANDLE$free.invokeExact((MemorySegment)(address != null ? address : MemorySegment.NULL))
有效,但是HANDLE$free.invokeExact((address != null ? address : MemorySegment.NULL))
不起作用
这是某种刻意的设计、实现限制还是 JVM 错误?
请注意,由于
invokeExact
是多态签名,因此编译器的工作是根据参数表达式确定您尝试调用的方法的方法类型。如果编译器发现不匹配的方法类型,则表达式在运行时求值的类型无关紧要。实际上,条件表达式(三元运算符表达式)的类型是
Object
。从 JLS 15.25.3开始,
如果是
条件运算符的目标类型是
Object
,如invokeExact
声明为 takeObject...
。invoke
比 更宽松invokeExact
,因此使用invoke
有效。在您所展示的所有其他情况下,参数表达式的类型都可以轻松证明是
MemorySegment
,因此它们也可以起作用。