1.字符串陷阱
1.1 java创建对象的常见方式如下:
- 通过new调用构造器创建Java对象。
- 通过Class对象的newInstance()方法调用构造器创建Java对象。
- 通过Java的反序列化机制从IO流中恢复Java对象。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,** 对象序列化不会关注类中的静态变量**。
- 通过Java对象提供的clone()方法复制一个新的Java对象。
- 对于字符串以及Byte,Short,Integer,Long,Character,Float,Double和Boolean这些基本类型的包装类。
- 直接量的方式创建java对象.
String str="abc";
- 通过简单的算法表达式,连接运算来创建Java对象。
String str2="abc"+"ddd";
1.2 JVM对字符串的处理
-
String java=new String("给我丶鼓励");
该语句实际创建了2个字符串对象,一个是“给我丶鼓励”这个直接量对应的字符串对象,一个是由new String()构造器返回的字符串对象。测试代码如下:
String java=new String("给我丶鼓励");
//test1
String test1="给我丶鼓励";
System.out.println(test1==java); //false 一个是在常量区,一个是new出来的内存区
//test2
System.out.println(java.intern()==test1); //true intern()方法是从常量区里返回该对象。
- 如果字符串连接表达式的值在编译的时候可以确定下来,那么JVM会在编译时计算该字符串变量的值,并让它指向字符串池中对应的字符串。也就是说如果一开始,这些算法表达式是字符串直接量、整数直接量,没有变量和方法参与,“宏替换”(使用 final 修饰的变量),那么就可以在编译期就可以确定字符串的值;如果使用了变量、调用了方法,那么只有等到运行时才能确定字符串表达式的值。例子如下:
String hello="hello";
String java="java";
final String str1Final="java";
final String str2Final=java;
String str1="hellojava";
String str2="hello"+"java";
String str3=hello+java;
String str4="hello"+str1Final;
String str5="hello"+str2Final;
//test
//字符串直接量
System.out.println(str1==str2); //ture
//使用了变量
System.out.println(str1==str3); //false
//宏替换
System.out.println(str1==str4); //true
//虽然是final 但不是宏替换
System.out.println(str1==str5); //false
解释:为什么第二条是false,其实要理解
String hello="hello";
String java="java";
String str3=hello+java;
//相当于:
String str3=(new StringBuilder().append("hello").append("java").toString());
因为new了一个新对象,所以比较肯定是false。
反编译后代码是这样的
String s = "hello";
String s1 = "java";
String s2 = (new StringBuilder()).append(s).append(s1).toString();
String s3 = (new StringBuilder()).append("hello").append("java").toString();
为什么第4条也是false呢?因为没有在编译期就确定下来的值,所以这个final不是宏替换,只能当不变的"变量"对待,所以接下来跟第二条原理一样了。
- 字符串比较
如果要比较两个字符串是否相同,用==进行判断就可以了,但如果要判断两个字符串所含的字符序列是否相同,则应该用String重写过的equals()方法进行比较,代码如下:
//r如果两个字符串相同,返回true
if(this==anObject){
retrun true;
}
//如果anObject是String类型
if(anObject instanceof String){
String anotherString =(String)anObject;
//n代表当前字符串的长度
int n=count;
//如果两个字符串的长度相等
if(n==anotherString.count){
//获取当前字符串,anotherString底层封装的字符数组
char v1[]=value;
char v2[]=anotherString.value;
int i =offset;
int j =anotherString.offset;
//逐渐比较v1数组和v2数组里的每个字符
while(n--!=0){
if(v1[i++]!=v2[j++])
return false;
}
return true;
}
return false;
}
String 类还实现了Comparable接口,因此程序还可通过String提供的compareTo()方法来判断两个字符串之间的大小,当两个字符串所包含的字符序列相等时,程序通过compareTo()比较,将返回0.
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
2.表达式类型的陷阱
2.1 表达式类型
-
所有 byte、short、char类型将被提升到 int 类型参与运算
-
整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型,操作数的等级排列如下:
-
char -> int -> long ->float -> double
-
byte -> short -> int -> long ->float -> double
-
2.2 复合赋值运算符
- 复合赋值运算符中包含的隐式类型转换,例如
a=a+5;
a+=5; //实际上等价于 a=(a的类型)(a+5);
- 复合赋值运算的时候,小心“溢出”,如果"溢出",会进行高位”截断“。例如:
short st=5;
st+=90010;
System.out.println(st); //将会输出24479
//此时会"溢出",因为系统有一个隐式的类型转换,short只能接受-32768~32767之间的整数,所以就会"溢出",因此会进行高位截断。
- 如果把+当成字符串连接运算符使用,则+=运算符左边的变量只能是String类型,而不可能是String的父类型(如Object或CharSequence等)。
3.转义字符的陷阱
- 慎用字符的 Unicode 转义形式
- 中止行注释的转义字符
4.泛型可能引起的错误
4.1原始类型变量的赋值
- 当程序把一个原始类型的变量赋给一个带有泛型信息的变量时,总是可以通过编译(只是会提示警告信息)
- 当程序试图访问带泛型声明的集合的集合元素时,编译器总是把集合元素当成泛型类型处理(它并不关心集合里集合元素的实际类型)
- 当程序试图访问带泛型声明的集合的集合元素时,JVM会遍历每个集合元素自动执行强制转型,如果集合元素的实际类型与集合所带的泛型信息不匹配,运行时将引发 ClassCastException
4.2原始类型带来的擦除
- 当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都会丢弃。
4.3创建泛型数组的陷阱
- Java 中不允许创建泛型数组
5.正则表达式陷阱
5.1String类的一些方法支持正则表达式
- matches(String regex);判断该字符串是否匹配指定的正则表达式。
- String replaceAll(String regex,String replacement);将字符串中所以匹配指定正则表达式的子串替换成replacement后返回
- String replaceFirst(String regex,String replacement);将字符串中第一个匹配指定正则表达式的子串替换成replacement后返回
- String[] split(String regex);以regex正则表达式匹配的子串作为分割符来分割该字符串
6.多线程的陷阱
6.1不要调用 RUN 方法
开启线程是用 start() 方法,而不是 run() 方法。
6.2静态的同步方法
对于同步代码块而言,程序必须显式为它指定同步监视器;对于同步非静态方法而言,该方法的同步监视器是 this —— 即调用该方法的 Java 对象;对于静态的同步方法而言,该方法的同步监视器不是 this,而是该类本身。