1、关于字符串的陷阱
字符串底层实际上采用一个字符数组来保存该字符串所包含的字符序列。
1.1、JVM对字符串的处理
对于Java程序中的字符串直接量,JVM会使用一个字符串池来保存它们:当第一次使用某个字符串直接量时,JVM会将它放入字符串池进行缓存。在一般情况下,字符串池中的字符串对象不会被垃圾回收,当程序再次需要使用该字符串时,无须重新创建一个新的字符串,而是直接让引用变量指向字符串池中已有的字符串。如果字符串的连接表达式的值可以在编译时确定下来,那么JVM会在编译时计算该字符串变量的值,并让它指向字符串池中对应的字符串。如果表达式中有变量或者方法调用参与,则表达式的值不能在编译时确定,也就不会指向字符串池中的对应字符串。字符串表达式中的变量可以执行“宏替换”,可以直接确定表达式的值。
如下Java语句:
String java = new String("crazy java");
上面语句实际上创建了两个字符串对象,期中一个是“crazy java”这个直接量对应的字符串对象,另一个是由new String()构造器返回的字符串对象。
如下Java语句:
String str = "Hello " + "Java ," + "crazyit.org";
上面语句实际上只创建了一个字符串对象。
注意:当程序中需要使用字符串、基本类型包装实例时,应该尽量使用字符串直接量、基本类型直接量,避免通过new String()、new Integer()的形式来创建字符串、基本类型包装实例,这样能保证有较好的性能。
1.2、不可变的字符串
String类是一个典型的不可变类,当一个String对象创建完成后,该String类里包含的字符序列就被固定下来,以后永远都不能改变。但是字符串的引用变量可以改变,当该引用变量指向了一个新的String对象,但原String对象可能永远都不会再被用到,而且也不会被垃圾回收,它将一直存在于字符串池中——这就是Java内存泄漏的原因之一。
StringBuilder或StringBuffer是一个字符序列可以发生改变的字符串类。StringBuilder与StringBuffer的区别在于,StringBuffer是线程安全的,StringBuffer里的绝大部分方法都增加了synchronized修改符,但降低了该方法的执行效率。在没有多线程的环境下,优先使用StringBuilder类来表示字符串。
2、表达式类型的陷阱
Java是一门强类型语言,所有的变量必须先声明,然后才能使用,声明变量时必须指定该变量的数据类型,一旦某个变量的数据类型确定下来,那么这个变量将永远只能接受该类型的值,不能“盛装”其他类型的值。
2.1、表达式类型的自动提升
当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将自动提升。提升规则如下:
(1) 所有的byte类型、short类型和char类型将被提升到int类型。
(2) 整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。操作数的等级排列如下图所示:
2.2、复合赋值运算符的陷阱
复合赋值运算符会自动将它计算的结果强制类型转换为其左侧变量的类型。如下所示:
E1 op= E2 (期中op可以是+、-、*、/、%、<<、>>、>>>、&、^、|等双目运算符)
并不等价于:E1 = E1 op E2;
而是等价于:E1 = (E1的类型) (E1 op E2);
如果结果值的类型比该变量的类型大,复合赋值运算符将执行一次强制类型转换,导致高位“截断”。
3、多线程的陷阱
任何线程进入同步方法、同步代码块之前,必须先获取同步方法、同步代码块对应的同步监视器。