改善Java程序的建議(每日5條)5

目錄

  • 建議21 接口中不要存在實現代碼
  • 建議22 靜態變量一定要先聲明後賦值
  • 建議23 不要覆寫靜態方法
  • 建議24 構造函數儘量簡化
  • 建議25 避免在構造函數中初始化其他

接口中不要存在實現代碼

部分神看到這可能會疑惑:接口有實現代碼?
確實 接口中可以聲明常量,聲明抽象方法,也可以繼承父接口,但就是不能有具體實現,因爲接口是一種契約,是一種框架性的協議,表明它的實現類都是同一種類型,或者是具備相似特徵的一個集合體。
當然排除JDK1.8新特性在接口中定義 default方法以期待對項目整體添加方法又不影響實現類

public Class Test{
	public static void main(String[] args){
		B.s.doSomething();//調用接口的實現
	}
}
//在接口中存在實現代碼
interface B{
	public static final S s = new S(){
		public void doSomething(){
			System.out.println("我在接口中實現了");
		}
	}
}
//被實現的接口
interface S{
	public void doSomething();
}

在main方法執行時能打印出結果。在B接口中聲明瞭一個靜態常量S,其值是一個匿名內部類的實例對象,就是該匿名內部類實現了s接口,這就是一種在接口中存在的實現代碼。這確實挺好,很強大,但是這是一種不好的編碼習慣違背了接口的含義。如果把實現代碼寫道接口中,那接口就綁定了可能變化的因素,就會導致接口不在穩定和可靠。是隨時都可能被拋棄、更改、被重構的。

建議22 靜態變量一定要先聲明後賦值

  • 話不多說先展示代碼
public class Test{
	public static int i =1;
	static {
		i = 100;
	}
	public static void main(String[] args){
		System.out.println(i);
	}
}

這段代碼執行結果是100毋庸置疑
我們再次稍稍修改一下

public class Test{
	static {
		i = 100;
	}
	public static int i =1;
	public static void main(String[] args){
		System.out.println(i);
	}
}

變量i的聲明和複製調換了位置。 還是可以編譯,沒有任何問題,結果是1而不是100;
因爲 靜態變量是類加載時被分配到數據區的,它在內存中只有一個拷貝,不會被分配多次,其後的所有賦值操作都是值改變,地址保持不變。我們指導JVM初始化變量是先聲明空間,然後再賦值的。
也就是:

int i = 100;
是先
int i;//分配地址空間
i = 100;//賦值

靜態變量在類初始化時首先被加載的,JVM會取查找類中所有的靜態聲明,然後分配空間,這時候只是完成地址空間分配,還沒有賦值,之後JVM會根據類中的靜態賦值的先後順序來執行。也就是先聲明瞭int類型的地址空間,並把地址傳遞給了i,然後按照類中的先後順序執行賦值動作,首先執行靜態塊中i=100,接着執行i=1,所以最後的結果就是i=1了

建議23 不要覆寫靜態方法

Java中可以通過覆寫來增強或者減弱父類的方法和行爲,但是覆寫是針對非靜態方法的,不能針對靜態方法

public class Test{
	public static void main(String[] args){
		Base base = new Sub();
		base.doAnything();
		base.doSomething();
	}
}
class Base{
	public static void doSomething(){
		System.out.println("父類靜態");
	}
	public void doAnything(){
		System.out.println("父類非靜態");
	}
}
class Sub extends Base{
	public static void doSomething(){
		System.out.println("子類靜態");
	}
	public void doAnything(){
		System.out.println("子類非靜態");
	}
}
打印的結果是
子類非靜態
父類靜態

我們可能會有點困惑,同樣是調用子類的方法,一個執行了子類方法,一個執行了父類方法,差別僅僅是static修飾,卻結果不同
其實一個實例對象有兩個類型:

  • 表面類型:聲明時的類型
  • 實際類型:對象產生式的類型
    對於以上例子變量base的表面類型是Base實際類型是Sub,對於非靜態方法罵它是根據對象的實際類型來執行的,也就是執行了Sub類中的doAnything方法。對於靜態方法來說比較特殊,他是不依賴實例對象的,是通過類名訪問的。其次可以通過對象訪問,如果是對象調用,則JVM會通過對象的表面類型查找執行。

建議24 構造函數儘量簡化

在new關鍵字生成對象時候會調用構造函數,構造函數的簡繁會直接影響實例對象的創建是否繁瑣。通常情況下構造函數應該京可能簡單,儘量不拋出異常,不做複雜算法。

建議25 避免在構造函數中初始化其它

構造函數是一個類初始化必須執行的代碼,決定類的初始化效率,如果其構造函數比較複雜,而且還關聯了其它類就會產生意想不到的問題。

public class Test{
	public static void main(String[] args){
		Son s = new Son();
		s.doSomething();
	}
}
//父類
class Father{
	Father(){
		new Other();
	}
}
//子類
class Son extends Father{
	public void doSomething(){
		Sysem.out.println("hi");
	}
}
class Other{
	public Other(){
		new Son();
	}
}

這段代碼的的結果就十分神奇了,棧內存溢出,這是因爲聲明s變量時調用了Son的無參構造函數,JVM又默認調用父類的構造函數,又初始化了Other類一個死循環就形成了,直到內存被消耗完畢。當然這種一眼能看出來的場景我們細心一點,基本不會出現。
但是如果Father是框架提供Son是我們自己編寫的擴展代碼,Other是框架要求的攔截類,拿這種場景就很有可能出現了。

注意 不要再構造函數中聲明初始化其它類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章