【Java】代碼復用(組合和繼承)


前言

代碼復用是Java眾多引人注目的功能之一。但要想成為極具革命性的語言,僅僅能夠復用代碼並對之加以改變是不夠的,它必須還能夠做更多的事情。Java解決問題都是圍繞類展開的,對於復用代碼,可以創建新的類來復用,也可以使用別人已經開發並調試好的類。方法的關鍵在於使用類而不破壞現有程序代碼。有兩種方式達成此方法的目的:組合和繼承。下面將介紹這兩種代碼重用機制。

組合和繼承的實現

在新的類中產生現有類的對象,即為組合。該方法只是復用了現有程序代碼的功能,而不是它的形式。

按照現有類的類型創建新類,無需改變現有類的形式,采用現有類的形式並在其中添加新的代碼,此方式就是繼承。

組合的實現

只需要將對象的引用置於新類中即可。

class A{
    ...
}
class B{
    private A;
    ...
}

在Java中,類中域為基本類型時會被自動初始化為對應的“零”,對象引用會被初始化為null。編譯器並不是簡單地為每一個引用都創建默認對象。若是想初始化對象引用,可以在代碼中的下列位置進行:

  • 在定義對象的地方。這也意味着它們會在構造器被調用之前被初始化。
  • 在類的構造器中。
  • 在臨近使用這些對象之前,再初始化,這種方式也叫做惰性初始化
  • 使用實例初始化。

使用《Java編程思想》上面的例子說明:

class Soap{
	private String s;
	public Soap() {
		System.out.println("Soap()無參構造器");
		s = "Constructed";		//在類構造器中初始化
	}
	
	@Override
	public String toString() { return s;}
}

public class Bath {
	private String s1 = "Happy";//在定義對象的地方初始化
	private Soap castille = new Soap();
	private String s2;
	private int i;
	
	public Bath() { System.out.println("Bath() 無參構造器");}
	//實例初始化
	{
		i = 31;
		System.out.println("初始化i為31");
	}
	
	@Override
	public String toString() {
		if(s2 == null) {		//惰性初始化
			s2 ="Java";
		}
		return s1 + "\t"+ s2 + "\t" + i + "\t" + castille;
	}
	
	public static void main(String[] args) {
		System.out.println(new Bath());
	}
}
/*
output:
Soap()無參構造器
初始化i為31
Bath() 無參構造器
Happy	Java	31	Constructed
*/

繼承的實現

Java中的繼承是使用關鍵字extends實現的,使用繼承子類將會得到父類中所有的域和方法。父類中的私有域和方法將被隱式繼承,非私有方法和域將被顯式繼承。隱式繼承可以通過一定方式被間接訪問到。

下面將以一個例子說明繼承中出現的一些問題。

class Person {
	private String name;	//私有域
	public Person() {
		System.out.println("Person() 無參構造器");
		name = "noName";
	}
	public Person(String name) {
		System.out.println("Person(String name) 帶參構造器");
		this.name  = name;
	}
	
	public String getName() { return name;}	//讓子類可以間接訪問到私有域name
	
	public String toString() { return "Person: "+name; }
}

class Student extends Person{
	public Student() {
		//默認調用 super(); 表示調用父類的無參構造器
		System.out.println("Student() 無參構造器");
	}
	
	public Student(String name) {
		super(name);	//調用父類的帶參構造器
		System.out.println("Student(String name) 帶參構造器");
	}
	
	public String toString() {	return "Student: " + getName();}
}

public class ExtendsExample {
	public static void main(String[] args) {
		System.out.println(new Student());
		System.out.println(new Student("sakura"));
	}
}
/*
output:
Person() 無參構造器
Student() 無參構造器
Student: noName
Person(String name) 帶參構造器
Student(String name) 帶參構造器
Student: sakura
*/

子類和父類的構造器調用問題

關於構造器的調用順序在前面提過,這里再着重介紹下子類和父類的構造器調用問題。

先有父才有子,Java中使用super關鍵字表示超類也就是父類。若是子類中沒有顯式調用父類的構造器,那么編譯器就會默認調用super(),即父類的默認構造器;若是父類沒有默認構造器,則子類中就必須顯式調用父類的帶參構造器,並且調用必須放在構造器中的第一行。

super()和this()可以同時存在嗎?

super()和this()是不能同時出現在構造器中,因為二者都要求被放在構造器的首行。

組合和繼承之間的區別

組合和繼承都允許在新的類中放置子對象,組合是顯式這樣做,而繼承則是隱式這樣做。

組合技術通常是用於想在新類中使用現有類的功能而非其接口的情況。即,在新類中嵌入某個對象,讓其實現所需要的功能;新類的用戶看到的只是為新類所定義的接口,而非所嵌入類的接口。

繼承是使用某個現有類並開發其特殊版本,會擁有現有類的接口並且可以為新類開發新的接口

所以,繼承關系為“is-a”(是一個),組合關系為“has-a”(有一個)。

protected關鍵字

在實際的項目中,經常會想要將某些事物盡可能對這個世界隱藏起來,但是仍然允許導出類的成員訪問它們。protected關鍵字就是實現這個作用。

它表明:對於任何繼承於此類的導出類或者任何與此類在同一個包中的類來說,被protected修飾的方法和域是可以訪問的,但是對於除這兩種類的其他類則是不可訪問的。

繼承的局限

Java中繼承的局限便是不允許類似C++那樣的多繼承,至於為什么沒有多繼承最直接的原因就為簡單編程,不用去考慮父類的父類是誰、父類的父類的父類是誰、以及自己包含的方法繼承自誰的。比如B和C繼承自A,D又繼承B、C,當D調用A中的一個方法時,是繼承自B的還是繼承自C的呢?

為了實現多繼承的效果的話可以使用多層繼承(A繼承B,B繼承C),但是最好不要超過三層,否則代碼會太亂。后面會介紹Java使用接口實現多繼承。一個類雖然不能從繼承多個基類但是可以聲明繼承多個接口(interface),從而達到多繼承效果。

小結

繼承和組合都可以從現有類型生成新類型。組合一般是將現有類型作為新類型的底層實現的一部分來加以復用,而繼承復用的是接口。在使用繼承時,由於導出的類具有基類的接口,因此它可以向上轉型至基類,這對后面要介紹的多態是至關重要的。

參考:

[1] Eckel B. Java編程思想(第四版)[M]. 北京: 機械工業出版社, 2007


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM