面向物件核心技術(java)
一、類的封裝詳解
在“面向物件程式設計基礎(java)”的時候講過,封裝是面向物件程式設計的核心思想。同時我們也知道類是載體,只不過我們把物件的屬性和行為封裝在載體中。
現我們用封裝的方式來實現,一個顧客去一家餐飲吃飯,點了一份西紅柿炒蛋。
分析:
- 顧客去餐館,要跟餐館服務員點菜
- 餐館服務員拿選單去通知後廚的廚師
- 廚師拿到選單,開始準備工作和烹飪
注意:顧客是把想吃的菜告訴了餐館服務員,那麼顧客是可以跟餐館服務員進行接觸的,同時服務員還要記錄顧客想吃的菜品。因為要把這個菜品轉達給後廚廚師。所以後廚廚師是一直在後面沒有跟顧客接觸。
//定義餐館類,為顧客提供服務 public class Restaurant{ //每一個餐館都有廚師 private Cook cook = new Cook(); public static void main(String[] args){ //例項化餐館,提供服務 Restaurant rest = new Restaurant(); System.out.println("我想吃西紅柿炒蛋,幫我來一份"); rest.takeOrder("西紅柿炒蛋"); } //有為顧客下單行為 public void takeOrder(String dish){ cook.cooking(dish); System.out.println(dish + "已經炒好了,請慢慢享用"); } } //定義廚師類 class Cook{ //廚師名字 private String name; public Cook(){ this.name = "託尼"; } //洗菜行為 private WashDishes(){ System.out.println(this.name + "洗菜"); } //炸花生米行為 private void Rice(){ System.out.println(this.name + "炸花生米"); } //炒菜行為 public void Cooking(String dish){ WashDishes(); Rice(); System.out.println(name + "開始炒" + dish); } }
在簡述封裝時,就提到過手機類,關於手機當中的功能使用者只要負責使用,內部怎麼實現不要管。那麼關於餐館,顧客是跟服務員進行溝通而餐館的廚師並沒有和顧客溝通。也就是說,顧客只負責吃自己下單的菜,關於廚師是誰並不知道,所以關於這個過程我們就可以稱之為封裝。
延伸:請分別以餐館類、使用者類、服務員類、廚師類來完成這個工作。
//餐館類 public class Restaurant3 { //餐飲名字 public static String name = "三無真味私廚"; public static void main(String[] args){ Restaurant3 rest = new Restaurant3(); User user = new User(); Waiter waiter = new Waiter(); System.out.println("歡迎光臨"+name+","+user.name+",我是"+waiter.name+"為您服務"); System.out.println("請問你要吃一點什麼?"); System.out.println("給我來一份"+user.SayLive()); waiter.TakeOrder(user.SayLive()); //顧客吃完去結賬 waiter.Cash(user); System.out.println(user.name + "慢走"); System.out.println("你多大了,長得很漂亮哦!"); waiter.Que(); } } //使用者類 class User { public String name; public User(){ this.name = "老闆"; } //說出自己喜歡吃的菜 public String SayLive(){ return "西紅柿炒蛋"; } //付錢 public void Pay(double money){ System.out.println("我已經支付了" + money + ",確認一下。"); } } //服務員類 class Waiter { public String name; private int age = 19; private Cook cook = new Cook(); public Waiter(){ this.name = "趙敏"; } //下單 public void TakeOrder(String dish){ cook.GetOrder(dish); System.out.println("菜已經炒好了,請慢用"); } //收銀 public void Cash(User user){ System.out.println(user.name+"您好,本次一共消費500元"); user.Pay(500.00); } //詢問芳年 public void Que(){ System.out.println("年齡是不方便說滴"); } } //廚師類 class Cook { private String name; public Cook(){ this.name = "周芷若"; } //洗菜 private void Wash(){ System.out.println(this.name + "正清洗青菜"); } //打碎雞蛋 private void Eggs(){ System.out.println(this.name + "正在打碎雞蛋"); } //炒菜 private void cooking(String dish){ Wash(); Eggs(); System.out.println(this.name + "開始炒菜" + dish); } //接單子 public void GetOrder(String dish){ System.out.println("廚師接到顧客選單"+dish); this.cooking(dish); } }
總結:就是把一些只提供使用的功能隱藏在載體裡,也就是類中!
二、類的繼承
繼承的思想就是子類可以繼承父類原有的屬性和方法,也可以增加父類所不具備的屬性和方法,或者直接重寫父類中的某些方法。
語法:子類名 extends 父類名
分析:繼承是讓一個類繼承另一個類,只需要使用enxtends關鍵字就可以了,同時也要注意java中僅支援單一繼承,也就是一個類只有一個父類。
例:電腦類和平板電腦之間的繼承
//電腦類 class Computer { String Color = "黑色"; String Screen = "液晶顯示屏"; void startUp(){ System.out.println("電腦正在開機,請稍等...."); } } //平板電腦 public class Pad extends Computer { //平板電腦自己的屬性 String Battery = "10000毫安鋰電池"; public static void main(String[] args){ //父類自己例項化 Computer cp = new Computer(); System.out.println("Computer的顏色是" + cp.Color); //電腦類物件呼叫開機方法 cp.startUp(); //建立Pad平板電腦類 Pad ipad = new Pad(); //呼叫父類的屬性 System.out.println("ipad的螢幕是:"+ipad.Screen); //ipad呼叫自己的屬性 System.out.println("ipad的電池:" + ipad.Battery); ipad.startUp(); } }
分析:從上面程式碼可以得出一個結論,繼承只是關係。父類和子類都可以進行例項化,子類物件可以去使用父類的屬性和方法,而不要做具體實現。同時子類也可以擴充套件自己的屬性和方法。關於父類屬性和方法被子類物件使用就是程式碼重複性。
三、方法的重寫
父類的成員都會被子類繼承,當父類的某個方法並不適用於子類時,就需要在子類重寫父類的這個方法。
重寫的實現
繼承不只是擴充套件父類的功能,還可以重寫父類的成員方法。重寫可以理解為覆蓋,就是在子類中將父類的成員方法名稱保留,再重新編寫父類成員方法的實現內容,更改成員方法的儲存許可權,或修改成員方法的返回資料型別。
總結:
- 資料返回型別可以進行重寫
- 重寫方法也可以增加方法的引數
- 訪問許可權只能從小到大,也就是父類為private,子類可以寫成public
注意 :
子類與父類的成員方法返回值、方法名稱、引數型別以及個數完全相同,唯一不同的是方法實現內容,這一種方式來重寫可以稱之為重構。
//子類重寫父類Eat方法,同時也修改了父類Eat方法的返回型別 public class Mans extends Humans{ public String Eat(){ return "吃東西行為"; } public static void main(String[] args){ Mans m = new Mans(); System.out.println(m.Eat()); } } //人類 class Humans{ public void Eat(){ System.out.println("吃東西的行為"); } }
//子類重寫父類方法Eat(),並在子類Eat()方法中可以接收一個引數 public class Mans extends Humans{ public void Eat(String name){ System.out.println("吃東西行為" + name); } public static void main(String[] args){ Mans m = new Mans(); m.Eat("利害吧!"); } } class Humans{ public void Eat(){ System.out.println("吃東西行為"); } }
//重寫父類方法Eat(),重寫內容是修改父類的訪問許可權 public class Mans extends Humans{ public void Eat(){ System.out.println("吃東西行為"); } public static void main(String[] args){ Mans m = new Mans(); m.Eat(); } } class Humans{ private void Eat(){ System.out.println("吃東西行為"); } }
子類如何來呼叫父類屬性和方法
如果子類重寫了父類的方法之後就無法呼叫父類的方法了嗎?答,是可以的,在子類重寫父類方法時還想呼叫父類方法可以使用super關鍵字來呼叫父類的方法。
在子類裡super關鍵字代表父類物件。
public class Child extends Supers{ public void Pint(){ super.Pint(); System.out.println("我重寫了父類的方法"); } public static void main(String[] args){ Supers s = new Supers(); s.Pint(); } } class Supers{ public void Pint(){ System.out.println("我是父類的方法"); } }
四、所有類的父類——Object
在java中所有的類都直接或者間接繼承了java.lang.Object類。也稱Object為基類。所以Object類比較特殊,因為他是所有類的父類,是java類中最高層類。也就是說,在我們建立一個class時,如果沒有指定該類的繼承類,那麼java.leng.Object類都是他們預設的繼承類
public Humans{} => public Humans extends Object{}
分析:在Object類中主要包括clone()、finalize()、equals()、toString()等這一些方法。所以所有的類都可以重寫Object類中的方法。
注意:Object類中的getClass()、notify()、notifyAll()、wait()等方法是不能重寫的,因為這一些方法定義了final型別,關於final型別下面會講到。
1.getClass()方法
getClass()方法會返回某個對執行時的Class例項也就是物件,再通過Class例項呼叫getName()方法獲取類的名稱。
//得到一個物件的類名稱 public class Hello{ public static void main(String[] args){ Hello h = new Hello(); System.out.println(h); //Hello } public String toString(){ return getClass().getName(); } }
總結:getClass()和getName()這兩個方法的配合就是獲取物件的類名。
2.equals()方法在Object類中是用來比較兩個物件的引用地址是否相等。
class H{} public class Hello{ public static void main(String[] args){ String s1 = new String("111"); String s2 = new String("222"); System.out.println(s1.equals(s2)); //true H h1 = new H(); H h2 = new H(); System.out.println(h1.equals(h2)); //false } }
五、類的多型
多型在程式裡面的意思就是一種定義有多種實現。例如:java裡的“+”有個個數
相加、求和,還有就是字串連線符。類的多型性可以從兩個方面體現:一是方法的過載,二是類的上下轉型。
5.1 方法的過載
構造方法的名稱由類名決定。如果以不同的方式建立類的物件,那麼就需要使用多個形參不同的構造方法來完成。在類中如果有多個構造方法存在,那麼這一些構造方法的形參個數不一相同,而且必須且到方法“方法的過載”。那麼方法的過載就是在同一個類中允許同時存在多個同名方法,只要這些方法的引數個數或者型別不同就可以。
public class OverLoadDemo{ public static int add(int a){ return a; } public static int add(int a, int b){ return a + b; } public static double add(double a, double b){ return a + b; } public static int add(int a, double b){ return (int)(a + b); } public static int add(double a, int b){ return (int)(a + b); } public static int add(int... a){ int sum = 0; for (int a = 0; i < a.length; i++){ sum += a[i]; } return sum; } public static void main(String[] args){ System.out.println("呼叫add(int)方法:" + add(1)); System.out.println("呼叫add(int,int)方法:" + add(1,2)); System.out.println("呼叫add(double,double)方法:" + add(1.5,2.9)); System.out.println("呼叫add(int,double)方法:" + add(15,2.9)); System.out.println("呼叫add(double,int)方法:" + add(15.8,9)); System.out.println("呼叫add(int... a)方法:" + add(1,2,3,4,5,6,7,8,9)); System.out.println("呼叫add(int... a)方法:" + add(1,2,3,4,5)); } }
注意:在類方法中只要方法的引數個數或者型別不同,並且方法名稱一樣,那麼這樣一來就構成了過載
5.2 向上轉型
子類引用的物件轉化為父類型別稱為向上轉型。通俗地來說就是將子類物件轉為父類物件。此處父類物件也可以是介面。
class Parent { public static void draw(Parent p){ System.out.println("我開始來了"); } } public class Zhuangxing extends Parent { public static void main(String[] args){ Zhuangxing zx = new Zhuangxing(); zx.draw(zx); }
Parent是我們Zhuangxing的父類,父類中有一個draw()有一個引數,這個引數的型是Parent,也就是父類自己。在Zhuangxing類中的主方法裡我們得到子類物件,並呼叫了父類的draw()方法,同時也吧子類物件作為引數傳入draw()方法。因為Parent類和Zhuangxing是繼承關係,所以吧子類物件賦值給父類型別物件時,這我們就管他稱為向上轉型。
class Parent{ public static void hello(){ System.out.println("你好"); } } public class Zhuangxing extends Parent{ public String name = "張三"; public static void main(String[] args){ //向上轉型是子類物件當成是父類物件 //Zhuangxing zx = new Parent(); 這是錯誤的 Parent p = new Zhuangxing(); //把子類物件當成父類物件看 p.name; //報錯,找不到此方法 } }
分析:
在向上轉型時,父類的物件無法呼叫子類獨有的屬性或者方法。而我們父類物件呼叫自己的屬性或者方法是可以的。這其實就是向上轉型的一個特性,向上轉型的物件會遺失子類中父類沒有的方法,而且子類的同名方法會覆蓋父類的同名方法。相當於向上轉型時,改物件對於只存在子類中而父類中不存在的方法是不能訪問的。同時,若子類重寫了父類的某些方法,在呼叫這些方法時,實際上呼叫的是子類定義的方法,這也就是動態連結、動態呼叫。通俗的來理解,蘋果可以是水果,但不能說水果就是蘋果。如果要讓水果呼叫蘋果的特有屬性和方法,這是不符合常理的。
class Fruit //水果類 { public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit //蘋果類並繼承水果類 { public static void main(String[] args){ //向上轉型 Fruit apple = new Apple(); apple.sayName(); apple.Hello(); //這裡就會報錯,說沒有此方法 } //這是蘋果類獨有的方法 public void Hello(){ System.out.println("我是蘋果類獨有的方法"); } public void sayName(){ System.out.println("我是蘋果..."); } }
總結:向上轉型是對父類物件的方法進行擴充,即父類物件可以訪問子類物件重寫父類的方法。
5.3 向下轉型
向下轉型就是指父類型別的物件轉型為子類型別。也就是,宣告的是子類型別,但引用的是父類型別的物件。
同時向上轉型可以由java編譯器自動來完成的,但是向下轉型就要人工強制轉換。
class Fruit{ public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit{ public void sayName(){ System.out.println("我是蘋果"); } //子類獨有方法 public void Hello(){ System.out.println("我是子類獨有方法"); } public static void main(String[] args){ Fruit apple = new Apple(); //父類型別引用的是子類物件 apple.sayName(); //輸出的是 - 我是蘋果 //強制轉播換->父類型別Fruit的物件apple向下轉型為子類型別Apple Apple fruit = (Apple)apple; fruit.sayName(); //我是蘋果 fruit.Hello(); //不會報錯->輸出我是子類獨有方法內容 } }
分析:
首先apple的型別是Fruit水果類,但實際引用的是子類Apple的例項,所以apple是向上轉型Fruit型別。從程式碼來看,apple是父類型別(fruit),引用的是子類物件。所以再將apple從Fruit型別向下轉換為Apple型別是安全的。因為我們apple物件本身指向的就是子類物件。
注意-->上下轉型關於載入問題
不管是向上還是向下,載入類時都會載入到記憶體當中。不過向上轉型時,物件雖然遺失了父類沒有同名的方法,但這些已經載入到了記憶體當中,因為是向上轉型,所以物件不能呼叫這一些方法罷了。同時載入類,除了物件特有的屬性不會被載入外,其他的都會被載入。
向上和向下轉型總結
- 向上轉型和向下轉型是實現多型的一種機制
- 向上轉化編譯器自動實現,目的是抽象化物件,簡化程式設計
- 向上轉型時,子類將遺失父類不同名的方法,子類同名的方法將覆蓋父類的方法
- 向下轉型需要強制轉換
- 向下轉型要注意物件引用的是子類物件還是父類物件,引用子類物件不會報錯,引用父類就會報錯
同時向上轉型和向下轉型是java抽象程式設計的奧祕。向上轉型是實現多型的一種機制,我們可以通過多型性提供的動態分配機制執行相應的動作,使用多型編寫的程式碼不使用多種型別進行檢測的程式碼更加易於擴充套件和維護。
5.4 instanceof關鍵字
class Fruit{ public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit{ public static void main(String[] args){ //fruit是父類型別引用指向的還是父類型別 Fruit fruit = new Fruit(); //Fruit型別的fruit向下強制轉型為Apple型別 Apple apple = (Apple)fruit; apple.sayName(); } public void sayName(){ System.out.println("我是蘋果"); } }
程式碼編譯環境不會報錯,但執行程式碼就會包java.lang.ClassCastException:Fruit cannot be cast to Apple錯。這也是我們剛開始學習轉型時會遇到的場景。
物件A instanceof 類B => 翻譯就是:物件A是否為類B的例項,如果是返回true,否則為false
class Fruit{ public void sayName(){ System.out.println("我是水果"); } } public class Apple extends Fruit{ public static void main(String[] args){ Fruit apple = new Apple(); // 向下轉型 Apple fruit = null; //判斷apple是不是Apple的例項 if (apple instanceof Apple){ fruit = (Apple)apple; } fruit.sayName(); //我是蘋果 fruit.Hello();//我是子類獨有方法 } public void sayName(){ System.out.println("我是蘋果"); } public void Hello(){ System.out.println("我是子類獨有方法"); } }
&n