我有以下人為的代碼示例。為了使位元組碼保持較小,它沒有任何用處,但希望您可以通過一些更改看到它是如何實作的。
List<String> letters = Arrays.asList("a", "b");
Stream.of(/*a, b, c, d*/).filter(letters::contains).toArray(String[]::new);
Java 8 生成以下位元組碼
public Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=1
start local 0 // Main this
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: iconst_2
5: anewarray #2 // class java/lang/String
8: dup
9: iconst_0
10: ldc #3 // String a
12: aastore
13: dup
14: iconst_1
15: ldc #4 // String b
17: aastore
18: invokestatic #5 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
21: astore_1
start local 1 // java.util.List letters
22: iconst_0
23: anewarray #6 // class java/lang/Object
26: invokestatic #7 // InterfaceMethod java/util/stream/Stream.of:([Ljava/lang/Object;)Ljava/util/stream/Stream;
29: aload_1
30: dup
31: invokevirtual #8 // Method java/lang/Object.getClass:()Ljava/lang/Class;
34: pop
35: invokedynamic #9, 0 // InvokeDynamic #0:test:(Ljava/util/List;)Ljava/util/function/Predicate;
40: invokeinterface #10, 2 // InterfaceMethod java/util/stream/Stream.filter:(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;
45: invokedynamic #11, 0 // InvokeDynamic #1:apply:()Ljava/util/function/IntFunction;
50: invokeinterface #12, 2 // InterfaceMethod java/util/stream/Stream.toArray:(Ljava/util/function/IntFunction;)[Ljava/lang/Object;
55: pop
56: return
end local 1 // java.util.List letters
end local 0 // Main this
我對這個特別感興趣
30: dup
31: invokevirtual #8 // Method java/lang/Object.getClass:()Ljava/lang/Class;
34: pop
這實際上相當于將代碼更改為
List<String> letters = Arrays.asList("a", "b");
letters.getClass(); // inserted
Stream.of().filter(letters::contains).toArray(String[]::new);
在 Java 9 中,這已更改為對Objects.requireNonNull
.
30: dup
31: invokestatic #8 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
34: pop
我想我看到了這兩個點:如果方法參考參考的變數為空,則生成 NullPointerException。如果letters
為 null,則呼叫getClass()
它會拋出例外,從而使下一個取消參考安全。
根據檔案,invokedynamic
(用于呼叫contains
)本身不能拋出 NPE:“總之,這些不變數意味著系結到呼叫站點物件的呼叫動態指令永遠不會拋出 NullPointerException”,因此編譯器可能插入其他可以預先提供保證的東西。
但是,在這種情況下,變數實際上是最終的,并且包含建構式呼叫的結果。我相信它保證非空。是否可以跳過這種情況的檢查只是一個不存在的編譯器優化,或者我錯過了一些邊緣情況?
我問的是一個具體的、實際的原因。我正在使用 AspectJ 來編織 javac 的位元組碼,而 AspectJ 似乎正在“優化”這 3 條指令,我猜是因為它認為它們什么都不做。這個專案使用的是Java 8。我沒有檢查它是否被洗掉了9 。
在我上面展示的情況下,也許洗掉是好的,因為參考不能為空,但我看到在我們的代碼庫中發生了數百個這種情況,并且很難詳盡地證明它們都是安全的。
invokedynamic
如果參考為空,通過 AspectJ 修改位元組碼的結果,會有什么行為?不明確的?
uj5u.com熱心網友回復:
確實,Object.getClass()
用于發出NullPointerException
.
較新的 Java 版本使用Objects.requireNonNull
我稍微減少了復制器,并添加了另一種方法來顯示差異:
import java.util.List;
import java.util.function.Predicate;
public class LambdaTest {
public static Predicate<String> test1ref(List<String> letters) {
return letters::contains;
}
public static Predicate<String> test2lambda(List<String> letters) {
return s -> letters.contains(s);
}
}
使用 Java 17 編譯時,javap -p -c LambdaTest
輸出以下內容:
Compiled from "LambdaTest.java"
public class LambdaTest {
public LambdaTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static java.util.function.Predicate<java.lang.String> test1ref(java.util.List<java.lang.String>);
Code:
0: aload_0
1: dup
2: invokestatic #7 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
5: pop
6: invokedynamic #13, 0 // InvokeDynamic #0:test:(Ljava/util/List;)Ljava/util/function/Predicate;
11: areturn
public static java.util.function.Predicate<java.lang.String> test2lambda(java.util.List<java.lang.String>);
Code:
0: aload_0
1: invokedynamic #17, 0 // InvokeDynamic #1:test:(Ljava/util/List;)Ljava/util/function/Predicate;
6: areturn
private static boolean lambda$test2lambda$0(java.util.List, java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokeinterface #18, 2 // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z
7: ireturn
}
兩種方法的行為相似——一種使用直接參考,另一種被脫糖為私有方法——這在這里無關緊要。
如果test2lambda
使用null
作為引數呼叫,它將成功創建 lambda - 并且僅NullPointerException
在有人呼叫Predicate
'test()
方法時才會失敗。
相反,test1ref
會提前失敗——因為系結到null
物件并不是很有用。
但它的行為類似于test2lambda
如果null
省略檢查 - “成功”創建 aPredicate
只會拋出NullPointerException
s - 盡管堆疊跟蹤指向 lambda 的使用站點。
我們可以通過創建這樣一個我們自己的 lambda 來測驗這一點:
import java.lang.invoke.*;
import static java.lang.invoke.MethodType.methodType;
import java.util.List;
import java.util.function.Predicate;
public class DirectRef {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle target = l.findVirtual(List.class, "contains", methodType(boolean.class, Object.class));
CallSite cs = LambdaMetafactory.metafactory(l, "test", methodType(Predicate.class, List.class),
methodType(boolean.class, Object.class), target, methodType(boolean.class, String.class));
@SuppressWarnings("unchecked")
Predicate<String> pred = (Predicate<String>) cs.dynamicInvoker().invokeExact((List<?>) null);
pred.test("foo"); // Line 14
}
}
運行此程式時,出現以下例外:
Exception in thread "main" java.lang.NullPointerException
at DirectRef.main(DirectRef.java:14)
確實,它是指向謂詞的使用地點。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/521550.html