Java 面向對象的特徵
繼承
繼承是從已有類得到繼承信息創建新類的過程。提供繼承信息的類被稱爲父類(超類、基類);得到繼 承信息的類被稱爲子類(派生類)。繼承讓變化中的軟件系統有了一定的延續性,同時繼承也是封裝程序中可變因素的 重要手段。
封裝
通常認爲封裝是把數據和操作數據的方法綁定起來,對數據的訪問只能通過已定義的接口。面向對象的本質就是將現實世界描繪成一系列完全自治、封閉的對象。我們在類中編寫的方法就是對實現細節的一種封裝;我們編寫一個類就是對數據和數據操作的封裝。可以說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程 接口。
多態性
多態性是指允許不同子類型的對象對同一消息作出不同的響應。簡單的說就是用同樣的對象引用調用同樣的方法但是做了不同的事情。多態性分爲編譯時的多態性和運行時的多態性。如果將對象的方法視爲對象向外 界提供的服務,那麼運行時的多態性可以解釋爲:當 A 系統訪問 B 系統提供的服務時,B 系統有多種提供服務的方式, 但一切對 A 系統來說都是透明的。方法重載(overload)實現的是編譯時的多態性(也稱爲前綁定),而方法重寫(override)實現的是運行時的多態性(也稱爲後綁定)。運行時的多態是面向對象最精髓的東西,要實現多態需要做 兩件事:1. 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法);2. 對象造型(用父類型引用引用子類型對 象,這樣同樣的引用調用同樣的方法就會根據子類對象的不同而表現出不同的行爲)。
抽象
抽象是將一類對象的共同特徵總結出來構造類的過程,包括數據抽象和行爲抽象兩方面。抽象只關注對 象有哪些屬性和行爲,並不關注這些行爲的細節是什麼。
訪問權限修飾符
修飾符 |
當前類 |
同 包 |
子 類 |
其他包 |
public |
√ |
√ |
√ |
√ |
protected |
√ |
√ |
√ |
× |
default |
√ |
√ |
× |
× |
private |
√ |
× |
× |
× |
clone 對象
1、爲什麼要用 clone?
在實際編程過程中,我們常常要遇到這種情況:有一個對象 A,在某一時刻 A 中已經包含了一些有效值,此時可 能會需要一個和 A 完全相同新對象 B,並且此後對 B 任何改動都不會影響到 A 中的值,也就是說,A 與 B 是兩個獨立 的對象,但 B 的初始值是由 A 對象確定的。在 Java 語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需 求雖然有很多途徑,但實現 clone()方法是其中最簡單,也是最高效的手段。
2、new 一個對象的過程和 clone 一個對象的過程區別。
new 操作符的本意是分配內存。程序執行到 new 操作符時,首先去看 new 操作符後面的類型,因爲知道了類型, 才能知道要分配多大的內存空間。分配完內存之後,再調用構造函數,填充對象的各個域,這一步叫做對象的初始化,構造方法返回後,一個對象創建完畢,可以把他的引用(地址)發佈到外部,在外部就可以使用這個引用操縱這個對象。
clone 在第一步是和 new 相似的,都是分配內存,調用 clone 方法時,分配的內存和原對象(即調用 clone 方法 的對象)相同,然後再使用原對象中對應的各個域,填充新對象的域,填充完成之後,clone 方法返回,一個新的相同 的對象被創建,同樣可以把這個新對象的引用發佈到外部。
clone對象的使用
1、複製對象和複製引用的區別
1. Person p = new Person(23, "zhang");
2. Person p1 = p;
3. System.out.println(p);
4. System.out.println(p1);
當 Person p1 = p;執行之後, 是創建了一個新的對象嗎? 首先看打印結果:
1.com.itheima.Person@2f9ee1ac
2.com.itheima.Person@2f9ee1ac
可以看出,打印的地址值是相同的,既然地址都是相同的,那麼肯定是同一個對象。p 和 p1 只是引用而已,他們 都指向了一個相同的對象 Person(23, “zhang”) 。 可以把這種現象叫做引用的複製。上面代碼執行完成之後,內存中的情景如下圖所示:
而下面的代碼是真真正正的克隆了一個對象:
1.Person p = new Person(23, "zhang");
2.Person p1 = (Person) p.clone();
3.System.out.println(p);
4.System.out.println(p1);
從打印結果可以看出,兩個對象的地址是不同的,也就是說創建了新的對象, 而不是把原對象的地址賦給了一個 新的引用變量:
1. com.itheima.Person@2f9ee1ac
2. com.itheima.Person@67f1fba0
以上代碼執行完成後, 內存中的情景如下圖所示:
2、深拷貝和淺拷貝
上面的示例代碼中,Person 中有兩個成員變量,分別是 name 和 age, name 是 String 類型,age是int類型。代碼非常簡單,如下所示:
package com.ithao.main;
public class Person implements Cloneable
{
private int age;
public Person()
{
super();
}
public Person(int age, String name)
{
super();
this.age = age;
this.name = name;
}
private String name;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Override
protected Object clone()
throws CloneNotSupportedException
{
return super.clone();
}
}
由於 age 是基本數據類型,那麼對它的拷貝沒有什麼疑議,直接將一個 4 字節的整數值拷貝過來就行。但是 name 是 String 類型的, 它只是一個引用,指向一個真正的 String 對象,那麼對它的拷貝有兩種方式: 直接將原對象中的 name 的引用值拷貝給新對象的 name 字段, 或者是根據原 Person 對象中的 name 指向的字符串對象創建一個 新的相同的字符串對象,將這個新字符串對象的引用賦給新拷貝的 Person 對象的 name 字段。這兩種拷貝方式分別 叫做淺拷貝和深拷貝。深拷貝和淺拷貝的原理如下圖所示:
如果兩個 Person 對象的 name 的地址值相同, 說明兩個對象的 name 都指向同一個 String 對象,也就是淺拷貝;而如果兩個對象的 name 的地址值不同,那麼就說明指向不同的 String 對象, 也就是在拷貝 Person 對象的時候, 同時拷貝了 name 引用的 String 對象, 也就是深拷貝。
3、淺拷貝驗證
package com.ithao.main;
public class CloneDemo
{
public static void main(String[] args) throws Exception
{
Person p = new Person(24,"張無忌");
Person p1 = (Person)p.clone();
String result=p.getName()==p1.getName()?"clone 是淺拷貝的":"clone 是深拷貝的";
System.out.println(result);
}
}
打印結果爲:clone 是淺拷貝的。所以,clone 方法執行的是淺拷貝, 在編寫程序時要注意這個細節。
4、深拷貝驗證
如果想要深拷貝一個對象,這個對象必須要實現 Cloneable 接口,實現 clone 方法,並且在 clone 方法內部,把該對象引用的其他對象也要 clone 一份,這就要求這個被引用的對象必須也要實現 Cloneable 接口並且實現 clone 方法。那麼,按照上面的結論,實現以下代碼 Body 類組合了 Head 類,要想深拷貝 Body 類,必須在 Body 類的 clone 方法中將 Head 類也要拷貝一份。代碼如下:
package com.ithao.main;
public class CloneDemo
{
static class Body implements Cloneable
{
public Head head;
public Body()
{
super();
}
public Body(Head head)
{
super();
this.head = head;
}
@Override
protected Object clone()throws CloneNotSupportedException
{
Body newBody = (Body)super.clone();
newBody.head = (Head)head.clone();
return newBody;
}
}
static class Head implements Cloneable
{
public String name;
public Head()
{
super();
}
public Head(String name)
{
super();
this.name = name;
}
@Override
protected Object clone()throws CloneNotSupportedException
{
return super.clone();
}
}
public static void main(String[] args)
throws Exception
{
Body body = new Body(new Head("張無忌"));
Body body1 = (Body)body.clone();
System.out.println("body == body1 : " + (body == body1));
System.out.println("body.head == body1.head : " + (body.head == body1.head));
}
}
打印結果爲:
body == body1 : false
body.head == body1.head : false
Java 中實現多態的機制
靠的是父類或接口定義的引用變量可以指向子類或具體實現類的實例對象,而程序調用的方法在運行期才動態綁定,就是引用變量所指向的具體實例對象的方法,也就是內存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。