Java进阶5 面向对象的陷阱 20131103
Java是一门纯粹面向对象的编程语言,Java面向对象是基础,而且面向对象的基本语法非常多,非常的细,需要程序员经过长时间的学习才可以掌握。本章重点介绍的是Java中面向对象中的容易出现的陷阱。
1. instanceof 运算符的陷阱
Instanceof运算符前一个操作数通常是一个引用类的变量,后一个操作数是一个类,也可以是接口,用于判断前面的对象是不是后面类或者其子类,如果是的话,则返回true反之则返回false。但是也会有一些陷阱:
String str = "yang";
System.out.println( (str instanceof String));
System.out.println((str instanceof Comparable));
//System.out.println((str instanceof Math));
最后面的一句代码就会出现编译失败,因为instanceof 运算符是不可以随便使用的,Java语言中对于instanceof的规范是:instanceof前面的操作数编译时的类型必须是如下三种中的一种:
与后面类的相同的类,或者是后面类的父类的对象,或者是后面类的子类的对象。
如果不满足这写情况的话,程序是无法通过编译的,因此在使用instanceof运算符的时候,要从编译的角度去考虑。只有通过编译之后,才可以考虑他的运算结果。
在编译阶段实现数据类型的强制转换必须是满足下面几个条件:被转的变量编译的时候和目标类型是相同的;被转的变量类型在编译的时候是目标类型的父类;被转的对象在编译的时候类型是目标类型的子类,但是这种情况之下,可以自动向上转换的,没有必要强制性转换。
在运行阶段的变量类型转换,被转型变量所引用的实际类型必须是目标类型的对象或者是目标类型的子类,否则会抛出异常。
Object obj = “yang”;
String objStr = (String) obj;//编译、运行都会正常,因为实际指向的是String的父类Object,可以转换;
Object objPri = new Integer(5);
String str = (String)objPri;//编译期间是正常的,因为Object是String的父类;但是运行的时候,就会报错,因为实际指向的是Integer类型的对象和String不相关;
String s = “yang”;
Math m = (Math) s;编译出错,因为Math和String是不相关的。
对于null的话,返回的是false,因为实际的变量没有引用任何对象,这样就会保证了第一个对象操作数不是null。
2.构造器的陷阱
构造函数有没有返回值,这一点争论不休,但是有一点是可以确定的,就是不可以显示的声明返回值类型,也不能够使用void声明构造器没有返回值。如果在构造函数之前,添加了void等等返回之类型,构造函数就会退化成为一个普通的成员函数,但是编译器是不会报错的。
构造函数实际上是没有创建对象的,构造器只是负责对象的初始化工作,在构造器执行之前,java对象所需要的内存空间已经分配好了,是由关键字new实现的,而不是构造器。绝大多数的情况之下都会使用new关键字和构造函数生成并且初始化一个对象。但是有些时候是没有必要使用构造函数的:
使用反序列化方式恢复Java对象;使用对象的Clone方法赋值java对象。
public class TestMain implements Serializable{
private String name;
public TestMain(String name){
this.name = name;
}
public boolean equals(Object obj){
if (this == obj ) {
return true;
}
if(obj.getClass() == this.getClass()){
TestMain target = (TestMain) obj;
return target.name.equals(this.name);
}
return false;
}
public int hasCode(){
return name.hashCode();
}
public static void main(String []args) throws FileNotFoundException, IOException, ClassNotFoundException{
TestMain t = new TestMain("yangtengfei");
System.out.println("finish create the object");
TestMain t2 = null;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.bin"));
oos.writeObject(t);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.bin"));
t2 =(TestMain) ois.readObject();
System.out.println(t.equals(t2));
System.out.println(t2.name);
}
}
实现反序列化,从ObjectInputStream中恢复对象,该Class需要实现Serialization接口,使用字节流ObjectInputStream和ObjectOutputStream对象字节输入输出流,这样将对象格式化够传输,然后使用readObject和writeObject写入和恢复对象。这个时候,JVM中存在的是两个对象。这样使用反序列化机制就可以创建对个对象,即使把构造器声明为private,也是可以实现多个实例的。
当然可以防止这种事情的发生,在类中重写函数private Object readResolve(){ return instance;}这样的话,当反序列化恢复一个对象的时候,就会自动调用这个readResolve方法返回已经指定好的对象。
再有一种方法就是使用clone方法,该类需要实现Cloneable接口,为该类提供clone函数
注意在普通的代码块中不要调用构造函数,否则会无限期的递归调用下去。