java中遇到的坑

本文详细介绍了Java编程中的关键概念,包括整数与数组的处理、三元表达式的特性、货币计算的精度问题、单例模式的实现、泛型的使用与限制,以及数组与对象的类型转换。此外,还探讨了字符连接、特殊字符处理、单例实例的多重创建、以及Java字节码中方法签名的细节。
摘要由CSDN通过智能技术生成

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("\\.","");好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值