一、類的封裝詳解
在“面向對象編程基礎(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("我是子類獨有方法"); } }