時間:2018-07-21
作者:魏文應(yīng)
一沙峻、對象的多態(tài)性
public class Person {
public void eat(){
System.out.println("人吃飯");
}
public void animal(){
System.out.println("人是一種動物");
}
}
public class Man extends Person{
public void eat(){
System.out.println("男人吃飯比較多!");
}
public void entertain(){
System.out.println("男人請客。");
}
}
public class TestPerson {
public static void main(String[] args){
Person p = new Man();
}
}
看上面代碼志电,你會發(fā)現(xiàn),一般我們是 Person p = new Person();
去創(chuàng)建一個對象實例蛔趴,但這里使用的是 Person p = new Man();
挑辆。這里,就使用了 對象的多態(tài)性。這樣鱼蝉,就可以使用子類中洒嗤,子類重寫的那些方法了。什么意思呢魁亦?看下面例子:
Person p = new Man();
p.eat(); // System.out.println("男人吃飯比較多渔隶!");
這時,使用父類 Person 的 引用p 洁奈,p指向 子類Man的一個對象實體间唉,p.eat() 實際執(zhí)行 的是 子類Man中的eat()方法,這就被叫做 虛擬方法調(diào)用 利术。下面調(diào)用是 錯誤的:
Person p = new Man();
p.entertain(); // 這樣使用是錯誤的呈野,不能使用entertain()方法
因為父類Person 并沒有entertain這個方法,這也說明了印叁,這種情況下被冒,如果要 使用子類的方法,只能使用子類中重寫父類的方法(比如轮蜕,Man中有eat()方法姆打,要使用Man中的eat(),父類Person也必須要有一個叫做eat()的方法)肠虽。下面調(diào)用是 正確的:
Person p = new Man();
p.animal();
雖然子類沒有 animal() 方法幔戏,但父類Person是有這個方法的,所以可以使用税课。直觀的表現(xiàn)就是: 父類Person中的所有方法都可以使用闲延,也只能使用這些方法。只是有些方法被子類重寫了韩玩,執(zhí)行的時候垒玲,跑去執(zhí)行子類的方法去了。 這里說的父類的所有方法找颓,包括父類從其它類繼承來的方法合愈。
子類對象的多態(tài)性使用的前提
其實上面講的那么多,實際上是和 程序有編譯狀態(tài)和運行狀態(tài) 有關(guān)击狮,對于下面語句:
Person p = new Man();
在使用 p 這個引用時佛析,編譯器 只會檢查 Person p
有哪些屬性和方法,它 不管 new Man()
有哪些方法和屬性彪蓬,只要 Man 是 Person 的子類就行了寸莫。
- 要有類的繼承關(guān)系,比如上面的 Person 和 Man 就有繼承關(guān)系档冬。
- 要有子類對父類方法的重寫膘茎,比如上面的子類Man中桃纯,對父類Person eat() 方法的重寫(這不是必須的,但如果不這樣披坏,
Person p = new Man();
和Person p = new Person ();
的效果就是一樣的了态坦, 多態(tài)性的作用沒有很好的體現(xiàn)出來)。
多態(tài)性的用途
假設(shè)我們有下面三個類棒拂,三個類都有 eat() 方法:
public class Person {
public void eat(){
System.out.println("人吃飯");
}
}
public class Man extends Person{
public void eat(){
System.out.println("男人吃飯比較多驮配!");
}
}
public class Man extends Person{
public void eat(){
System.out.println("女人吃飯比較文雅!");
}
}
public class TestPerson {
public static void main(String[] args){
TestPerson t = new TestPerson();
Person m = new Man();
t.eat(m); // 使用的是Man 類的eat()方法
Person w = new Woman();
t.eat(w); // 使用的是Woman 類的eat()方法
Person p = new Person();
t.eat(p); // 使用的是Person 類的eat()方法
}
public void eat(Person p){
p.eat();
}
public void eat(Man m){
m.eat();
}
public void eat(Woman w){
w.eat();
}
}
上面 TestPerson 類中的三個 eat() 方法之間構(gòu)成方法重載着茸。如果除了 Person、Man琐旁、Woman
涮阔,還有更多的方法需要去重載呢?這樣就會太多方法重載了灰殴,寫起來也很麻煩敬特。因此:
public class TestPerson {
public static void main(String[] args){
TestPerson t = new TestPerson();
Person m = new Man();
t.eat(m); // 使用的是Man 類的eat()方法
Person w = new Woman();
t.eat(w); // 使用的是Woman 類的eat()方法
Person p = new Person();
t.eat(p); // 使用的是Person 類的eat()方法
}
public void eat(Person p){
p.eat();
}
}
上面代碼中,只需 eat(Person p)
牺陶,就可以替代 eat(Man m)
伟阔、eat(Person p)
、eat(Woman w)
這三個方法掰伸。如果Person子類更多皱炉,替代的就越多。這樣就比較方便了狮鸭,這就是對象的多態(tài)性的用途之一合搅。
對象的多態(tài)性是對于方法來說的, 屬性是沒有多態(tài)性的歧蕉。比如下面示例:
public class Person{
int id = 1001;
}
public class Man extends Person{
int id = 1002;
}
public class TestPerson {
public static void main(String[] args){
Person p = new Man();
System.out.println(p.id); // 這時打印出的是1001灾部,是Person類的。
}
}
說明惯退,屬性是沒有多態(tài)性的赌髓。
二、類的強制類型轉(zhuǎn)換:向下轉(zhuǎn)型
子類繼承父類的屬性和方法(雖然private屬性和方法子類無法直接使用)催跪,可以認為父類是子類的子集锁蠕。所以,下面這個懊蒸,子類賦值給父類匿沛,容量大的賦值給容量小的,稱為 向上轉(zhuǎn)型 :
Person p = new Man();
容量小的賦值給容量大的榛鼎,稱為 向下轉(zhuǎn)型:
Person p = new Man();
Man m = (Man)p; // 向下轉(zhuǎn)型
m.entertain(); // 子類非重寫的方法逃呼,也可以使用了鳖孤。
轉(zhuǎn)型時,我們要注意抡笼,創(chuàng)建的對象實體是否有相應(yīng)的方法:
public class Person {
}
public class Man extends Person{
}
public class Woman extends Person{
public void shopping(){
System.out.println("女人天生愛購物苏揣。");
}
}
public class TestPerson {
public static void main(String[] args){
Person p = new Man();
Woman w = (Woman)p;
w.shopping(); // 這里執(zhí)行時會報錯,因為Man沒有shopping()方法推姻。
}
}
執(zhí)行上面代碼時平匈,就會 拋出異常,因為 new Man()
時藏古,根本就沒有 shopping()方法 在方法區(qū)被創(chuàng)建增炭。編譯時語法沒有錯誤,運行時出錯了:
Exception in thread "main" java.lang.ClassCastException: my.wwy.java1.Man cannot be cast to my.wwy.java1.Woman
at my.wwy.java1.TestPerson.main(TestPerson.java:33)
三拧晕、instanceof 關(guān)鍵字
格式:
對象 instanceof 類
比如:
Man m = new Man();
if(m instanceof Man){
System.out.println("這是一個Man類");
}
這就是:判斷對象 m 是否是 類Man 的一個實例隙姿。 是的話,返回true厂捞;不是的話输玷,返回flase。instanceof 判斷時靡馁,如果是父類欲鹏,也會返回true :
public class Man extends Person{
}
Man m = new Man();
if(m instanceof Man){ // 返回true
System.out.println("這是一個Man類");
}
if(m instanceof Person){ // 這個同樣返回true
System.out.println("這是一個Person類");
}
上面的 m instanceof Man
和 m instanceof Person
判斷都返回 true
。想想也是臭墨,Man 繼承了 Person 赔嚎,也就繼承了Person 的方法和屬性,也算是 Person 類的一個實例胧弛。
四尽狠、Object 類及其 equals() 方法
equals() 方法在Object類中,其源代碼是這樣的:
public boolean equals(Object obj) {
return (this == obj);
}
說明其輸入?yún)?shù)是 類的對象叶圃。比較的是兩個引用變量的 地址值 袄膏。比如下:
class Person{
}
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.equals(p2)); // false柑肴, 比較了p1的值是否和p2值相等
上面的p1和p2值不一樣抢腐,返回false淤井。
String str1 = new String("AA");
String str2 = new String("AA");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
上面的 String 類输瓜,重寫了Object類的 equals() 方法窃这,變成比較的是內(nèi)容是否一樣缘厢,而不是地址萝毛。
五楷力、String 類在內(nèi)存中存儲
String 類的內(nèi)容眉厨,是存儲在常量池中的:
String str1 = "AA";
String str2 = "AA";
String str3 = new String("AA");
System.out.println(str1 == str2); // true
System.out.println(str1.equals(str2)); // true
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str3)); // true
以 String str1 = "AA"
這種方式創(chuàng)建String對象時锌奴,引用變量 str1 直接保存的是常量池中的 AA
字符串的地址。如果以 String str3 = new String("AA")
方式new出來的String變量憾股,str3 保存了內(nèi)存堆的String地址鹿蜀,而內(nèi)存堆中同樣保存 "AA" 字符串在常量池中的位置箕慧,而不是直接在內(nèi)存堆中 new 出 String 字符串 “AA” 。如果 “AA” 被修改為 “AB” 呢茴恰?那么就重新指向處理池中的 “AB”颠焦。如果常量池沒有 “AB”,則常量池自動創(chuàng)建 “AB” 字符串往枣。
六伐庭、toString() 方法的使用
比如,我們有下面代碼:
class Person {
}
Person p1 = new Person();
System.out.println(p1.toString()); // my.wwy.java.Person@311e170c
System.out.println(p1); // my.wwy.java.Person@311e170c
你發(fā)現(xiàn)上面打印的信息一樣分冈,打印的都是引用變量的值圾另,也就是 內(nèi)存地址值。toString() 方法在JDK中源代碼如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
在我們的代碼中雕沉,在定義類時集乔,應(yīng)該顯式地 重寫 toString() 方法,使得調(diào)用toString()類時蘑秽,能夠 打印類的屬性信息 :
public String toString(){
return "Person: " + "name:" + name + ";" + "age:" + age;
}
當然,也可以使用 Eclipse 提供的箫攀,自動生成 toString() 方法肠牲。將光標放在需要創(chuàng)建 toString()方法的類的內(nèi)部,依次選擇 Source -> Generate toString() -> OK 即可:
自動生成的內(nèi)容靴跛,大致如下:
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
這樣缀雳,當我們每次調(diào)用 toString() 方法時,返回的是 當前類的屬性信息梢睛,而不是 引用變量的值:
public class Person {
String name = new String("魏文應(yīng)");
int age = 12;
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class TestToString {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println(p1.toString()); // Person [name=魏文應(yīng), age=12]
System.out.println(p1); // Person [name=魏文應(yīng), age=12]
}
}
很多類都對Object類中的toString()方法進行了重寫肥印,比如String類、Data類绝葡、File類深碱、包裝類等。比如下面的String:
public class TestToString {
public static void main(String[] args) {
String str = "AA";
System.out.println(str.toString()); // 打印結(jié)果為:AA
}
}
上面的 toString() 方法藏畅,是打印 str 對應(yīng)的字符串的值敷硅,而不是str保存的內(nèi)存地址值。下面是JDK的 String類中的toString() 方法源代碼:
public String toString() {
return this;
}