一、字符串陷阱
1.对于对象的创建方式
(1)通过new关键字调用构造器创建Java对象:String a=new String("ltt")->此语句创建了两个Java对象即“ltt”这个直接量对应的字符串对象和new String构造器返回的字符串对象。
(2)通过Class对象的的newInstance方法调用构造器创建Java对象。
某类.class.newInstance():调用某类的无参构造器
(3)通过Java的反序列化机制从IO流中恢复Java对象。
public class SingletonTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException{
Singleton t1=Singleton.getInstatce("ltt");
Singleton t2=null;
ObjectOutputStream oos=null;
ObjectInputStream ois=null;
try{
//通过反序列化从IO流中恢复Java对象
oos=new ObjectOutputStream(new FileOutputStream("a.bin"));
ois=new ObjectInputStream(new FileInputStream("a.bin"));
oos.writeObject(t1);
oos.flush();
t2=(Singleton)ois.readObject();
System.out.println(t1==t2);
}finally{
if(oos!=null){
oos.close();
}
if(ois!=null){
ois.close();
}
}
}
}
class Singleton implements Serializable{
private static Singleton instance;
private String name;
private Singleton(String name){
System.out.println("调用有参数的构造器");
this.name=name;
}
public static Singleton getInstatce(String name){
if(instance==null){
instance=new Singleton(name);
}
return instance;
}
private Object readResolve(){
return instance;
}
}
(4)通过Java对象提供的clone方法复制一个新的Java对象。
要是用clone的方法则该类必须实现Cloneable接口,然后为该类提供clone方法,使这个方法进行复制。
(5)通过直接量直接创建Java对象:String str="ltt"。
对于Java中的字符串直接量,jvm会使用一个字符串池来保存它们,第一次使用字符串直接量时,jvm会把它放在字符串池中缓存,一般情况来说,垃圾回收机制不会回收字符串缓冲池中的对象的空间,当程序要再次使用这个字符串时无需创建一个新的字符串,而是直接让新的引用变量指向字符串缓冲池中已有的字符串对象。
(6)通过简单的算术表达式:String str="abd"+"sd"。
如果字符串连接表达式的值可以在编译时确定下来,那么jvm会在编译时计算该字符串变量的值,并让它指向字符串池中对应的字符串。如何判断字符串连接表达式在编译时能否确定下来?在字符串连接表达式中只要它们都是直接量,没有变量以及方法的调用,那么该表达式就能在编译时确定下来,就能让该表达式指向字符串池中的字符串对象。有一个例外是当字符串连接表达式中包括宏变量时,则这个表达式的数值在编译时也是可以确定下来的。但是要注意的是宏变量只有在初始化时加上final关键字给变量赋初值时,这个变量才会有宏替换的作用。
public static void main(String[] args){
String a="ltt,jiayou的长度为10";
//利用字符串直接量连接成表达式,因此b指向字符串缓冲池中的对象
String b="ltt"+",jiayou的长度为10";
String str="ltt";
//c的表达式中包含了str变量,因此不能在编译时确定下来
String c=str+",jiayou的长度为10";
//d的表达式中包含了方法的调用,因此不能在编译时确定下来
String d="ltt,jiayou的长度为"+"ltt,jiayou".length();
final String str1="ltt";
//e中包含了final关键字的宏变量,因此在编译时能够通过
String e=str1+",jiayou的长度为10";
//很显然a和b和e是指向同一个对象,因此它们的hashcode值也是一样的。
System.out.println(a==b);
System.out.println(a==c);
System.out.println(a==d);
System.out.println(a==e);
System.out.println("a的hashcode:"+System.identityHashCode(a));
System.out.println("b的hashcode:"+System.identityHashCode(b));
System.out.println("c的hashcode:"+System.identityHashCode(c));
System.out.println("d的hashcode:"+System.identityHashCode(d));
System.out.println("d的hashcode:"+System.identityHashCode(e));
}
结果如下:
对于String str="ltt"+"jiayou",这个语句是创建了一个字符串对象即“lttjiayou”,因为str的数值在编译时就能确定下来。
2.String是不可变的字符串,不可变类总是线程安全的,当String对象创建完成后,该String对象里包含的字符序列就被确定下来就不能再改变了。
StringBuffer和StringBuilder是可变的字符串,StringBuffer是线程安全的,StringBuilder是非线程安全的。
字符串比较,对于String来说,它改写了equals方法,所以两个字符串对象只要内容是一样的,那么equals比较就是true,但是==比较就是false;但是对于StringBuilder来说,它没有改写equals方法,因此即使内容一样的两个对象,那么equals和==比较的结果都是false。
public static void main(String[] args){
String a=new String("ltt");
String a1=new String("ltt");
System.out.println("a.equals(a1):"+a.equals(a1)+",a==a1:"+(a==a1));
System.out.println("a的hashcode:"+System.identityHashCode(a));
a=a+"jiayou";
System.out.println("a+'jiayou'后的hashcode:"+System.identityHashCode(a));
StringBuilder b=new StringBuilder("ltt");
StringBuilder b1=new StringBuilder("ltt");
System.out.println("b.equals(b1):"+b.equals(b1)+",b==b1:"+(b==b1));
System.out.println("b的hashcode:"+System.identityHashCode(b));
b.append("jiayou");
System.out.println("b+'jiayou'后的hashcode:"+System.identityHashCode(b));
}
结果如下:
二、表达式类型的陷阱
1.当一个算术表达式中包含多个基本类型的数值时,整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。下图是操作数的等级排列,位于箭头右边的等级高于箭头左边的类型等级。
定义一个short类型变量,当进行第二条语句时,s会自动提升到int类型,因此第二条语句的右边的表达式就是int类型,把int类型的数值赋值给short类型就会报错,给int类型就不会报错。
2.复合赋值运算符
e1 op e2等价于e1=(e1的类型)(e1 op e2)即复合赋值运算符包含了一个隐式的类型转换即会把计算结果强制类型转换为左侧变量的类型。
a1+=5等价于a1=(a1的类型)(a1+5),如下表达式就没有问题:
如果结果的类型与该变量类型相同,则这个转型不会有问题,但是如果结果的类型比该变量的类型要大,则这个强制类型转换可能会导致高位截断,如下:
short类型的范围是-32768~32768,因此加90000后超出了范围则会被高位截断,结果是24469.对于字符串类型来说,+=左面的变量只能是String类型.
三、泛型
参数化类型即允许我们在创建集合时指定元素的类型,Java的参数化类型就称作是泛型。
1不存在泛型类,我们可以把ArrayList<String>当做是ArrayList的子类,但是事实上ArrayList<String>并不是一个类,只是这个对象能添加String对象.
2类型通配符
List<?> List<? extends Number> List<? super Integer>这些都是,注意的是如果用了这种类型通配符是不能向集合里添加元素的,因为元素并不知道这个集合是什么类型的,只有null能添加进去,其他的都添加不进去。<? extends Number>中?代表的是Number或者是它的子类,<? super Integer>中?代表的是Integer或者是它的父类。
注意的是Integer是Number的子类,但是在集合中List<Integer>不是List<Number>的子类。
3泛型方法
4.擦除
当把一个具有泛型信息的对象赋值给另一个不具有泛型信息的变量时,所有在尖括号之间的类型信息都会被丢弃。当把List<String>类型的变量赋值给List变量,则该List变量的类型变成了String的父类即Object,或者如果把List<Integer>类型的变量赋值给List,则该List变量的类型变成了Integer的父类即Number。
5.没有泛型数组
6当把一个原始类型赋值给一个具有泛型的变量时,编译是可以通过的,,编译器总会吧集合元素当成泛型的类型进行处理,不关心元素的实际类型是什么,因此元素输出是没有问题的,但是如果要用集合里面的元素时,如果集合元素的实际类型和集合所带的泛型类型不一致,则会报错。
四、多线程
需要注意的一点是不要认为静态初始化块里面的代码一定是类初始化操作,如果里面有一个线程的run方法,那么它新线程的线程执行体,不是类初始化的操作;同样非静态代码块中启动新的线程的run方法也只是新线程的线程执行体,不是对象初始化操作。