簡體   English   中英

有沒有辦法在 jshell 中對頂級函數使用方法引用?

[英]Is there a way to use method references for top-level functions in jshell?

假設我在 jshell 中執行此操作:

jshell> void printIsEven(int i) {
   ...>     System.out.println(i % 2 == 0);
   ...> }
|  created method printIsEven(int)

jshell> List<Integer> l = Arrays.asList(7,5,4,8,5,9);
l ==> [7, 5, 4, 8, 5, 9]

jshell> l.forEach(/* ??? */); // is it possible to use a method reference here?

在普通程序中,我可以在非靜態上下文中編寫l.forEach(this::printIsEven)或在名為MyClass的類的靜態上下文中l.forEach(MyClass::printIsEven)

在 jshell 中使用this::printIsEven不起作用,因為 jshell 在靜態上下文中執行語句,但您不能使用靜態方法引用,因為沒有類名前綴::printIsEven ,並嘗試l.forEach(::printIsEven)只是一個語法錯誤。

您可以為此創建一個新類:

jshell> class Foo { static void printIsEven(int i) {
   ...>     System.out.println(i % 2 == 0);
   ...> }}
|  created class Foo

jshell> Arrays.asList(1,2,3).forEach(Foo::printIsEven)
false
true
false

從技術上講,它不再是頂級功能,而是達到了預期的效果。

現在,如果您知道這一點並且仍然想引用頂級方法......

據我所知,保存外殼“狀態”的“頂級類”是jdk.jshell.JShell ,但jdk.jshell.JShell::printIsEven導致Error: invalid method reference 而且您已經提到不可能使頂級方法成為靜態( Modifier 'static' not permitted in top-level declarations, ignored )。

快速瀏覽JEP 后,似乎是有意為之。 它實際上提到了上面的“在新類中定義靜態方法”的方法

頂級“類”需要特殊的魔法才能重新定義方法和其他頂級聲明,並且這些限制可能源於 JVM 自身在運行時重新定義類/方法的能力的限制。 來源很有趣,但我無法從中得出有意義的答案。


編輯:所以,我有點得意忘形了。 這是你的錯。
我仍然認為不可能在 jshell 中獲得對頂級方法的方法引用,但是......我之前對原因的猜測可能是錯誤的。

下面顯示了在 jshell 中,當您“重新定義”一個類時,舊類仍然存在:評估上下文只是移動了一些映射以解析對新類定義的進一步引用。

jshell> class A { static int v=1; void m() { System.out.println(getClass() + " v=" + v); } }
|  created class A

jshell> new A().m()
class REPL.$JShell$11$A v=1

// Changing static value of "v"
jshell> class A { static int v=2; void m() { System.out.println(getClass() + " v=" + v); } }
|  modified class A

// Actually not modified, this is still the same class (and as a result the static init of v has not been reexecuted, so, still 1)
jshell> new A().m()
class REPL.$JShell$11$A v=1

// Let's add a boolean field to change the structure
jshell> class A { static int v=3; boolean x=false; void m() { System.out.println(getClass() + " v=" + v); } }
|  replaced class A

// Notice new class name:
jshell> new A().m()
class REPL.$JShell$11B$A v=3

// But old version is still there, only hidden a bit by evaluation context:
jshell> Class.forName("REPL.$JShell$11$A").getDeclaredField("v").getInt(null)
$7 ==> 1

jshell> Class.forName("REPL.$JShell$11B$A").getDeclaredField("v").getInt(null)
$8 ==> 3

所以這個小演示表明它與用於類重新定義的 JVM 內部無關,因為這里沒有發生這樣的事情。

然后我想查看所有加載的類的列表:

jshell> Class c = Thread.currentThread().getContextClassLoader().getClass()
c ==> class jdk.jshell.execution.DefaultLoaderDelegate$RemoteClassLoader

jshell> while (c != java.lang.ClassLoader.class) { c = c.getSuperclass(); System.out.println(c); }
class java.net.URLClassLoader
class java.security.SecureClassLoader
class java.lang.ClassLoader

jshell> c.getDeclaredField("classes").setAccessible(true)
|  java.lang.reflect.InaccessibleObjectException thrown: Unable to make field private final java.util.Vector java.lang.ClassLoader.classes accessible: module java.base does not "opens java.lang" to unnamed module @7494e528
|        at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:337)
|        at AccessibleObject.checkCanSetAccessible (AccessibleObject.java:281)
|        at Field.checkCanSetAccessible (Field.java:175)
|        at Field.setAccessible (Field.java:169)
|        at (#26:1)

啊,是的,Java 9 模塊......該死的 :)

哦,好吧,今天就到此為止。

充分意識到陳述一個明顯事情的風險,與將它們包裝在類中相比,有一種更簡單的方法來引用頂級函數聲明。

jshell> void printIsEven(int i) {
...>     System.out.println(i % 2 == 0);
...> }
|  created method printIsEven(int)

jshell> List<Integer> l = Arrays.asList(7,5,4,8,5,9);
l ==> [7, 5, 4, 8, 5, 9]

jshell> l.forEach(i -> printIsEven(i)); // lambdas can refer to top-level declarations

這不是被問到的,但這只是使用例如流 API 的一種方便的方式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM