自动装箱,拆箱和NoSuchMethodError
J2SE 5為Java編程語(yǔ)言引入了許多功能。 這些功能之一是自動(dòng)裝箱和拆箱 ,這是我?guī)缀趺刻於紱](méi)有考慮過(guò)的功能。 它通常很方便(尤其是與收藏夾一起使用時(shí)),但有時(shí)會(huì)導(dǎo)致一些令人討厭的驚喜 ,即“ 怪異 ”和“ 瘋狂” 。 在此博客文章中,我介紹了一種罕見(jiàn)的NoSuchMethodError案例(但對(duì)我來(lái)說(shuō)很有趣),該案例是由于在自動(dòng)裝箱/拆箱之前將使用Java版本編譯的類與包含自動(dòng)裝箱/取消裝箱的Java版本編譯的類混合在一起而造成的。
下一個(gè)代碼清單顯示了一個(gè)簡(jiǎn)單的Sum類,該類可以在J2SE 5之前編寫(xiě)。它已重載了“ add”方法,這些方法接受不同的原始數(shù)值數(shù)據(jù)類型,并且Sum>每個(gè)實(shí)例Sum>簡(jiǎn)單地添加了通過(guò)以下任何一種方式提供給它的所有數(shù)字類型其重載的“添加”方法。
Sum.java(J2SE 5之前的版本)
import java.util.ArrayList;public class Sum {private double sum = 0;public void add(short newShort){sum += newShort;}public void add(int newInteger){sum += newInteger;}public void add(long newLong){sum += newLong;}public void add(float newFloat){sum += newFloat;}public void add(double newDouble){sum += newDouble;}public String toString(){return String.valueOf(sum);} }在無(wú)法進(jìn)行拆箱之前,上述Sum類的所有客戶端都需要向這些“添加”方法提供原語(yǔ),或者,如果它們具有與原語(yǔ)相同的引用,則需要在將其中一個(gè)引用稱為“添加”方法。 在調(diào)用這些方法之前,在客戶端代碼上有責(zé)任從引用類型轉(zhuǎn)換為相應(yīng)的原始類型。 下一個(gè)代碼清單中顯示了如何完成此操作的示例。
不取消裝箱:客戶端將引用轉(zhuǎn)換為基元
private static String sumReferences(final Long longValue, final Integer intValue, final Short shortValue) {final Sum sum = new Sum();if (longValue != null){sum.add(longValue.longValue());}if (intValue != null){sum.add(intValue.intValue());}if (shortValue != null){sum.add(shortValue.shortValue());}return sum.toString(); }J2SE 5的自動(dòng)裝箱和拆箱功能旨在解決這種情況下所需的額外工作 。 通過(guò)取消裝箱,客戶端代碼可以使用與預(yù)期的基本類型相對(duì)應(yīng)的引用類型來(lái)調(diào)用上述“添加”方法,并且這些引用將自動(dòng)“取消裝箱”為原始形式,以便可以調(diào)用適當(dāng)?shù)摹疤砑印狈椒ā?Java語(yǔ)言規(guī)范的 第5.1.8節(jié) (“取消裝箱轉(zhuǎn)換”)說(shuō)明了提供的數(shù)字引用類型在取消裝箱中將轉(zhuǎn)換為哪些原語(yǔ),該規(guī)范的 第5.1.7節(jié) (“裝箱轉(zhuǎn)換”)列出了自動(dòng)裝箱的引用類型。從自動(dòng)裝箱中的每個(gè)原語(yǔ)。
在此示例中,在調(diào)用Sum的“ add”方法之前,將引用類型轉(zhuǎn)換為對(duì)應(yīng)的原始對(duì)等類型,從而使客戶方面的拆箱工作減少了,但并沒(méi)有使客戶完全不必在提供它們之前處理數(shù)字值。 因?yàn)橐妙愋涂梢詾閚ull ,所以客戶端可以為Sum的“ add”方法之一提供null引用,并且當(dāng)Java嘗試自動(dòng)將null取消裝箱到其對(duì)應(yīng)的原語(yǔ)時(shí),將引發(fā)NullPointerException 。 下一個(gè)代碼清單從上面進(jìn)行了改編,以指示在客戶端不再需要將引用轉(zhuǎn)換為原語(yǔ),但是仍然需要檢查null以避免NullPointerException 。
自動(dòng)取消裝箱秘密對(duì)原始的引用:仍然必須檢查是否為空
private static String sumReferences(final Long longValue, final Integer intValue, final Short shortValue) {final Sum sum = new Sum();if (longValue != null){sum.add(longValue);}if (intValue != null){sum.add(intValue);}if (shortValue != null){sum.add(shortValue);}return sum.toString(); }在設(shè)計(jì)API時(shí),可能需要避免客戶端代碼在Sum上調(diào)用“ add”方法之前檢查其引用是否為null。 消除需求的一種方法是更改??“添加”方法以顯式接受引用類型,而不是原始類型。 然后, Sum類可以在顯式或隱式(取消裝箱)對(duì)它進(jìn)行解引用之前檢查null。 接下來(lái)顯示了經(jīng)過(guò)修改的Sum類,其中包含已更改的,更易于客戶端使用的API。
用“ add”方法求和的類期望引用而不是基元
import java.util.ArrayList;public class Sum {private double sum = 0;public void add(Short newShort){if (newShort != null){sum += newShort;}}public void add(Integer newInteger){if (newInteger != null){sum += newInteger;}}public void add(Long newLong){if (newLong != null){sum += newLong;}}public void add(Float newFloat){if (newFloat != null){sum += newFloat;}}public void add(Double newDouble){if (newDouble != null){sum += newDouble;}}public String toString(){return String.valueOf(sum);} }修改后的Sum類對(duì)客戶端更友好,因?yàn)樗试S客戶端將引用傳遞給它的任何“ add”方法,而不必?fù)?dān)心傳入的引用是否為null。 但是,如果涉及的任何一個(gè)類(客戶端類或Sum類的一個(gè)版本)都使用不同版本的Java編譯,則像這樣對(duì)Sum類的API進(jìn)行更改可能會(huì)導(dǎo)致NoSuchMethodError 。 特別是,如果客戶端代碼使用原語(yǔ)并且使用JDK 1.4或更早版本進(jìn)行編譯,并且Sum類是所示的最新版本(期望使用引用代替原語(yǔ))并且使用J2SE 5或更高版本進(jìn)行了編譯,則將遇到類似以下內(nèi)容的NoSuchMethodError (“ S”表示它是“ add”方法,它期望原始short ,而“ V”表示該方法返回void )。
Exception in thread "main" java.lang.NoSuchMethodError: Sum.add(S)Vat Main.main(Main.java:9)另一方面,如果客戶端使用J2SE 5或更高版本進(jìn)行編譯,并且如第一個(gè)示例中那樣將原始值提供給Sum (預(yù)拆箱),并且Sum類在JDK 1.4或更早版本中使用“ add”方法進(jìn)行編譯原語(yǔ),會(huì)遇到不同版本的NoSuchMethodError 。 請(qǐng)注意,此處引用了Short參考。
Exception in thread "main" java.lang.NoSuchMethodError: Sum.add(Ljava/lang/Short;)Vat Main.main(Main.java:9)由此可見(jiàn)對(duì)Java開(kāi)發(fā)人員的一些觀察和提醒。
- 類路徑很重要:
- 使用相同版本的Java(相同的-source和-target )編譯的Java .class文件可以避免本文中的特定問(wèn)題。
- 自動(dòng)裝箱和取消裝箱的目的是很好的,并且通常非常方便,但是如果在一定程度上不牢記,可能會(huì)導(dǎo)致令人驚訝的問(wèn)題。 在這篇文章中,仍然需要檢查空值(或知道對(duì)象不是空值),這是由于拆箱而導(dǎo)致隱式解引用的情況。
- 是否允許客戶端傳遞null并讓服務(wù)類代表它們檢查null是API風(fēng)格的問(wèn)題。 在工業(yè)應(yīng)用程序中,我將用每個(gè)方法的Javadoc注釋中的@param聲明每個(gè)“添加”方法參數(shù)是否允許為null。 在其他情況下,可能要讓調(diào)用者負(fù)責(zé)確保任何傳入的引用都不為空,并且如果調(diào)用者不遵守該約定,則拋出NullPointerException內(nèi)容將是滿意的(也應(yīng)在方法的Javadoc)。
- 盡管通常會(huì)在完全刪除某個(gè)方法或在該方法可用之前訪問(wèn)舊類或方法的API在類型或類型數(shù)方面發(fā)生更改時(shí)看到NoSuchMethodError 。 在Java自動(dòng)裝箱和拆箱在很大程度上被視為理所當(dāng)然的日子里,很容易想到將方法從采用原語(yǔ)轉(zhuǎn)換為采用相應(yīng)的引用類型不會(huì)產(chǎn)生任何影響,但是即使這種更改也會(huì)導(dǎo)致異常,如果并非所有涉及的類都基于支持自動(dòng)裝箱和拆箱的Java版本構(gòu)建。
- 確定要針對(duì)其編譯特定.class文件的Java版本的一種方法是使用javap -verbose并在javap輸出中查找“主要版本:”。 在本文示例中使用的類(針對(duì)JDK 1.4和Java SE 8編譯)中,“ 主要版本 ”條目分別為48和52( Java類文件上Wikipedia條目的“ 常規(guī)布局”部分列出了主要版本) )。
幸運(yùn)的是,由于構(gòu)建通常會(huì)清理所有工件并在相對(duì)連續(xù)的基礎(chǔ)上重建代碼,因此本文中使用示例和文本演示的問(wèn)題并不常見(jiàn)。 但是,在某些情況下可能會(huì)發(fā)生這種情況,最可能的情況之一是意外使用舊的JAR文件時(shí),因?yàn)樗挥谶\(yùn)行時(shí)類路徑上的等待中。
翻譯自: https://www.javacodegeeks.com/2014/08/autoboxing-unboxing-and-nosuchmethoderror.html
總結(jié)
以上是生活随笔為你收集整理的自动装箱,拆箱和NoSuchMethodError的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 介绍JBoss BPM Suite安装程
- 下一篇: 这是东西:jUnit:动态测试生成