EA问题的JDK14实例
Tagir Valeev最近發布了一條有關即將發布的Java JDK14版本的預覽功能的推文:
#Java14模式匹配將名稱隱藏帶入了更高的瘋狂程度。 在這里,我為FLAG字段添加或刪除了final修飾符,該修飾符僅在不可達的if分支中訪問。 這實際上改變了程序的語義! #ProgrammingIsFun 。 pic.twitter.com/UToRY3mpW9
#Java14模式匹配將名稱隱藏帶入了更高的瘋狂程度。 在這里,我為FLAG字段添加或刪除了final修飾符,該修飾符僅在不可達的if分支中訪問。 這實際上改變了程序的語義! #ProgrammingIsFun 。 pic.twitter.com/UToRY3mpW9
-Tagir Valeev(@tagir_valeev) 2019年12月27日問題在于,有一個計劃中的并且在EA版本中已經可用的Java新功能引入了模式變量,而所提議的新標準的當前版本為某些真正令人毛骨悚然的編碼問題留出了空間。
鳴叫之后,對細節進行了足夠詳細的討論,以了解實際問題。 但是,在本文中,我將總結所有這些內容,以使您無需深入了解推文和標準。
什么是模式變量
在上面的推文中深入探討問題概述之前,讓我們先討論一下模式變量是什么。 (也許有點草率,比準確和完整更能說明問題,但是來了。)
進行多次編程后,我們需要檢查某些對象的類型。 運算符instanceof為我們做到了。 典型的示例代碼可以是這樣的:
// HOW THIS IS TODAY, JAVA < 14 Object z = "alma" ; if (!(z instanceof String)){ throw new IllegalArgumentException(); } System.out.println(((String)z).length());在現實生活中,變量z可能來自其他地方,在這種情況下,它是不那么明顯,這是一個字符串。 當我們想使用println打印出字符串的長度時,我們已經知道z引用的對象是一個字符串。 另一方面,編譯器則沒有,我們必須將變量強制轉換為String ,然后才能使用length()方法。 其他語言做得更好。 理想情況下,我可以寫:
// HOW IT WOULD BE THE SIMPLEST Object z = "alma" ; if (!(z instanceof String)){ throw new IllegalArgumentException(); } System.out.println(z.length());這不是Java方式,也不是JDK14簡化此編程模式的方式。 相反,建議的功能為instanceof運算符引入了一種新語法,該語法引入了一個新變量: 模式變量 。
長話短說,上面的示例如下所示:
// HOW IT IS IN JDK14-EA / OpenJDK (build 14-ea+28-1366) Object z = "alma" ; if (!(z instanceof String s)){ throw new IllegalArgumentException(); } System.out.println(s.length());它引入了一個新變量s ,僅當引用的對象是String時才在范圍內。 沒有異常拋出部分的簡單代碼版本是
Object z = "alma" ; if (z instanceof String s){ // we have here 's' and it is a String System.out.println(s.length()); } // we do not have 's' here當條件為true時,對象是字符串,因此我們有's'。 如果條件為假,那么我們將跳過then_statement,并且由于沒有字符串,所以這里沒有's'。 代碼中只有在對象為字符串時才運行“ s”。 這樣,模式變量的變量范圍不僅受變量的句法范圍的限制,而且還受可能的控制流程的限制。 僅考慮可以確定分析的控制流。
在Java編譯器中,這種控制流分析并非無與倫比。 例如,如果存在編譯器可以檢測到的無法訪問的代碼,則Java程序將不會編譯。
到目前為止,這似乎很簡單,我們都很高興獲得Java 14的新功能。
JSL14標準
精確的范圍計算在JLS14(Java語言規范14)標準中定義。 在撰寫本文時,該規范僅作為預覽提供。
http://cr.openjdk.java.net/~gbierman/jep305/jep305-20191021/specs/patterns-instanceof-jls.html#jls-6.3.2.2
由于Java程序的執行流程可以由許多不同的語言構造來控制,因此為每種結構定義了模式變量的范圍。 針對不同的邏輯運算符,有單獨的部分來評估短路,“ if”語句,“ while”語句等。 我不想廣泛討論不同的情況。 在這里,我將僅關注“ if”語句的情況,而沒有“ else”部分。 上面引用的標準說:
以下規則適用于“ if(e)S”(14.9.1)語句:
*當e為true時,由e引入的模式變量肯定與`S`相匹配。
如果`e`引入的任何模式變量true都已經在`S`的作用域內,則是編譯時錯誤。
*當且僅當`false`和`S'無法正常完成時,`if(e)S`引入`V`。
如果`if`語句引入的任何模式變量已經在范圍內,則是編譯時錯誤。
有趣的部分是“無法正常完成”。 上面的示例就是一個很好的例子:我們創建了一個所謂的guard if語句。 當變量z不是String我們將拋出異常,返回或執行其他操作,這將始終阻止在變量不是String的if語句之后執行代碼。
對于throw或return語句,通常很容易直接看出代碼“無法正常完成”。 在無限循環的情況下,這并不總是那么明顯。
問題
讓我們看一下以下代碼片段:
private static boolean FLAG = true ; static String variable = "Hello from field" ; public static void main() { Object z = "Hello from pattern matching" ; if (!(z instanceof String variable)){ while (FLAG) { System.out.println( "We are in an endless loop" ); } } System.out.println(variable); }在這種情況下,我們有一個循環,它是無限的或不是無限的。 這取決于代碼的另一部分,這可能會將類字段FLAG的值從true更改為false 。 這部分代碼“可以正常完成”。
如果我們修改上面的代碼,只需稍微使字段FLAG為final ,如
private static final boolean FLAG = true ; static String variable = "Hello from field" ; public static void main() { Object z = "Hello from pattern matching" ; if (!(z instanceof String variable)){ while (FLAG) { System.out.println( "We are in an endless loop" ); } } System.out.println(variable); }那么編譯器將看到循環是無限的并且無法正常完成。 在第一種情況下,程序Hello from field中打印出Hello from field Hello from pattern matching打印Hello from pattern matching 。 第二種情況下的模式variable隱藏了字段variable因為模式變量的范圍擴展到了if語句之后的命令,因為那么部分無法正常完成。
確實,此預覽功能確實存在問題。 在這種情況下,代碼的可讀性非常可疑。 模式變量的范圍以及是否隱藏字段取決于該字段的final修飾符,該修飾符不存在。 當我們查看某些代碼時,實際的執行和代碼結果應該很簡單,并且不應真正依賴于距離很遠的某些代碼,并且可能會跳過我們在本地閱讀代碼的注意。
這不是Java中唯一出現此異常的情況。 例如,您的代碼庫中可以有一個名為String的類。 當它們引用String類型時,位于同一包中的類的代碼將使用該類。 如果我們從用戶代碼中刪除String類,則String類型的含義變為java.lang.String 。 該代碼的實際含義取決于“遠”的其他代碼。
但是,第二個示例是一個黑客,沒有失去主意的Java程序員不可能將String類命名為(嚴重https://github.com/verhas/jScriptBasic/blob/master/src/main/ java / com / scriptbasic / classification / String.java ?)或JDK中java.lang包中也存在的其他名稱。 也許這是純粹的運氣,也許在決策過程中考慮到了避免從java.lang包中強制導入類的java.lang 。 這是歷史。
另一方面,變量名隱藏和上面的情況似乎并不那么怪異,某些Java代碼中肯定不會偶然發生某些事情。
幸運的是,這只是預覽功能。 它將按原樣出現在JDK14中,但是作為預覽功能,僅當javac編譯器和Java執行使用--enable-preview標志并且預覽功能將來可能以不兼容的方式更改時,該功能才可用。
解
我不知道它將如何改變。 我什至不能說它會改變。 僅憑我個人的看法,如果仍然這樣下去將是非常可悲的。 有了此功能,只要我們計算經驗豐富的Java程序員可以編寫的程序的精妙程度和可讀性,Java就會是更好的語言。 但是,如果我們看看沒有經驗的,新鮮的初級人員如何弄糟代碼,情況將會更糟。 以我的拙見,第二點更為重要,而Java在這方面有很強的優勢。 Java不是一種黑客語言,您應該非常拼命編寫一個非常不可讀的代碼。 我不希望它改變。
說完之后,我們可以看看技術上的可能性。 一種是放棄該功能,這實際上不是一個好的解決方案。 這實際上不是解決方案。
另一種可能性是將模式變量的范圍限制為then語句或else語句。
就個人而言,我希望綁定變量范圍僅適用于顯式聲明的else塊,而不適用于這種情況下的隱式塊。
-Michael Rasmussen(@jmichaelras) 2019年12月27日這樣,我們就不會依賴代碼的“無法正常完成”功能。 else保證只有在if語句的條件為false時才執行else分支。 這將使解決方案不太優雅。
同樣,另一種可能性是禁止模式變量遮蓋任何字段變量。 它可以解決上面概述的問題,但是會引入一個不同的問題。 受此限制,當我們引入一個名為V的新字段變量時,可能會發生帶有方法和模式變量V的現有類停止編譯的情況。 至少此問題是編譯時的問題,而不是運行時有問題的某些代碼。
我寧愿有100個編譯時錯誤,也不愿有1個運行時錯誤。
還有一種可能是放棄模式變量,而僅使用原始變量和擴展的類型信息,而當前的預覽解決方案使用模式變量。 Kotlin粉絲會喜歡這種解決方案。 由于局部變量已經遮蔽(或不遮蓋)字段變量,因此這也可以很好地消除陰影問題。 此解決方案的缺點是,重新作用域限定的變量類型在代碼的不同位置將具有不同的類型。 讓我們看下面的代碼:
package javax0.jdk14.instanceof0; public class Sample2 { public static class A { public static void m(){ System.out.println( "A" ); } } public static class B extends A { public static void m(){ System.out.println( "B" ); } } public static void main(String[] args) { A a = new B(); if ( a B b){ ( a instanceof B b){ bm(); } am(); } }此代碼將先打印出B然后打印出A因為根據變量b的聲明類型,對bm()的調用與Bm()相同,并且根據聲明的類型,對am()與Am()的相同方法的變量a 。 省略模式變量并使用原始變量可能會造成混淆:
// NOT ACTUAL CODE public static void main(String[] args) { A a = new B(); if ( a B){ ( a instanceof B){ am(); } am(); }am()會在不同的行上調用不同的方法嗎?
如您所見,沒有已知的最佳或最佳解決方案……除了一個。 在JDK中稱您的代表為“議會”,并告訴他們那樣不好。 (Psst:他們已經從原始推文中知道了。)
帶走
這是一篇特別的文章,因為這與某些完善的Java功能或某些良好的編程工具或樣式,模式,方法無關。 我們討論了預覽功能。 預覽功能也許證明了我們為什么需要Java中的預覽功能。
對于需要長期支持的長期商業項目,請使用最新的LTS版本。
將最新發布的Java版本用于您的實驗和開源項目,并在用戶需要時準備支持較舊的Java版本。
不要在項目中使用預覽功能,也不要準備從代碼中獲得新版本,以防它們在變為非預覽但正常功能時在下一個Java版本中發生更改。
嘗試使用預覽功能以將其包含在內,并在它們成為真實功能時具有某種肌肉記憶。 并且還可以向Java社區提供反饋,以防您覺得它們不是很完美。
翻譯自: https://www.javacodegeeks.com/2020/01/jdk14-instance-of-ea-issue.html
總結
以上是生活随笔為你收集整理的EA问题的JDK14实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让你的声音录制达到新高度 - 雅马哈UR
- 下一篇: 淘宝电脑首页尺寸(淘宝电脑首页尺寸怎么设