java中遇到的坑

1.除0

代碼:

 

Java代碼  收藏代碼
  1. System.out.println(1.0d / 0);  
  2. System.out.println(0.0d / 0);  
  3. System.out.println(1 / 0);  
  4. System.out.println(0 / 0);  
 

 

輸出:

Infinity

NaN

java.lang.ArithmeticException: / by zero

at test.ww.Test.main(Test.java:27)

java.lang.ArithmeticException: / by zero

at test.ww.Test.main(Test.java:32)

 

原因:

因爲 IEEE 754 有規定無窮大是怎麼表示的,因此被除數不爲 0,除數是 0 的話計算結果是正無窮或者是負無窮,如果被除數和除數都是 0 的話,那麼計算結果是 NaN

 

整數不在是 IEEE 754 規定的,也沒有無窮大的表示,因此只能拋出異常了

 

2. Arrays.asList?可變長參數?

 

 

Java代碼  收藏代碼
  1. int[] arr = new int[]{1,2,3};  
  2. System.out.println(Arrays.asList(arr).contains(1));  
 

 

 

輸出什麼?

大多數人的回答肯定是true,顯而易見麼。。但它輸出的卻是false,爲什麼,哪裏出錯了麼?

分解一下表達式,先調用Arrays.asList(arr),通過遍歷或者debug你會發現裏面的元素個數爲1,這又是什麼原因呢,查看一下API或者源代碼,發現它聲明爲List<T> Arrays.asList(T... args),難道是可變長參數有問題?

做一個測試:

 

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.   
  3.        int[] arr = new int[]{123 };  
  4.        test1(arr);  
  5.   
  6.    }  
  7.   
  8.    private static void test1(Object... values) {  
  9.        System.out.println(values.length);  
  10.    }  
 

 

 

輸出

1

把int數組改爲Integer數組後,輸出

3

 

看來是基本類型的數組被當作了一個對象,而對象類型的數組的每個元素才能分別作爲可變長參數方法的參數。

 

3.詭異的三元表達式

三元表達式,註釋部分是該行的輸出,爲啥?

 

Java代碼  收藏代碼
  1. char x = 'X';  
  2. int i = 0;  
  3. System.out.println(true ? x : 0);// X  
  4. System.out.println(false ? i : x);// 88  
  5. char x = 'x';
  6. System.out.println(true?120:x);
 

 

原因:

 true ? x : 0返回的是x的類型,即char型,調用char的toString方法,輸出X

 false ? i : x返回的是i的類型,即int型,先把‘X’轉爲整型(88),再輸出。

對於三目運算符中的兩個結果,如果一個是常量,一個是類型T的變量,則常量會被轉型爲類型T,這個據說是java編程規範中規定的,反正我是沒看過,就此記住一條。所以常量120被轉型爲char,對應於x(小寫)

 

即三元運算符返回類型以第二個變量爲主(問號後第一個)


4.我的錢呢?

total是你兜裏的所有的錢,price是你要買的東西的價格,remaining是剩餘的錢。count是你最多買的東西的數量,請問下面的代碼有什麼問題,輸出什麼?

 

Java代碼  收藏代碼
  1. double total = 2.0;  
  2. double price = 0.1;  
  3. double remaining = total;  
  4. int count = 0;  
  5. while (remaining >= price) {  
  6.     count++;  
  7.     remaining -= price;  
  8. }  
  9. System.out.println(count);  
  10. System.out.println(remaining);  

 代碼看起來沒有問題,判斷兜裏的錢大於物品的價格就買一個,直到買不起爲止,看起來應該輸出20和0.

可運行後會發現輸出19和0.09999999999999937(可能根據不同的機器這個餘數略有差異)。

錢怎麼少了?

 

這個原因,和二進制無法精確的表示某些浮點數有關,2.0-1.1結果是0.899999999999。

所以,如果你想要精確的浮點數,請使用BigDecimal類進行運算,當然可能會慢一些,需要自己權衡。

5.x+=i等同於x = x + i?

1)

 

Java代碼  收藏代碼
  1. short x = 1;  
  2. int i = 1;  
  3. x += i;  
  4. x = x + i;  
 

 

當你試圖編譯上面代碼的時候,編譯器會報錯,在x=x+i的地方:Type mismatch: cannot convert from int to short

2)

Java代碼  收藏代碼
  1. Object x = 1;  
  2. String i = "";  
  3. x += i;  
  4. x = x + i;  
 

當你試圖編譯上面代碼的時候,編譯器會報錯,在x += i;的地方:The operator += is undefined for the argument type(s) Object, String

 

6.字符連接???

 System.out.println('N' + 'C');

當你運行上面代碼的時候,它不會如你所願輸出NC,而會輸出145,這是怎麼了?

char型變量的加法不是連接字符,而是和int類似,但相加的是它的ASCII碼,本例中輸出78+67=145

7.E呢?

Java代碼  收藏代碼
  1. StringBuffer sb = new StringBuffer('E');  
  2. sb.append('M');  
  3. sb.appent('S');  
  4. System.out.println(sb.toString());  
 

 

當你滿心歡喜的運行上面代碼的時候,會發現它並沒有如你所願的輸出EMS,而是輸出了MS,E呢?

 

看來是StringBuffer的構造器出問題,當你查看StringBuffer的構造函數的時候,你會驚奇的發現它並不提供char型參數的構造器,這是怎麼回事?

如果你使用eclipse或者其他IDE點進入的時候,會發現new StringBuffer('E');進入的是StringBuffer(int)的構造器,這個構造器只是初始化了一下緩衝區大小。

 

到這裏應該都明白了,所以使用char的時候要注意,它有時候更像是int而不是String。

 

8.單例?

相同虛擬機下下面的類什麼時候能得到多於一個實例?

 

Java代碼  收藏代碼
  1. public class Test implements Seraziable{  
  2.     private static Test  instance = null;  
  3.     private Test (){  
  4.     }  
  5.   
  6.     public static Test getInstance() {  
  7.         if(instance == null) {   
  8.             instance = new Test();  
  9.         }  
  10.     }  
  11. }  
 

 

第一個想到的應該是多線程,兩個線程同時訪問的時候可能會產生兩個實例。

細心的人會發現這個類實現了序列化接口,也就是序列化反序列化的時候也可能產生多個實例:

 

 

Java代碼  收藏代碼
  1. public class TestSerializable implements Serializable {  
  2.     private static final TestSerializable instance = new TestSerializable();  
  3.     private String name = "t1";  
  4.     private TestSerializable() {  
  5.         System.out.println("**********TestSerializable************");  
  6.     }  
  7.     public static TestSerializable getInstance() {  
  8.         return instance;  
  9.     }  
  10.     public String getName() {  
  11.         return name;  
  12.     }  
  13.     public void setName(String name) {  
  14.         this.name = name;  
  15.     }  
  16.     public static void main(String[] args) throws Exception {  
  17.         TestSerializable singleton = TestSerializable.getInstance();  
  18.            
  19.         FileOutputStream fos = new FileOutputStream("E:\\test.txt");  
  20.         ObjectOutputStream oos = new ObjectOutputStream(fos);  
  21.         oos.writeObject(TestSerializable.getInstance());  
  22.         oos.flush();  
  23.         oos.close();  
  24.           
  25.         FileInputStream fis = new FileInputStream("E:\\test.txt");  
  26.         ObjectInputStream ois = new ObjectInputStream(fis);  
  27.           
  28.         TestSerializable singleton2 = (TestSerializable)ois.readObject();  
  29.         singleton.setName("111");  
  30.         System.out.println(singleton.getName());  
  31.         System.out.println(singleton2.getName());  
  32.     }  
  33. }  
 

輸出:

111

t1

 

解決這個問題首先要知道反序列化的時候怎麼執行的,通過查找資料,知道了反序列化的時候調用了方法

private Object readResolve()

jdk有個默認的反序列化方式,會從序列化文件讀取信息,並創建實例(不通過構造函數),然後對變量賦值。

 

如果不考慮遠程傳數據,下面的辦法可以保證當前jvm只有一個實例:

在類中增加方法:

Java代碼  收藏代碼
  1. private Object readResolve(){  
  2.         return instance;  
  3.     }  
 

 

輸出:

**********TestSerializable************

111

111

但是這樣僅僅保證了單例,而丟失了序列化應有的功能。

所以大家在設計可序列化接口的類時要多多考慮。

 

9.泛型?

編譯下面的類,會發現編譯器報錯:Method test(List<String>) has the same erasure test(List<E>) as another method in type Test001

Java代碼  收藏代碼
  1. public class Test001 {  
  2.   
  3.     public void test(List<String> lst) {  
  4.     }  
  5.     public void test(List<Object> lst) {  
  6.     }  
  7.   
  8. }  
 

 

 這就是泛型的類型擦除,也就是泛型會在編譯後消失。

 

但是看下面的代碼,卻能編譯通過,難道java中返回類型也作爲方法簽名;不可能!絕對不可能!java規範明明寫着方法簽名不包括返回值,這到底怎麼了?

另外如果把泛型去掉這個類也不能通過編譯,看來還是和泛型相關:

 

Java代碼  收藏代碼
  1. public class Test001 {  
  2.     public static void test(List<Object> lst) {  
  3.         System.out.println("test(List<Object> lst)");  
  4.     }  
  5.   
  6.     public static String test(List<String> lst) {  
  7.         System.out.println("test(List<String> lst)");  
  8.         return "ttttt";  
  9.     }  
  10. }  
 

 

 

調用一下試試?

 

Java代碼  收藏代碼
  1. public class Test001 {  
  2.     public static void main(String[] args) {  
  3.         List<String> aaa = new ArrayList<String>();  
  4.         aaa.add("1");  
  5.         test(aaa);// 如果List不指定<String>,則編譯報錯  
  6.     }  
  7.   
  8.     public static void test(List<Object> lst) {  
  9.         System.out.println("test(List<Object> lst)");  
  10.     }  
  11.   
  12.     public static String test(List<String> lst) {  
  13.         System.out.println("test(List<String> lst)");  
  14.         return "ttttt";  
  15.     }  
  16. }  
 

正如註釋縮寫,如果調用的時候不用泛型,java也無法找到該調用哪個。

 

這到底怎麼了,泛型不是被擦除了麼?怎麼會這樣??

 

直接查看字節碼javap -verbose:

Java代碼  收藏代碼
  1. public class test.Test001 extends java.lang.Object  
  2. SourceFile: "Test001.java"  
  3. minor version: 0  
  4. major version: 49  
  5. Constant pool:  
  6. const #1 = class #2// test/Test001  
  7. const #2 = Asciz test/Test001;  
  8. const #3 = class #4// java/lang/Object  
  9. const #4 = Asciz java/lang/Object;  
  10. const #5 = Asciz <init>;  
  11. const #6 = Asciz ()V;  
  12. const #7 = Asciz Code;  
  13. const #8 = Method #3.#9// java/lang/Object."<init>":()V  
  14. const #9 = NameAndType #5:#6;// "<init>":()V  
  15. const #10 = Asciz LineNumberTable;  
  16. const #11 = Asciz LocalVariableTable;  
  17. const #12 = Asciz this;  
  18. const #13 = Asciz Ltest/Test001;;  
  19. const #14 = Asciz main;  
  20. const #15 = Asciz ([Ljava/lang/String;)V;  
  21. const #16 = class #17// java/util/ArrayList  
  22. const #17 = Asciz java/util/ArrayList;  
  23. const #18 = Method #16.#9// java/util/ArrayList."<init>":()V  
  24. const #19 = String #20// 1  
  25. const #20 = Asciz 1;  
  26. const #21 = InterfaceMethod #22.#24// java/util/List.add:(Ljava/la  
  27. ng/Object;)Z  
  28. const #22 = class #23// java/util/List  
  29. const #23 = Asciz java/util/List;  
  30. const #24 = NameAndType #25:#26;// add:(Ljava/lang/Object;)Z  
  31. const #25 = Asciz add;  
  32. const #26 = Asciz (Ljava/lang/Object;)Z;  
  33. const #27 = Method #1.#28// test/Test001.test:(Ljava/util/List;)Ljava/la  
  34. ng/String;(2)調用的方法  
  35. const #28 = NameAndType #29:#30;// test:(Ljava/util/List;)Ljava/lang/String;(3)方法簽名,可這裏卻包含了返回值。。。  
  36. const #29 = Asciz test;  
  37. const #30 = Asciz (Ljava/util/List;)Ljava/lang/String;;  
  38. const #31 = Asciz args;  
  39. const #32 = Asciz [Ljava/lang/String;;  
  40. const #33 = Asciz aaa;  
  41. const #34 = Asciz Ljava/util/List;;  
  42. const #35 = Asciz LocalVariableTypeTable;  
  43. const #36 = Asciz Ljava/util/List<Ljava/lang/String;>;;  
  44. const #37 = Asciz (Ljava/util/List;)V;  
  45. const #38 = Asciz Signature;  
  46. const #39 = Asciz (Ljava/util/List<Ljava/lang/Object;>;)V;  
  47. const #40 = Field #41.#43// java/lang/System.out:Ljava/io/PrintS  
  48. tream;  
  49. const #41 = class #42// java/lang/System  
  50. const #42 = Asciz java/lang/System;  
  51. const #43 = NameAndType #44:#45;// out:Ljava/io/PrintStream;  
  52. const #44 = Asciz out;  
  53. const #45 = Asciz Ljava/io/PrintStream;;  
  54. const #46 = String #47// test(List<Object> lst)  
  55. const #47 = Asciz test(List<Object> lst);  
  56. const #48 = Method #49.#51// java/io/PrintStream.println:(Ljava/l  
  57. ang/String;)V  
  58. const #49 = class #50// java/io/PrintStream  
  59. const #50 = Asciz java/io/PrintStream;  
  60. const #51 = NameAndType #52:#53;// println:(Ljava/lang/String;)V  
  61. const #52 = Asciz println;  
  62. const #53 = Asciz (Ljava/lang/String;)V;  
  63. const #54 = Asciz lst;  
  64. const #55 = Asciz Ljava/util/List<Ljava/lang/Object;>;;  
  65. const #56 = Asciz (Ljava/util/List<Ljava/lang/String;>;)Ljava/lang/String;  
  66. ;  
  67. const #57 = String #58// test(List<String> lst)  
  68. const #58 = Asciz test(List<String> lst);  
  69. const #59 = String #60// ttttt  
  70. const #60 = Asciz ttttt;  
  71. const #61 = Asciz SourceFile;  
  72. const #62 = Asciz Test001.java;  
  73. {  
  74. public test.Test001();  
  75. Code:  
  76. Stack=1, Locals=1, Args_size=1  
  77. 0: aload_0  
  78. 1: invokespecial #8//Method java/lang/Object."<init>":()V  
  79. 4return  
  80. LineNumberTable:  
  81. line 60  
  82. LocalVariableTable:  
  83. Start Length Slot Name Signature  
  84. 0 5 0 this Ltest/Test001;  
  85. public static void main(java.lang.String[]);  
  86. Code:  
  87. Stack=2, Locals=2, Args_size=1  
  88. 0new #16//class java/util/ArrayList  
  89. 3: dup  
  90. 4: invokespecial #18//Method java/util/ArrayList."<init>":()V  
  91. 7: astore_1  
  92. 8: aload_1  
  93. 9: ldc #19//String 1  
  94. 11: invokeinterface #212//InterfaceMethod java/util/List.add:(Ljava/lan  
  95. g/Object;)Z  
  96. 16: pop  
  97. 17: aload_1  
  98. 18: invokestatic #27//Method test:(Ljava/util/List;)Ljava/lang/String; //(1)調用入口  
  99. 21: pop  
  100. 22return  
  101. LineNumberTable:  
  102. line 90  
  103. line 108  
  104. line 1117  
  105. line 1222  
  106. LocalVariableTable:  
  107. Start Length Slot Name Signature  
  108. 0 23 0 args [Ljava/lang/String;  
  109. 8 15 1 aaa Ljava/util/List;  
  110. LocalVariableTypeTable: length = 0xC  
  111. 00 01 00 08 00 0F 00 21 00 24 00 01  
  112. public static void test(java.util.List);  
  113. Signature: length = 0x2  
  114. 00 27  
  115. Code:  
  116. Stack=2, Locals=1, Args_size=1  
  117. 0: getstatic #40//Field java/lang/System.out:Ljava/io/PrintStream;  
  118. 3: ldc #46//String test(List<Object> lst)  
  119. 5: invokevirtual #48//Method java/io/PrintStream.println:(Ljava/lang/St  
  120. ring;)V  
  121. 8return  
  122. LineNumberTable:  
  123. line 150  
  124. line 168  
  125. LocalVariableTable:  
  126. Start Length Slot Name Signature  
  127. 0 9 0 lst Ljava/util/List;  
  128. LocalVariableTypeTable: length = 0xC  
  129. 00 01 00 00 00 09 00 36 00 37 00 00  
  130. public static java.lang.String test(java.util.List);  
  131. Signature: length = 0x2  
  132. 00 38  
  133. Code:  
  134. Stack=2, Locals=1, Args_size=1  
  135. 0: getstatic #40//Field java/lang/System.out:Ljava/io/PrintStream;  
  136. 3: ldc #57//String test(List<String> lst)  
  137. 5: invokevirtual #48//Method java/io/PrintStream.println:(Ljava/lang/St  
  138. ring;)V  
  139. 8: ldc #59//String ttttt  
  140. 10: areturn  
  141. LineNumberTable:  
  142. line 190  
  143. line 208  
  144. LocalVariableTable:  
  145. Start Length Slot Name Signature  
  146. 0 11 0 lst Ljava/util/List;  
  147. LocalVariableTypeTable: length = 0xC  
  148. 00 01 00 00 00 0B 00 36 00 24 00 00  
  149. }  
 

 

上面標註的(1),(2),(3)可以看到,java字節碼中的方法簽名竟然有返回值(位置(3)),。

如果是非泛型類,即使方法簽名帶返回值,也無法區分該調用哪個方法(因爲調用時可以不要返回值)

而是泛型類的話,通過編譯器(編譯的時候通過泛型可以知道該調用哪個方法)和帶返回值的方法聲明的結合,卻可以找到調用的方法。

 

另外注意:以上帶返回值的泛型方法在有些編譯器下也是不能通過編譯的(sun的可以)

 

10.靠,出錯了?

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.        List<String> aaa = new ArrayList<String>();  
  3.        aaa.add("1");  
  4.        test(aaa);  
  5.    }  
  6.    public static void test(List<Object> lst) {  
  7.        System.out.println("List<Object> lst");  
  8.    }  
  9.    public static void test(Object obj) {  
  10.        System.out.println("Object obj");  
  11.    }  
 

當你想當然的認爲輸出List<Object> lst的時候,卻輸出了Object obj,難道List<String>不是List<Object>的子類?

通過查看規範發現確實List<String>不是List<Object>的子類,它倆沒啥關係。。。編譯器直接就指向了test(Object obj)

 

11.騙子!

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.     List<Object> strList = new ArrayList<Object>();  
  3.     strList.add("1");  
  4.     strList.add("2");  
  5.     String[] arrA1 = strList.toArray(new String[0]);  
  6.     System.out.println(arrA1.length);  
  7.   
  8.     String[] arrA2 = (String[]) strList.toArray(new Object[0]);  
  9.     System.out.println(arrA2.length);  
  10.   
  11. }  
 

 

上面的代碼你可能認爲第一個toArray會報錯,因爲list聲明爲Object,轉化爲String會錯誤,但運行你會發現它正確的輸出了2,這是因爲上面說的泛型的類型擦除的原因,運行時沒有Object的聲明,只會根據運行態的類型進行轉化。

而第二個toArray卻會報錯(類型轉化異常),爲啥?

這種寫法相當於

Java代碼  收藏代碼
  1. Object[] o = new Object[]{"1""2"};  
  2. String[] arrO = (String[]) 0;  
  3. System.out.println(arrO.length);  
 

這回看出來了吧,要轉化的數組和聲明後的數組類型不一樣(雖然存儲的數據確實是String的),直接就報數組的類型轉化錯誤。

12.replaceAll的使用

String str="34.ff4.455434";

String mt=str.replaceAll(".","");結果到測試時候才發現得到的結果是個空的字符串

改成String mt=str.replaceAll("\\.","");好了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章