Java的精妙之处,包括基元和变量参数数组
在我最近的博客文章Arrays.hashCode()與 DZone聯合版本的評論中提出了一個有趣的問題。 Objects.hash() “。 該評論的作者建立了一些示例,這些示例與我的博客文章中使用的示例相似,并且顯示出與我看到的結果不同的結果。 感謝評論作者抽出寶貴的時間來發表這篇文章,因為它帶來了Java的細微差別,我認為這很值得寫博客。
評論作者顯示了以下有效的Java語句:
int[] arr = new int[]{1,2,3,4}; System.out.println(Arrays.hashCode(arr)); System.out.println(Objects.hash(1,2,3,4)); System.out.println(Arrays.hashCode(new Integer[]{new Integer(1),new Integer(2),new Integer(3),new Integer(4)})); System.out.println(Objects.hash(new Integer(1),new Integer(2),new Integer(3),new Integer(4)));該評論的作者提到,對于所有四個語句,運行剛顯示的代碼的結果都完全相同。 這與我的示例不同,在示例中,在原始int值數組上調用Arrays.hashCode(int [])的結果與在同一原始int值數組上調用Objects.hash(Object…)的結果不同。
對原始反饋評論的一個答復準確地指出,不能保證在不同JVM上生成的哈希碼是相同的。 實際上, Object.hashCode()方法的Javadoc注釋指出(我強調了 ):
- 只要在Java應用程序執行期間在同一對象上多次調用它,hashCode方法就必須一致地返回相同的整數,前提是不修改該對象的equals比較中使用的信息。 從一個應用程序的執行到同一應用程序的另一執行,此整數不必保持一致。
- 如果根據equals(Object)方法,兩個對象相等,則在兩個對象中的每個對象上調用hashCode方法必須產生相同的整數結果。
陳述了所有這些內容之后,為整數計算的哈希碼通常在每次運行之間都是一致的。 原始評論者示例的輸出都具有完全相同的值也很有趣。 盡管我可能不希望這些值與示例的值相匹配,但是令人驚訝的是,評論者提供的所有示例都具有相同的答案。
反饋注釋中提供的示例與我的示例之間的區別在于注釋者的示例如何為原始int值數組調用Objects.hash(Object...)與我的示例如何調用Objects.hash(Object...)用于原始int值的數組。 在我的示例中,我將相同的本地數組傳遞給所有方法調用。 該注釋者的示例將原始int值的顯式數組傳遞給Arrays.hashCode(int[]) ,但將各個int元素傳遞給Objects.hash(Object...)而不是將數組傳遞給后一個方法。 當我向注釋者的示例集中添加另一個示例,該示例確實將原始int值數組傳遞給Objects.hash(Object...)方法時,我得到的生成的哈希碼與所有其他哈希碼不同。 接下來顯示該增強的代碼。
final int[] arr = new int[]{1,2,3,4}; out.println("Arrays.hashCode(int[]): " + Arrays.hashCode(arr)); out.println("Objects.hash(int, int, int, int): " + Objects.hash(1,2,3,4)); out.println("Objects.hash(int[]): " + Objects.hash(arr)); out.println("Objects.hashCode(Object): " + Objects.hashCode(arr)); out.println("int[].hashCode(): " + arr.hashCode()); out.println("Arrays.hashCode(Int, Int, Int, Int): " + Arrays.hashCode(new Integer[]{1,2,3,4})); out.println("Objects.hash(Int, Int, Int, Int): " + Objects.hash(1,2,3,4));運行注釋器提供的代碼的經過調整和增強的版本會導致輸出(帶有我添加的示例突出顯示):
Arrays.hashCode(int[]): 955331 Objects.hash(int, int, int, int): 955331 Objects.hash(int[]): 897913763 Objects.hashCode(Object): 897913732 int[].hashCode(): 897913732 Arrays.hashCode(Int, Int, Int, Int): 955331 Objects.hash(Int, Int, Int, Int): 955331將輸出與生成它的代碼進行比較,可以看出,當將int值數組的元素傳遞給Arrays.hashCode(int[]) ,它與Objects.hash(Object...)生成相同的哈希碼值Objects.hash(Object...)方法作為單個元素。 但是,我們還可以看到,當完整地傳遞原始int值的數組(作為單個數組而不是作為數組的單個元素)時, Objects.hash(Object...)方法生成了完全不同的哈希碼。 我添加的其他兩個示例(突出顯示)是通過直接在數組上調用.hashCode()或通過Objects.hashCode獲得等效結果來顯示原始int值數組上的“直接”哈希碼。 (對象) 。 [這并非巧合, Objects.hash(Object...)為原始int值數組生成的哈希碼比為原始int值數組生成的“直接”哈希碼正好大31。 ]
所有這些都指向這里的真正問題:通常最好不要將原語數組傳遞給接受可變參數 (通告省略號 )的方法。 SonarSource規則瀏覽器 ( Java )在RSPEC-3878中提供了有關此內容的更多詳細信息。 與規則描述特別相關的是與歧義有關的問題:“數組應該是一個對象還是對象的集合?”
剛剛提出的問題的答案是,當將原始int值數組傳遞給接受方法Objects.hash(Object...)的變量參數時, 整個數組將被視為單個 Object 。 相反,當將引用對象的數組(例如Integer )傳遞給相同的方法時,它將其視為與數組中的元素傳遞給它的對象數量相同。 下一個代碼清單和相關輸出證明了這一點。
package dustin.examples.hashcodes;import static java.lang.System.out;/*** Demonstrates the difference in handling of arrays by methods that* accept variable arguments (ellipsis) when the arrays have primitive* elements and when arrays have reference object elements.*/ public class ArraysDemos {private static void printEllipsisContents(final Object ... objects){out.println("==> Ellipsis Object... - Variable Arguments (" + objects.length + " elements): " + objects.getClass() + " - " + objects);}private static void printArrayContents(final Object[] objects){out.println("==> Array Object[] - Variable Arguments (" + objects.length + " elements): " + objects.getClass() + " - " + objects);}private static void printArrayContents(final int[] integers){out.println("==> Array int[] - Variable Arguments (" + integers.length + " elements): " + integers.getClass() + " - " + integers);}public static void main(final String[] arguments){final int[] primitiveIntegers = ArraysCreator.createArrayOfInts();final Integer[] referenceIntegers = ArraysCreator.createArrayOfIntegers();out.println("\nint[]");printEllipsisContents(primitiveIntegers);printArrayContents(primitiveIntegers);out.println("\nInteger[]");printEllipsisContents(referenceIntegers);printArrayContents(referenceIntegers);} }int[] ==> Ellipsis Object... - Variable Arguments (1 elements): class [Ljava.lang.Object; - [Ljava.lang.Object;@2752f6e2 ==> Array int[] - Variable Arguments (10 elements): class [I - [I@1cd072a9Integer[] ==> Ellipsis Object... - Variable Arguments (10 elements): class [Ljava.lang.Integer; - [Ljava.lang.Integer;@7c75222b ==> Array Object[] - Variable Arguments (10 elements): class [Ljava.lang.Integer; - [Ljava.lang.Integer;@7c75222b剛剛顯示的示例代碼和相關的輸出表明,期望變量參數的方法將傳遞給它的原始值數組視為單個元素數組 。 另一方面,相同的方法將傳遞給具有參考對象類型的數組的數組視為具有相同元素數的數組。
考慮到這一點,請返回哈希碼生成示例,由Objects.hash(Object...)為原始int值數組生成的哈希碼與由Arrays.hashCode(int[])生成的哈希碼不同。 類似地,我們現在可以解釋為什么對象引用數組導致相同的哈希碼,而不管調用了哪種方法。
前面我提到過,由Objects.hash(Object)生成的哈希碼比整個數組的“直接”哈希碼高31并非巧合。 這并不奇怪,因為Objects.hash(Object...)的OpenJDK實現將Arrays.hashCode(Object[]) Objects.hash(Object...)委托給Arrays.hashCode(Object[]) ,該數組使用素數乘以31 ,并乘以計算出的哈希碼中的每個元素。 考慮到上述觀察,由Objects.hash(Object...)為原始int值數組提供的哈希碼值似乎正是該方法的實現將導致我們期望的結果:整個數組的直接哈希值加上31個質數。 當該哈希碼方法僅循環一個元素時(傳遞給需要可變參數的方法的基元數組就是這種情況),其計算本質上是31 * 1 + <directHashValueOfOverallArray> 。
值得注意的是,即使參考對象數組的哈希碼計算得出的結果與將元素傳遞給接受可變參數的方法時的結果相同,還是最好避免將參考對象數組傳遞給這樣的對象。方法。 當發生這種情況時, javac編譯器會提供此警告:“警告:對最后一個參數使用不精確參數類型的varargs方法的非varargs調用”,并添加了有關解決此問題的潛在方法的這些有用的細節:“為varargs調用廣播到對象” “廣播到Object []以進行非可變參數調用并禁止顯示此警告”。 當然,對于JDK 8和更高版本,在將數組提供給需要可變參數的方法之前,以多種其他方式處理數組是相當簡單的。
我在原始帖子 (及其DZone聯合版本 )中添加了最后一段,以嘗試快速解決此問題,但是我已使用此帖子來更詳細地表達此信息。 此處總結的經驗教訓可以概括為“對原始數組使用適當的重載Arrays.hashCode方法,而不是使用Objects.hash(Object...) ”和“對數組數組使用Favor Arrays.hashCode(Object[]) ”。引用類型,而不是使用Objects.hash(Object...) 。” 如果調用的方法“看到”的元素數量無論如何都是重要的,則更通用的準則是要警惕將原始值數組傳遞給需要Object類型變量參數的方法,并且要警惕傳遞引用數組指向期望可變參數的方法的對象,以避免編譯器警告和模棱兩可的警告。
翻譯自: https://www.javacodegeeks.com/2018/09/java-subtlety-with-arrays-of-primitives-and-variable-arguments.html
總結
以上是生活随笔為你收集整理的Java的精妙之处,包括基元和变量参数数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑电源在哪个位置(电脑主机的电源在哪里
- 下一篇: 安卓选股软件(安卓选股)