胡八一之Java(七):面向物件的陷阱
一、instanceof的陷阱:
如果前面運算元的編譯型別與後面的型別沒有任何關係,那麼編譯將不通過。例如:
String a ="aaa";
System.out.println("a是否屬於MATH的型別:"+(a.instanceof Math));
String型別與Math無任何關係,所以編譯不通過,那麼instanceof編譯通過的條件是什麼?
通過條件:前面的運算元型別要麼與後面類相同,要麼是父/子類
二、構造器的陷阱:
當為構造器新增任何修飾符時,編譯器不會報錯,只是把它當作普通方法來處理。
構造器只是負責初始化,在構造器執行前,Java物件所需要的記憶體空間,已有new關鍵字申請建立。
那麼有木有不呼叫構造方法而建立物件的方式?
答案是肯定的。(1)使用反序列化的方式恢復Java物件。(2)使用clone()方法複製Java物件
先介紹第一種方法:
package aaaaa; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import org.omg.PortableInterceptor.ObjectIdHelper; class Wolf implements Serializable{ String name; public Wolf(String name){ System.out.println("呼叫有引數的構造方法"); this.name =name; } public boolean equal(Object obj){ if(this ==obj){ return true; } if(obj.getClass() ==Wolf.class){ Wolf target =(Wolf)obj; return target.name.equals(this.name); } return false; } public int hashCode(){ return name.hashCode(); } } public class Test{ public static void main(String args[]) throws Exception{ Wolf w =new Wolf("灰太狼"); System.out.println("Wolf物件建立完成"); Wolf w1 =null; try( //建立物件輸出流 ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("a.bin")); //建立物件輸入流 ObjectInputStream ois =new ObjectInputStream(new FileInputStream("a.bin")); ) {//反序列化輸出Java物件 oos.writeObject(w); oos.flush(); //反序列化恢復Java物件 w1 =(Wolf)ois.readObject(); //兩個物件的例項方法完全相同 System.out.println(w.equal(w1)); //兩個物件不相同 System.out.println(w ==w1); } } }
程式碼執行情況如下:
呼叫有引數的構造方法
Wolf物件建立完成
true
false
程式可以通過反序列化機制去破壞單例模式,但大部分時候也不會主動使用反序列化機制去破壞單例模式,但要想保證單例模式不會被破壞,應該為其提供readResolve()方法
如下:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.io.Serializable; class Singleton implements Serializable{ private static Singleton instance; private String name; private Singleton(String name) { System.out.println("呼叫有引數的建構函式"); this.name=name; } public static Singleton getInstance(String name) { if(instance==null) { instance =new Singleton(name); } return instance; } private Object readResolve() throws ObjectStreamException{ return instance; } } public class Test { public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub Singleton s=Singleton.getInstance("灰"); Singleton s1 =null; try ( ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("b.bin")); ObjectInputStream ois =new ObjectInputStream(new FileInputStream("b.bin")); ) { oos.writeObject(s); oos.flush(); s1 =(Singleton)ois.readObject(); System.out.println(s==s1); } } }
程式執行返回值是true;
除了使用反序列化機制恢復Java物件無需構造器之外,使用clone()方法複製Java物件也無需呼叫構造方法。
讓Java類實現Cloneable介面,為該Java類提供clone()方法,該方法負責複製。
class Dog implements Cloneable{
private String name;
private double weight;
public Dog(String name, double weight){
System.out.println("呼叫有引數的構造方法");
this.name =name;
this.weight =weight;
}
public Object clone(){
Dog dog =null;
try{
dog =(Dog)super.clone();
}
catch (CloneNotSuppottedException e){
e.printStackTrace();
}
return dog;
}
無限遞迴的構造器
因為不管是定義例項變數指定的初始值還是在非靜態初始化塊中執行初始化的操作,都將提到構造器中執行。
所以要注意以下三個方面:
1、儘量不要在宣告變數時指定值為當前物件的例項。
2、儘量不要在初始化塊中構造當前類的例項。
3、儘量不要在構造器內呼叫本類構造器來建立Java物件
當一個類的例項持有當前類的其他例項時需要特別小心,因為程式很容易形成遞迴呼叫。
在jvm中呼叫過載函式匹配引數的時候,具有一定的智慧性。
例如:將一個int型別的資料傳給一個需要double方法的引數,程式不會報錯。
public class Test {
public void info(String name,double age) {
System.out.println("name:"+name+" age:"+age);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info("張三",4);
}
}
程式結果:
name:張三 age:4.0
那麼如果我有int型別的方法也有double型別的方法,這樣會呼叫那個方法呢?
public class Test {
public void info(String name,double age) {
System.out.println("name:"+name+" age:"+age);
}
public void info(String name,int age) {
System.out.println("name:"+name+" age:"+age);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info("張三",4);
}
}
程式結果:
name:張三 age:4
jvm會精確匹配到int型別的那個方法。
那如果這樣呢?我第一個引數是null,會怎樣呢?
public class Test {
public void info(Object obj,double age) {
System.out.println("name:"+obj+" age:"+age);
}
public void info(Object[] obj,int age) {
System.out.println("name:"+obj+" age:"+age);
System.out.println("呼叫了Object[]方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info(null,4);
}
}
執行結果:
name:null age:4
呼叫了Object[]方法
根據精確匹配的原則,當實際呼叫的引數同時滿足多個方法時,如果某個方法的形參要求範圍更小時,那麼這個方法就越精確。
但jvm也有無法斷定那個方法更適合的時候,這樣就編譯不通過,例如:
public class Test {
public void info(Object obj,int age) {
System.out.println("name:"+obj+" age:"+age);
}
public void info(Object[] obj,double age) {
System.out.println("name:"+obj+" age:"+age);
System.out.println("呼叫了Object[]方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info(null,4);
}
}
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method info(Object, int) is ambiguous for the type Test
at Test.main(Test.java:14)
非靜態內部類的構造器:
public class Test {
public static void main(String[] args) throws Exception {
new Test().test();
}
public void test() throws Exception {
//建立非靜態內部類的物件
System.out.println(new Inner());//1
//使用反射來建立Inner物件
System.out.println(Inner.class.newInstance());//2
}
public class Inner{
public String toString() {
return "Inner物件";
}
}
}
一處直接通過new呼叫Inner無引數的構造方法來建立例項,二處通過反射來呼叫Inner無引數的構造器建立例項,會發現丟擲以下異常
Inner物件
Exception in thread "main" java.lang.InstantiationException: Test$Inner
at java.base/java.lang.Class.newInstance(Class.java:547)
at Test.test(Test.java:11)
at Test.main(Test.java:5)
Caused by: java.lang.NoSuchMethodException: Test$Inner.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3302)
at java.base/java.lang.Class.newInstance(Class.java:532)
... 2 more
這是因為程式表面上呼叫Inner無引數的構造器建立例項,實際上虛擬機器會將底層this(代表當前預設的Test物件)作為實參傳入Inner構造器,二處程式碼通過反射指定呼叫Inner類無參建構函式,所以引發了執行時異常。