最近准备重新把《Java解惑》看一遍,为了以后可以快速的回忆起书中说到的一些陷阱,我把认为值得记下来的简要记录一下,以备以后看。
毕竟一天是看不完的,所以本博客是持续更新的.......
1. 想通过num%2==1判断num是不是奇数是有问题的,因为对于负奇数会等于-1而不是1。所以可以通过num%2!=0来判断是否为奇数。为了提高性能可以采用(num&1)!=0来判断是否为奇数。
2. 二进制数是无法准确表达所有小数的,比如说0.9。在需要精确答案的地方避免使用float和double;尽量使用int,long,BigDecimal。
3. JAVA不具有目标确定类型的特性,比如说: long num=50000*50000; 执行之后num并不等于2500000000。因为JAVA并不能根据结果是long型的而在计算中采用long型数进行计算。实际上50000*50000的计算是按照int型计算的。所以应该这样:long num=50000L*50000;
4. 十六进制数和八进制数和十进制数是不同的,如果十六进制或八进制数的最高位被置位了,那么他们就是负数。而十进制数是通过加一个减号实现负数的。所以对于十六进制和八进制数要特别注意。对于0xcafebabe被提升为long型数值是会发生符号扩展变为0xffffffffcafebabeL。
5. 从较窄的整数转换成较宽的整数时候有一条规则:如果最初的数值类型是有符号的,那么就执行符号扩展;如果是char,那么不管它将要被转换成什么类型都执行零扩展。
6. 在单个表达式中更不要对相同的变量赋值两次。
7. 确定条件表达式结果类型的规则;1:如果第二个和第三个操作数类型相同那么它既是条件表达式的类型。2:如果一个操作数的类型是T,T表示byte,short,char。而另一个操作数是一个int型的常量表达式,他的值也可以用类型T表示,那么条件表达式的类型就是T;若不能被T表示,比如说超出了范围,比如:
char x='a'; System.out.println(true ? 50000 : x ); //char无法表示50000,所以结果是: ?
3:否则对类型进行二进制提升。条件表达式的类型就是提升后的类型。
8. 复合赋值表达式自动将所执行计算的结果转换为其左侧变量的类型。比如:
short x=0; int num=12345; x+=i; System.out.println(x); //计算结果会转型成short,由于short无法表示12345所以结果为: -7616
所以请不要将复合赋值操作符作用于byte,short,char类型的变量。
9. 当且仅当+操作符的操作数中至少有一个是String类型时,才会执行字符串连接操作。 比如:
String str='a'+'a'+""; String string=""+'a'+'a'; System.out.println(str); System.out.println(string); //运行结果: 194 aa
10. 对所有数组调用toString()的结果就是 巴拉巴拉hashCode神马的。还有就是如果引用为null,调用toString会转换成“null”。(我在这里吃过苦头)。比如:
String str=null; String string="hello"; str=str+string; System.out.println(str); //运行结果: nullhello
要想讲一个char数组转换成一个字符串调用toString()可不行,要调用String.valueOf(char[])比如:
char arr[]={'a','v','c'}; System.out.println(String.valueOf(arr)); //运行结果: avc
11. String类型的编译期常量是内存限定的,也就是说任何两个String类型的常量表达式,如果指定的是相同的字符序列,那么他们就用同一对象引用来表示。如:
String string="hello"; String str="hello"; System.out.println(str==string); //运行结果: true
而以下也是:
String string="hello"; String str="he"+"llo"; System.out.println(str==string); //运行结果: true
但是如果改为:
String string="hello2"; String str="hello"+"he".length(); System.out.println(str==string); //结果: false
好好体会一下。
再注意一个问题:+的优先级比==高,所以:
if ("hello"+"java"=="hellojava") { System.out.println("OK"); } //结果: OK
12. 我们来看一下Unicode转义字符,Java对再字符串字面常量中的Unicode转义字符没有提供任何特殊处理,会直接将其转化为相应字符,比如:
System.out.println("hello\u0022.length()); //\u0022被直接转成了"所以,相当于"hello".length() 5
想在字符串常量表达式中用一些特殊符号怎么办呢?可以使用转义字符序列,就是在特殊字符前加上\,举例如下:
System.out.println("hello\\".length()); System.out.println("\\\\".length()); System.out.println("\"".length()); //结果: 6 2 1
Unicode转义字符带来的混乱还不止是这些,比如如果我们在注释中这样写:
// \u
编译器是一定会报错的,因为\u表示一个Unicode转义字符的开始,而且Unicode转义字符必须是良构的,在注释中也必须是。所以要在\u后跟四个数字(16进制大小写均可)。Unicode转义字符是很危险的,比如\u000A会转义成换行。
13. 将byte数组转化为String的时候,我们都在使用一个字符集。无论是否指定。所以最好这样做:String str=new String(bytes,"ISO-8859-1");我们指定字符集。
缺省的会使用OS的缺省字符集。
14. 块注释符(/* */)是不支持嵌套的。比如下面语句就会出错:
/* if("+-*/".equals("hello")) */
所以注释掉代码的最好方式是使用单行的注释。//
15. String.replaceAll接受了一个正则表达式做为第一个参数。\后面跟非特殊字符是非法的。比如 \c就是编译不通过的。我们应该使用String.replace。
16. Random.nextInt(int):返回一个伪随机数,均等地分布在从0(包含)到指定的数值(不包含)之间的一个int数值。StringBuffer有一个无参的构造函数,一个接受一个String做为字符串缓冲区初始内容的构造器,以及一个接受int做为缓冲区初始容量的构造器。new StringBuffer('a');实际上是计算‘a’的int型值然后开辟这么大的缓冲区。
17. byte是有符号的。((byte)0x90==0x90)会返回false。
18. for(int i=Integer.MIN_VALUE; i<=Integer.MAX_VALUE; i++)是个无限循环。因为i递增到Integer.MAX_VALUE的时候再递增就变成Integer.MIN_VALUE。
19. 最近发现Java中的移位操作符的问题,详见这篇博客:
http://wjy320.iteye.com/blog/2070199
20. 我们都知道,一个无穷大的数字加上1之后还是无穷大。事实上可以用double或者float数值来表示无穷大。例如:double i=1.0e40; (i==i+1)返回的true。
对于float类型,加1不会产生任何效果的最小级数是2的25次方。对于double类型,最小级数是2的54次方。还有请记住:将一个很小的浮点数加到一个很大的浮点数上时,将不会改变大浮点数的值。所以:二进制浮点数只是对实际算数的一种近似。
21. 知道0.0/0.0等于多少吗?这肯定是有问题的不是吗?因为怎么可能去除0呢?在Java中,这个特殊的数字是有名字的叫NaN。可以通过:
Float.NaN 和 Double.NaN获得。要注意一下:1,NaN不等于任何浮点数包括它自身(NaN!=NaN)。2,任何浮点操作,只要它的一个或者多个操作数为NaN,那么其结果为NaN。
22. >>> 和 <<<是对应于无符号移位操作的操作符。之前我们提过对于复合操作符在计算的时候会执行拓宽原始类型的操作(如果两个操作数不一样长的话,会将短的操作数执行拓宽操作(对于有符号数执行符号扩展,对于char永远执行零扩展。))。
23. 注意一下比较操作符(<,>,>=,<=)。左边这些比较操作无论什么时候都是进行的数值比较。但是==操作可就不同了,对于==而言必须至少有一个操作数是原生类型时才执行数值比较,否则执行的是判断是否引用相等的操作。比如:
Integer i=new Integer(3); Integer j=new Integer(3); System.out.println(i==j); System.out.println(i>=j); System.out.println(i<=j); //运行结果: false true true
24. java使用2的补码的算术运算是不对称的。对于每一种有符号的整数类型(int,long,byte和short),负的数值总是比正的数值多一个,这个多出来的数值总是这种类型所能表示的最小的数值。(比如int表示的数据范围是负的2的32次方到正的2的32次方减1)。对Integer.MIN_VALUE取负数还等于他自身。对Short,Byte...同样适用。
25. 不要使用浮点数做为循环索引,将一个int或者long转换成一个float或double时,可能会丢失精度。事实上float虽然是32位的,但是只能提供24位的精度,指数神马的还要占位的。
26. 取余操作和乘法操作具有相同的优先级,所以ms%60*1000相当于(ms%60)*1000。
27. 千万不要时间用return,break,continue或者throw来退出finally语句块,千万不要允许让受检验的异常传播到finally语句块之外。finally中直接写break或continue是不合法的。比如:finally{break;} 因为break和continue必须在循环中,finally并不是循环。一般在打开IO流的时候我们在finally中关闭它,像这样:
finally{ if(in!=null) in.close(); if(out!=null) out.close(); }
其实以上写法是不好的,close可能抛出IOException异常,如果in.close()抛出了异常会导致out.close()永远不会执行到。从而输出流永远保持开放。所以正确的写法应该是:
finally{ if(in!=null){ try { in.close(); } catch (Exception e2) { // TODO: handle exception } } if(out!=null){ try { out.close(); } catch (Exception e2) { // TODO: handle exception } } }
从java5.0开始,可以使用Closeable接口进行重构:
private static void closeIgnoringException(Closeable c){ if(c!=null){ try { c.close(); } catch (Exception e) { // TODO: handle exception } } } finally{ closeIgnoringException(in); closeIgnoringException(out); }
28. 1,如果一个catch子句要捕获一个类型为E的受检查异常,而其相对应的try子句不能抛出E的某种子类型的异常,那么这就是一个编译期错误;但是捕获Exception或者Throwable的catch子句是永远合法的,无论对应的try子句内容为何。2,一个方法必须要么捕获其方法体可以抛出的所有受检验的异常,要么声明它将抛出这些异常。3,一个方法可以抛出的受检查的异常集合是它所适用的所有类型声明要抛出的受检查异常集合的交集。比如:
interface A{ void f() throws CloneNotSupportedException; } interface B{ void f() throws InterruptedException; } interface C extends A,B{ } public class Test implements C{ public static void main(String args[]){ C c=new Test(); c.f(); } @Override public void f(){ // TODO Auto-generated method stub System.out.println("HELLO"); } }
就是说接口C的方法f()会抛出A和B中方法f()的抛出异常的交集。所以接口C中的方法f()不会抛出异常。
29. 一个空的final域只有在它的确未被附过值的地方才可以被赋值。比如下面是不合法的:
//错误原因:编译器会认为可能对USER_ID重复赋值,try中赋值语句后面的语句若抛出异常,会在catch中重复赋值。 private static final int USER_ID; static{ try { USER_ID=1; } catch (Exception e) { USER_ID=2; // TODO: handle exception } } //其实下面的也是错的,因为不能保证USER_ID一定被赋值成功。而final是必须被初始化的。 private static final int USER_ID; static{ try { USER_ID=1; } catch (Exception e) { // TODO: handle exception } }
解决办法,加个方法就OK了:
private static final int USER_ID=getUserId(); private static int getUserId(){ try { return 1; } catch (Exception e) { return 2; // TODO: handle exception } }
30. 下面的finally中的代码永远都执行不到:
public static void main(String args[]){ try { System.exit(0); } finally{ // TODO: handle exception System.out.println("Good bye."); } }
是不是感觉有悖常理,其实System.exit方法将停止当前线程和所有其他当场死亡的线程。finally子句并不能给予线程继续执行的特殊权限。
31. 实例(不管是静态的还是非静态的)初始化操作是先于构造器的程序体而运行的。实例初始化操作抛出的任何异常都会传播给构造器。如果初始化操作抛出的是受检验的异常,那么构造器必须声明也会抛出这些异常,但是应该避免这样做。看看下面的程序,会产生栈溢出:
public class Test{ private Test instance=new Test(); public static void main(String args[]){ Test test=new Test(); } } //这就是没有将构造函数私有化的恶果。
32. &操作符除了常见的做为整形操作数的位AND操作符之外,当用于布尔操作数时,他的功能被重载为逻辑AND操作符,但是他和常用的条件操作符&&有很大的不同。&总是要计算他的两个操作数,而&&在其左边的操作数被计算为false时,就不在计算右边的操作数了。|也是一样的,|总是要计算他的两个操作符,而||在其左边的操作数被计算为true时,就不再计算右边的操作数了。
33. Class的成员变量如果是final的,要求必须对其初始化。其实将其的初始化延迟到构造函数中也是可以的。像下面这样也可以:
private final int num=getNum(); public int getNum(){ return 1; }
34. 1.一定要意识到Class.newInstance可以抛出它没有声明过的受检查的异常。2.泛型信息是在编译期而非运行期检查的。3.Java的异常检查机制并不是虚拟机强制执行的,它只是一个编译期工具,被设计用来帮助我们更加容易的编写正确的程序,但是在运行期可以绕过它。所以,不要忽视编译器给出的警告信息。
35. 要是想检测一个类是否丢失的程序,应该使用反射:
try { Object mObject=Class.forName("ClassName").newInstance(); } catch (ClassNotFoundException e) { // TODO: handle exception e.printStackTrace(); }不要对捕获NoClassDefFoundError形成依赖,类的初始化的时机是很明确的,但是类被加载的时机是不可预测的,捕获Error及其子类型几乎是不应该的。
36. 谜题45没看。待补充。
下部分:http://wjy320.iteye.com/blog/2076381
相关推荐
java 解惑 java 解惑 java 解惑 java 解惑 java 解惑 java 解惑
《Java解惑(中文版)》是一本专为Java初学者设计的学习资料,旨在帮助读者解答在学习Java过程中遇到的各种困惑。"solve65p"可能代表这本书包含65个问题或主题,每个都深入浅出地进行了讲解,旨在解决初学者在编程...
Java解惑部分涵盖了诸多主题,包括但不限于: 1. **内存管理**:Java的垃圾回收机制是其一大特色,但也常常引发疑问。书中详细解释了如何理解和控制对象生命周期,避免内存泄漏和性能问题。 2. **异常处理**:异常...
Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑
以上知识点是Java编程中不可或缺的部分,"JAVA解惑.pdf"很可能详细讲解了这些内容,为读者提供了一条清晰的学习路径,以解答他们在学习过程中遇到的困惑。通过深入学习这些内容,开发者可以提升自己的Java技能,更好...
综上所述,文档《java解惑 PDF版》的这部分内容涵盖了在Java中处理取余、奇偶判断和浮点数精度问题时需要考虑的诸多细节。开发者在进行编程时应当特别注意这些细节,避免走入常见的陷阱。在处理涉及精度的计算时,...
Java编程语言是世界上最流行的开发平台之一,广泛应用于企业级应用、移动应用(尤其是Android系统)、大数据处理、云计算等领域。这份“Java解惑.pdf”文档很可能包含了解决Java开发者在编程过程中遇到的常见问题和...
《Java解惑》 布洛克 著;陈昊鹏 译 扫描清晰带目录,仅供参阅,请支持正版
"java解惑" PDF版本
《JAVA解惑》这本书主要针对...以上是《JAVA解惑》一书中可能涉及的部分知识点,每一点都值得深入学习和实践。通过掌握这些内容,开发者可以更好地解决Java编程中的各种问题,提升编程技能,写出更高效、更可靠的代码。
《JAVA解惑》是Java开发者领域的一本经典著作,它被广大编程爱好者称为Java四大名著之一,旨在解决初学者及有一定经验的开发者在学习和使用Java过程中遇到的各种困惑。这本书深入浅出地讲解了Java语言的核心概念和...
以上只是Java编程中的一部分知识点,实际上Java还有许多其他领域,如Spring框架、JDBC数据库操作、JPA实体映射、Maven构建工具、单元测试等。"Java解惑"这本书或者资源可能包含了这些内容的详细解答,通过学习和实践...
"Java解惑"的资料可能包含了两部分:`.chm`和`.pdf`格式的文件。`.chm`是Microsoft编写的HTML帮助文档,这种文件格式通常包含索引、搜索功能和组织良好的章节结构,便于用户快速查找信息。`.pdf`文件则是一种通用的...
讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java程序员阅读。
《Java解惑(中文)》是一本专门为Java开发者编写的指南书籍,旨在解决在实际编程过程中遇到的各种疑惑和难题。本书以中文语言呈现,使得国内的Java程序员能够更轻松地理解并应用其中的知识。通过阅读这本书,读者...
通过阅读博主戈友阿姨在iteye上的这篇博文,我们可以期待获取到一些关于Java编程的实际问题解答和深入理解。 【标签】:“源码”和“工具” 这两个标签暗示了博客内容可能涉及Java的源代码分析和使用的一些实用...
以上知识点只是《Java解惑(整理版本)》中可能涵盖的部分内容,具体细节和问题解决方案可在提供的文档“Java解惑(整理版本).doc”中找到更详尽的解析。通过深入学习和实践,开发者能有效解决Java编程中的困惑,...
"Java解惑(中文).pdf"这份文档很显然是为了帮助Java开发者解决他们在编程过程中遇到的一些常见困惑和误解。这份高清版、文字版的资料很可能包含了深入浅出的解释和实例分析,以中文的形式呈现,使得国内开发者更容易...