表达式中的陷阱

关于字符串的陷阱:
String java = new String("疯狂java");
创建了2个字符串对象,一个是“疯狂java”这个直接量对应的字符串对象,一个是由new String()返回的字符串对象。

对于java程序中的字符直接量,JVM会使用一个字符串池来保存它们:第一次使用某个字符串直接量,JVM会将它放入字符串池缓存。程序再次使用,直接引用变量指向字符串池中已有的字符串

public static void main(String[] args){
String str1 = "Hello java的长度:10";
String str2 = "Hello " + "java" + "的长度:" + 10;
System.out.println(str1 == str2);//true
}
上面程序中的所有运算数,都是字符串直接量、整数直接量,没有变量和方法参与,因此JVM可以在编译时就确定该字符串连接表达式的值,可以让该字符串变量指向字符串池中对应的字符串。但如果程序中使用了变量或方法,就无法利用JVM字符串池了,只能运行时才可以确定字符串连接表达式的值。

有一种情况例外,如果字符串连接运算中的所有变量都可执行“宏替换”,那么JVM一样可以在编译时就确定字符串连接表达式的值,一样会让字符串变量指定JVM字符串池中对应字符串。
public static void main(String[] args){
String str1 = "Hello java的长度:10";
final String s1 = "Hello ";
String str2 = s1 + "java" + "的长度:10";
System.out.println(str1 == str2); //true
}

简单面试题:
String str = "Hello " +"java," + "crazyit.org";一共创建了几张字符串对象
创建了一个:"Hello java crazyit.org";

不可变的字符串
System.identityHashCode()静态方法获取某个对象唯一的hashCode值,与该类是否重写了hashCode()方法无关。
如果程序需要一个字符序列会改变的字符串,考虑使用StringBuilder或StringBuffer。区别在于StringBuilder绝大部分方法使用synchronized修饰。

字符串比较
比较是否相同,使用==
比较字符串包含的字符序列是否相同,用重写过的equals().
public boolean equals(Object anObject){
if(this == anObject)
return true;
if(anObject instanceof String){
String anotherString = (String)anObject;
int n = count;
if(n == anotherString.count){
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while(n-- != 0){
if(v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
String实现了Comparable接口,可以使用CompareTo()比较。

表达式类型的陷阱:
强类型,两个特点:
变量必须先声明,然后才能使用,声明时必须指定该变量的数据类型。
一旦某个变量的数据类型确定下来,这个变量只能接受该类型的值。
表达式类型自动提升:
java语言规定,当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。java中的自动提升规则:
1、所有的byte、short和char将被提升到int
2、整个算术表达式的数据类型自动提升到与表达式中最高等级操作同样的类型。操作的数的等级排列如下:
byte -- short
char
---int --long --float -- double
复合赋值运算符的陷阱:
short sValue = 5;
sValue= sValue - 2; 会报错,因为sValue - 2会发生类型提升,所以int值不能直接赋值给short
改成这样就不会有问题:
short sValue = 5;
sValue -= 2;
java语言几乎允许所有的双目运算符和=一起结合成符号赋值运算符号,如+=、-=等。根据java语言规范,复合赋值运算符包含了一个隐式的类型的转换。对于复合赋值运算符而言,语句E1 op = E2;(op可以是+、-等双目运算符)等价于E1 = (E1的类型)(E1 op E2)

复合赋值运算符如果结果值的类型比该变量的类型大,那么复合赋值运算符将会执行一次强制类型转换,可能导致"截断",丢失精度或准度。
public static void main(String[] args){
short st = 5;
st += 10;
System.out.println(str);
st += 90000;
System.out.println(str);
}
short类型的变量只能接受-32768~32767之间的整数,因此上面的程序将会将90010的高位"截断",最后输出看到24479

如果把+当成字符串连接运算符使用,则+=运算符左边只能是String类型,而不可能是String的父类型(如Object等)。

输入法导致的陷阱:
注意全半角,特别是如果你输入的是空格的时候,很难分辨出来。如果使用了全角的空格,编译会报错,"非法字符\12288"

泛型可能引起的错误:
在严格的泛型程序中,使用泛型声明的类时应该总是为之指定类型参数,但为了与老的java代码保持一致,java也允许使用带泛型声明的类时不指定参数类型。
public static void main(String[] args){
List list = new ArrayList();
list.add("疯狂java讲义");
list.add("轻量级java EE企业应用实战");
list.add("疯狂ajax讲义");
List<Integer> intList = list;
for(int i = 0; i < intList.size(); i++){
System.out.println(intList.get(i));
}
}
运行,没有任何问题。不过需要指出的是,当把一个原始类型的变量赋给带泛型信息的变量(如List<Integer>)时会有一个潜在的问题:JVM会把集合里盛装的所有元素都当成Integer来处理.上面遍历List<Integer>没有出现问题,只是简单输出每个集合元素,并未涉及到集合类型的元素类型,所以不错,否则程序要么运行时出现ClassCastException,要么编译时提示编译错误。
public static void main(String[] args){
List list = new ArrayList();
list.add("疯狂java讲义");
list.add("轻量级java EE企业应用实战");
list.add("疯狂ajax讲义");
List<Integer> intList = list;
for(int i = 0; i < intList.size(); i++){
Integer in = intList.get(i); //报错ClassCastException
}
}
public static void main(String[] args){
List list = new ArrayList();
list.add("疯狂java讲义");
list.add("轻量级java EE企业应用实战");
list.add("疯狂ajax讲义");
List<Integer> intList = list;
for(int i = 0; i < intList.size(); i++){
String in = intList.get(i); //编译报错:不兼容的类型
}
}
上面程序给出的教训有3点:
当一个程序把一个原始类型的变量赋值给一个带泛型信息的变量时,总是可以通过编译--只是会提示一些警告信息;
当程序试图访问带泛型声明的集合和的集合元素时,编译器总是把集合元素当成泛型类型处理--它不关心集合里集合元素的实际类型;
当程序视图访问带泛型声明的集合元素时,JVM会遍历每个集合元素自动执行强制转换,如果集合元素的实际类型与几何所带的泛型信息不匹配,运行时将引发ClassCaseException
注意多线程环境

posted @ 2017-03-06 11:36  guodaxia  阅读(101)  评论(0)    收藏  举报