多個類中存在相同屬性和行爲時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行爲,只要繼承那一個類便可。如圖所示:java
其中,多個類能夠稱爲子類,單獨那一個類稱爲父類、超類(superclass)或者基類。數組
繼承描述的是事物之間的所屬關係,這種關係是: is-a
的關係。例如,圖中兔子屬於食草動物,食草動物屬於動物。可見,父類更通用,子類更具體。咱們經過繼承,可使多種事物之間造成一種關係體系。dom
經過 extends
關鍵字,能夠聲明一個子類繼承另一個父類,定義格式以下:ide
class 父類 { ... } class 子類 extends 父類{ ... }
繼承演示代碼以下:函數
//定義員工類Employee做爲父類 class Employee { //定義name屬性 String name; //定義員工的工做方法 public void work() { System.out.println("全力以赴地工做"); } } //定義教師類Teacher繼承員工類Employee class Teacher extends Employee { //定義一個打印name的方法 public void printName() { System.out.println("name="+ name); } } //定義測試類 public class ExtendDemo01 { public static void main(String[] args) { //建立一個教師類對象 Teacher t = new Teacher(); //爲該員工類的name屬性賦值 t.name = "小明"; //調用Teacher類中的printName()方法 t.printName(); // name=小明 //調用Teacher類繼承來的Work()方法 t.work(); // 全力以赴地工做 } }
當類之間產生了關係後,其中各種中的成員變量,又產生了哪些影響呢?學習
若是子類父類中出現不重名的成員變量,這時的訪問是沒有影響的。代碼以下:測試
class Fu { //Fu中的成員變量 int num = 5; } class Zi extends Fu { //Zi中的成員變量 int num2 = 6; //Zi中的成員方法 public void show() { //訪問父類中的num System.out.println("Fu num = "+num); //繼承而來,因此直接訪問。 //訪問子類中的num2 System.out.println("Zi num2 = "+num2); } } class ExtendsDemo02 { public static void main(String[] args) { //建立子類對象 Zi z = new Zi(); //調用子類中的show()方法 z.show(); } } 演示結果: Fu num = 5 Zi num2 = 6
若是子類父類中出現重名的成員變量,這時的訪問是有影響的。代碼以下:this
class Fu { //Fu中的成員變量 int num = 5; } class Zi extends Fu { //Zi中的成員變量 int num = 6; //Zi中的成員方法 public void show() { //訪問父類中的num System.out.println("Fu num = "+num); //訪問子類中的num System.out.println("Zi num = "+num); } } class ExtendsDemo02 { public static void main(String[] args) { //建立子類對象 Zi z = new Zi(); //調用子類中的show()方法 z.show(); } } 演示結果: Fu num = 6 Zi num = 6
子父類中出現了同名的成員變量時,在子類中須要訪問父類中非私有成員變量時,須要使用 super
關鍵字,修飾父類成員變量,相似於以前學過的 this
。編碼
使用格式:設計
super.父類成員變量名
子類方法須要修改,代碼以下:
class Zi extends Fu { //Zi中的成員變量 int num = 6; //Zi中的成員方法 public void show() { //訪問父類中的num System.out.println("Fu num = "+super.num); //訪問子類中的num System.out.println("Zi num = "+num); } } 演示結果: Fu num = 5 Zi num = 5
小貼士:Fu 類中的成員變量是非私有的,子類中能夠直接訪問。若Fu 類中的成員變量私有了,子類是不能直接訪問的。一般編碼時,咱們遵循封裝的原則,使用private修飾成員變量,那麼如何訪問父類的私有成員 變量呢?對!能夠在父類中提供公共的getXxx方法和setXxx方法。
當類之間產生了關係,其中各種中的成員方法,又產生了哪些影響呢?
若是子類父類中出現不重名的成員方法,這時的調用是沒有影響的。對象調用方法時,會先在子類中查找有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。代碼以下:
class Fu { public void show() { System.out.println("Fu類中的show方法執行"); } } class Zi extends Fu { public void show2() { System.out.println("Zi類中的show2方法執行"); } } public class ExtendsDemo04 { public static void main(String[] args) { Zi z = new Zi(); //子類中沒有show方法,可是能夠找到父類方法去執行 z.show(); z.show2(); } } 演示結果: Fu類中的show方法執行 Zi類中的show2方法執行
若是子類父類中出現重名的成員方法,這時的訪問是一種特殊狀況,叫作方法重寫 (Override)。
代碼以下:
class Fu { public void show() { System.out.println("Fu類中的show方法執行"); } } class Zi extends Fu { public void show() { System.out.println("Zi類中的show方法執行"); } } public class ExtendsDemo04 { public static void main(String[] args) { Zi z = new Zi(); z.show(); } } 演示結果: Zi類中的show方法執行
子類能夠根據須要,定義特定於本身的行爲。既沿襲了父類的功能名稱,又根據子類的須要從新實現父類方法,從而進行擴展加強。
super.父類成員方法,表示調用父類的成員方法
注意事項
當類之間產生了關係,其中各種中的構造方法,又產生了哪些影響呢?
首先咱們要回憶兩個事情,構造方法的定義格式和做用。
構造方法的做用是初始化成員變量的。因此子類的初始化過程當中,必須先執行父類的初始化動做。子類的構造方法中默認有一個 super() ,表示調用父類的構造方法,父類成員變量初始化後,才能夠給子類使用。代碼以下:
class Fu {
private int n;
Fu() {
System.out.println("Fu()");
}
}
class Zi extends Fu{
Zi() {
super(); //調用父類構造方法
System.out.println("Zi()");
}
}
public class ExtendsDemo07 {
public static void main(String args) {
Zi zi = new Zi();
}
}
輸出結果:
Fu()
Zi()
在每次建立子類對象時,先初始化父類空間,再建立其子類對象自己。目的在於子類對象中包含了其對應的父類空間,即可以包含其父類的成員,若是父類成員非private修飾,則子類能夠隨意使用父類成員。代碼體如今子類的構造方法調用時,必定先調用父類的構造方法。理解圖解以下:
訪問成員
this.成員變量 -- 本類的 super.成員變量 -- 父類的 this.成員方法名() -- 本類的 super.成員方法名() -- 父類的
訪問構造方法
this(...) -- 本類的構造方法 super(...) -- 父類的構造方法
子類的每一個構造方法中均有默認的super(),調用父類的空參構造。手動調用父類構造會覆蓋默認的super()。 super() 和 this() 都必須是在構造方法的第一行,因此不能同時出現。
Java只支持單繼承,不支持多繼承。
//一個類只能有一個父類,不能夠有多個父類。 class C extends A{} //ok class C exteds A,B... //error
Java支持多層繼承(繼承體系)。
class A{} class B extends A{} class C extends B{}
頂層父類是Object類。全部的類默認繼承Object做爲父類。
父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法聲明和方法主體,只有聲明還有意義,而方法主體則沒有存在的意義了。咱們把沒有方法主體的方法稱爲抽象方法。Java語法規定,包含抽象方法的類就是抽象類。
使用 abstract
關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值類型 方法名 (參數列表);
代碼舉例:
public abstract void run();
若是一個類包含抽象方法,那麼該類必須是抽象類。
定義格式:
abstract class 類名字 { }
代碼舉例:
public abstract class Animal { public abstract void run(); }
繼承抽象類的子類必須重寫父類全部的抽象方法。不然,該子類也必須聲明爲抽象類。最終,必須有子類實現該父類的抽象方法,不然,從最初的父類到最終的子類都不能建立對象,失去意義。
代碼舉例:
public class Cat extends Animal { public void run() { System.out.println("小貓在牆頭走"); } } public class CatTest { public static void main(String[] args) { Cat c = new Cat(); c.run(); } } 輸出結果: 小貓在牆頭走
此時的方法重寫,,是子類對父類抽象方法的完成實現,咱們將這種方法重寫的操做,也叫作實現方法。
關於抽象類的使用,如下爲語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
1.抽象類不能建立對象,若是建立,編譯沒法經過而報錯。只能建立其非抽象子類的對象。
理解:假設建立了抽象類的對象,調用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
2.抽象類中,能夠有構造方法,是供子類建立對象時,初始化父類成員使用的。
理解:子類的構造方法中,有默認的super(),須要訪問父類構造方法。
3.抽象類中,不必定包含抽象方法,可是有抽象方法的類一定是抽象類。
理解:未包含抽象方法的抽象類,目的就是不想讓調用者建立該類對象,一般用於某些特殊的類結構設計。
4.抽象類的子類,必須重寫抽象父類中全部的抽象方法,不然,編譯沒法經過而報錯。除非該g子類也是抽象類。
理解:假設不重寫全部抽象方法,則類中可能包含抽象方法。那麼建立對象後,調用抽象的方法,沒有意義。
羣主發普通紅包。某羣有多名成員,羣主給成員發普通紅包。普通紅包的規則:
請根據描述,完成案例中全部類的定義以及指定類之間的繼承關係,並完成發紅包的操做。
根據描述分析,獲得以下繼承體系
案例實現
定義用戶類:
public class User { private String username; //用戶名 private double leftMoney; //餘額 //構造方法 public User(){} public User(String username, double leftMoney) { this.username = username; this.leftMoney = leftMoney; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public double getLeftMoney() { return leftMoney; } public void setLeftMoney(double leftMoney) { this.leftMoney = leftMoney; } public void show() { System.out.println("用戶名:"+ username + ", 餘額爲:" + leftMoney + "元"); } }
定義羣主類:
public class Qunzhu extends User { public Qunzhu(){} public Qunzhu(String username, double leftMoney) { super(username, leftMoney); } /* 羣主發紅包,就是把一個浮點數的金額分紅若干份。 1.獲取羣主金額,是否夠發紅包 不夠則返回null並提示 夠則繼續 2.修改羣主餘額 3.拆分成包 3.1 若是能被整除就平均分 3.2 若是不能 那麼就把餘數分給最後一份 */ public ArrayList<Double> send(double money, int count) { double leftMoney = getLeftMoney(); if (money > leftMoney) { return null; } //修改羣主餘額 setLeftMoney(leftMoney - money); //建立一個集合,保存等份金額 ArrayList<Double> list = new ArrayList<>(); //擴大100倍,至關於折算成‘分’爲單位,避免小數運算損失精度 money = money * 100; //每份的金額 double m = money / count; //不能整除的餘數 double l = money % count; for(int i = 0;i < count - 1; i++) { list.add(m / 100.0); } if(l == 0) { //能整除,最後一份金額與以前每份金額一致 list.add(m / 100.0); } else { //不能整除,最後一份金額爲以前每份的金額+餘數金額 list.add((m + 1) / 100.0); } return list; } }
定義成員類:
public class Member extends User{ public Member() {} public Member(String username, double leftMoney) { super(username, leftMoney); } // 打開紅包,就是從集合中隨機取出一份保存到本身的餘額中 public void openHongbao(ArrayList<Double> list) { Random r = new Random(); int index = r.nextInt(list.size()); Double money = list.remove(index); setLeftMoney(money); } }
測試類:
public class Test { public static void main(String[] args) { Qunzhu qz = new Qunzhu("羣主", 200); Scanner sc = new Scanner(System.in); System.out.println("請輸入金額:"); double money = sc.nextDouble(); System.out.println("請輸入個數:"); int count = sc.nextInt(); //發送紅包 ArrayList<Double> sendList = qz.send(money,count); if(sendList == null) { System.out.println("餘額不足..."); return; } Member m1 = new Member(); Member m2 = new Member(); Member m3 = new Member(); m1.openHongbao(sendList); m2.openHongbao(sendList); m3.openHongbao(sendList); qz.show(); m1.show(); m2.show(); m3.show(); } }
接口,是Java語言中一種引用類型,是方法的集合,若是說類的內部封裝了成員變量、構造方法和成員方法,那麼接口的內部主要就是封裝了方法,包含抽象方法(JDK 7及之前),默認方法和靜態方法(JDK 8),私有方法(JDK 9)。
接口的定義,它與定義類方式類似,可是使用 interface
關鍵字。它也會被編譯成.class文件,但必定要明確它並非類,而是另一種引用數據類型。
引用數據類型:數組、類、接口。
接口的使用,它不能建立對象,可是能夠被實現( implements ,相似於被繼承)。一個實現接口的類(能夠看做是接口的子類),須要實現接口中全部的抽象方法,建立該類對象,就能夠調用方法了,不然它必須是一個抽象類。
public interface 接口名稱 { //抽象方法 //默認方法 //靜態方法 //私有方法 }
抽象方法:使用 abstract
關鍵字修飾,能夠省略,沒有方法體。該方法供子類實現使用。
代碼以下:
public interface InterFaceName { public abstract void method(); }
默認方法:使用 default
修飾,不可省略,供子類調用或者子類重寫。
靜態方法:使用 static
修飾,供接口直接調用。
代碼以下:
public interface InterFaceName { public default void method() { //執行語句 } public static void method2() { //執行語句 } }
私有方法:使用 private
修飾,供接口中的默認方法或者靜態方法調用。
代碼以下:
public interface InterFaceName { private void method() { //執行語句 } }
類與接口的關係爲實現關係,即類實現接口,該類能夠稱爲接口的實現類,也能夠稱爲接口的子類。實現的動做相似繼承,格式相仿,只是關鍵字不一樣,實現使用 implements
關鍵字。
非抽象子類實現接口:
實現格式:
class 類名 implements 接口名 { // 重寫接口中抽象方法【必須】 // 重寫接口中默認方法【可選】 }
必須所有實現,代碼以下:
定義接口:
public interface LivaAble { //定義抽象方法 public abstract void eat(); public abstract void sleep(); }
定義實現類:
public class Animal implements LiveAble { @Override public void eat() { System.out.println("吃東西"); } @Override public void sleep() { System.out.println("晚上睡"); } }
測試類:
public class InterfaceDemo { public static void main(String[] args) { //建立子類對象 Animal a = new Animal(); //調用實現後的方法 a.eat(); a.sleep(); } } 輸出結果: 吃東西 晚上睡
能夠繼承,能夠重寫,二選一,可是隻能經過實現類的對象來調用。
1.默認繼承方法,代碼以下:
定義接口:
public interface LiveAble { public default void fly() { System.out.println("天上飛"); } }
定義實現類:
public class Animal implements LiveAble { //繼承,什麼都不用謝,直接調用 }
測試類:
public class InterfaceDemo { public static void main(String[] args) { //建立子類對象 Animal a = new Animal(); //調用默認方法 a.fly(); } } 輸出結果: 天上飛
2.重寫默認方法,代碼以下:
定義接口:
public interface LiveAble { public default void fly() { System.out.println("天上飛"); } }
定義實現類:
public class Animal implements LiveAble { @Override public void fly() { System.out.println("自由自在的飛"); } }
測試類:
public class InterfaceDemo { public static void main(String[] args) { //建立子類對象 Animal a = new Animal(); //調用重寫方法 a.fly(); } } 輸出結果: 自由自在的飛
靜態與.class 文件相關,只能使用接口名調用,不能夠經過實現類的類名或者實現類的對象調用,代碼以下:
定義接口:
public interface LiveAble { public static void run() { System.out.println("地上跑"); } }
定義實現類:
public class Animale implements LiveAble { //沒法重寫靜態方法 }
測試類:
public class InterfaceDemo { public static void main(String[] args) { // Animal.run(); //【錯誤】沒法繼承方法,也沒法調用 LiveAble.run(); } } 輸出結果: 地上跑
若是一個接口中有多個默認方法,而且方法中有重複的內容,那麼能夠抽取出來,封裝到私有方法中,供默認方法去調用。從設計的角度講,私有的方法是對默認方法和靜態方法的輔助。
定義接口:
public interface LiveAble { default void func() { func1(); func2(); } private void func1(){ System.out.println("func1~~"); } private void func2(){ System.out.println("func2~~"); } }
以前學過,在繼承體系中,一個類只能繼承一個父類。而對於接口而言,一個類是能夠實現多個接口的,這叫作接口的多實現。而且,一個類能繼承一個父類,同時實現多個接口。
實現格式:
class 類名 [extends 父類名] implements 接口名1,接口名2,接口名3... { //重寫接口中的抽象方法【必須】 //重寫接口中的默認方法【不重名時可選】 }
接口中,有多個抽象方法時,實現類必須重寫全部抽象方法。若是抽象方法有重名的,只須要重寫一次。代碼以下:
定義多個接口:
interface A { public abstract void showA(); public abstract void show(); } interface B { public abstract void showB(); public abstract void show(); }
定義實現類:
public class C implements A,B{ @Override public void showA() { System.out.println("showA"); } @Override public void showB() { System.out.println("showB"); } @Override public void show() { System.out.println("show"); } }
接口中,有多個默認方法時,實現類均可繼承使用。若是默認方法有重名的,必須重寫一次。代碼以下:
定義多個接口:
interface A { public default void methodA(){} public default void method(){} } interface B { public default void methodB(){} public default void method(){} }
定義實現類:
public class C implements A,B { @Override public void method() { System.out.println("method"); } }
接口中,存在同名的靜態方法並不會衝突,緣由是隻能經過各自接口名訪問靜態方法。
當一個類,既繼承一個父類,又實現若干個接口時,父類中的成員方法與接口中的默認方法重名,子類就近選擇執行父類的成員方法。代碼以下:
定義接口:
interface A { public default void methodA(){ System.out.println("AAA"); } }
定義父類:
class B { public void methodA(){ System.out.println("BBB"); } }
定義子類:
class C extends D implements A { //未重寫methodA方法 }
測試類:
public class Test { public static void main(String[] args) { C c = new C(); c.methodA(); } } 輸出結果: BBB
一個接口能繼承另外一個或者多個接口,這和類之間的繼承比較類似。接口的繼承使用 extends 關鍵字,子接口繼承父接口的方法。若是父接口中的默認方法有重名的,那麼子接口須要重寫一次。代碼以下:
定義父接口:
interface A { public default void method(){ System.out.println("AAA"); } } interface B { public default void method(){ System.out.println("BBB"); } }
定義子接口:
interface D extends A,B { @Override public default void method() { System.out.println("DD"); } }
小貼士:
子接口重寫默認方法時,default關鍵字能夠保留。子類重寫默認方法時,default關鍵字不能夠保留。
多態是繼封裝、繼承以後,面向對象的第三大特性。
生活中,好比跑的動做,小貓、小狗和大象,跑起來是不同的。再好比飛的動做,昆蟲、鳥類和飛機,飛起來也是不同的。可見,同一行爲經過不一樣的事物,能夠體現出來的不一樣的形態。多態描述的就是這樣的狀態。
多態體現的格式:
父類類型 變量名 = new 子類對象; 變量名.方法名();
父類類型:指子類對象繼承的父類類型,或者實現的父接口類型。
代碼以下:
Fu f = new Zi(); f.method();
當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤;若是有,執行的是子類重寫後方法。
代碼以下:
定義父類:
public abstract class Animal { public abstract void eat(); }
定義子類:
public Cat extends Animal { public void eat() { System.out.println("吃魚"); } } public Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } }
測試類:
public class Test { public static void main(String[] args) { //多態形式,建立對象 Animal a1 = new Cat(); //調用的是Cat的eat a1.eat(); //多態形式,建立對象 Animal a2 = new Dog(); //調用的是Dog的eat a2.eat(); } } 輸出結果: 吃魚 吃骨頭
實際開發的過程當中,父類類型做爲方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展性與便利。代碼以下:
定義父類:
public abstract class Animal { public abstract void eat(); }
定義子類:
public Cat extends Animal { public void eat() { System.out.println("吃魚"); } } public Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } }
測試類:
public class Test_1 { public static void main(String[] args) { Cat c = new Cat(); Dog d = new Dog(); showCatEat(c); showDogEat(d); /* 以上兩個方法都可以被showAnimalEat(Animal a)方法所替代,且執行效果一致 */ showAnimalEat(c); showAnimalEat(d); } public static void showCatEat(Cat c){ c.eat(); } public static void showDogEat(Dog d){ d.eat(); } public static void showAnimalEat(Animal a){ a.eat(); } }
因爲多態特性的支持,showAnimalEat方法的Animal類型,是Cat和Dog的父類類型,父類類型接收子類對象,固然能夠把Cat對象和Dog對象,傳遞給方法。
當eat方法執行時,多態規定,執行的是子類重寫的方法,那麼效果天然與showCatEat、showDogEat方法一致,因此showAnimalEat徹底能夠替代以上兩方法。
不只僅是替代,在擴展性方面,不管以後再多的子類出現,咱們都不須要編寫showXxxEat方法了,直接使用showAnimalEat均可以完成。
因此,多態的好處,體如今,可使程序編寫的更簡單,並有良好的擴展。
多態的轉型分爲向上轉型與向下轉型兩種:
當父類引用指向一個子類對象時,即是向上轉型。
使用格式:
父類類型 變量名 = new 子類類型(); 如 Animal a = new Cat();
一個已經向上轉型的子類對象,將父類引用轉爲子類引用,可使用強制類型轉換的格式,即是向下轉型。
使用格式:
子類類型 變量名 = (子類類型) 父類變量名; 如 Cat c = (Cat) a;
當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤。也就是說,不能調用子類擁有,而父類沒有的方法。編譯都錯誤,更別說運行了。這也是多態給咱們帶來的一點"小麻煩"。因此,想要調用子類特有的方法,必須作向下轉型。
轉型演示,代碼以下:
定義類:
abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃魚"); } public void catchMouse() { Sysetm.out.println("抓老鼠"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); } public void watchHouse() { System.out.println("看家"); } }
測試類:
public class Test { public static void main(String[] args) { //向上轉型 Animal a = new Cat(); a.eat(); //調用的是Cat的eat //向下轉型 Cat c = (Cat) a; c.catchMouse(); } }
轉型的過程當中,一不當心就會遇到這樣的問題,請看以下代碼:
public class Test { public static void main(String[] args) { //向上轉型 Animal a = new Cat(); a.eat(); //向下轉型 Dog d = (Dog)a; d.watchHouse(); //調用的是Dog的watchHouse 【運行報錯】 } }
這段代碼能夠經過編譯,可是運行時,卻報出了 ClassCastException
,類型轉換異常!這是由於,明明建立了Cat類型對象,運行時,固然不能轉換成Dog對象的。這兩個類型並無任何繼承關係,不符合類型轉換的定義。
爲了不ClassCastException的發生,Java提供了 instanceof
關鍵字,給引用變量作類型的校驗,格式以下:
變量名 instanceof 數據類型 若是變量屬於該數據類型,返回true。 若是變量不屬於該數據類型,返回false。
因此,轉換前,咱們好先作一個判斷,代碼以下:
public class Test { public static void main(String[] args) { //向上轉型 Animal a = new Cat(); a.eat(); //向下轉型 if (a instanceof Cat) { Cat c = (Cat) a; c.catchMouse(); } else if (a instanceof Dog) { Dog d = (Dog)a; d.watchHouse(); } } }
筆記本電腦(laptop)一般具有使用USB設備的功能。在生產時,筆記本都預留了能夠插入USB設備的USB接口,但具體是什麼USB設備,筆記本廠商並不關心,只要符合USB規格的設備均可以。
定義USB接口,具有基本的開啓功能和關閉功能。鼠標和鍵盤要想能在電腦上使用,那麼鼠標和鍵盤也必須遵照USB規範,實現USB接口,不然鼠標和鍵盤的生產出來也沒法使用。
進行描述筆記本類,實現筆記本使用USB鼠標、USB鍵盤
定義USB接口:
public interface USB { void open(); //開啓功能 void close(); //關閉功能 }
定義鼠標類:
public class Mouse implements USB { @Override public void open() { System.out.println("鼠標開啓,紅燈亮"); } @Override public void close() { System.out.println("鼠標關閉,紅燈滅"); } public void click() { System.out.println("鼠標點擊"); } }
定義鍵盤類:
public class KeyBoard implements USB { @Override public void open() { System.out.println("鍵盤開啓,綠燈亮"); } @Override public void close() { System.out.println("鍵盤關閉,綠燈滅"); } public void type() { System.out.println("鍵盤打字"); } }
定義筆記本類:
class Laptop { public void run() { System.out.println("筆記本運行"); } //筆記本使用usb設備,當筆記本對象調用這個功能時必須給其傳遞一個符合USB規則的USB設備 public void useUSB(USB usb) { if (usb != null) { usb.open(); if (usb instanceof Mouse){ Mouse m = (Mouse) usb; m.click(); } else if (usb instanceof KeyBoard) { KeyBoard kb = (KeyBoard)usb; kb.type(); } usb.close(); } } public void shutDown() { System.out.println("筆記本關閉"); } }
測試類:
public class Test { public static void main(String[] args) { Laptop lt = new Laptop(); lt.run(); //建立鼠標實體對象 USB u = new Mouse(); //筆記本使用鼠標 lt.useUSB(u); //建立鍵盤實體對象 USB kb = new KeyBoard(); //筆記本使用鍵盤 lt.useUSB(kb); lt.shutDown(); } }
學習了繼承後,咱們知道,子類能夠在父類的基礎上改寫父類內容,好比,方法重寫。那麼咱們能不能隨意的繼承API中提供的類,改寫其內容呢?顯然這是不合適的。爲了不這種隨意改寫的狀況,Java提供了 final 關鍵字,用於修飾不可改變內容。
格式以下:
final class 類名 { }
查詢API發現像 public final class String
、 public final class Math
、 public final class Scanner
等,不少咱們學習過的類,都是被final修飾的,目的就是供咱們使用,而不讓咱們因此改變其內容。
格式以下:
修飾符 final 返回值類型 方法名(參數列表) { //方法體 }
重寫被 final
修飾的方法,編譯時就會報錯。
1.局部變量——基本類型
基本類型的局部變量,被final修飾後,只能賦值一次,不能再更改。
2.局部變量——引用類型
引用類型的局部變量,被final修飾後,只能指向一個對象,地址不能再更改。可是不影響對象內部的成員變量值的修改,代碼以下:
public class FinalDemo2 { public static void main(String[] args) { //建立User對象 final User u = new User(); //建立另外一個User對象 u = new User(); //報錯,指向了新的對象,地址值改變。 //調用setName方法 u.setName("張三"); //能夠修改 } }
3.成員變量
成員變量涉及到初始化的問題,初始化方式有兩種,只能二選一:
顯示初始化:
public class User {
final String USERNAME = "張三";
private int age;
}
構造方法初始化:
public class User {
final String USERNAME;
private int age;
public User(String username, int age) {
this.USERNAME = username;
this.age = age;
}
}
被final修飾的常量名稱,通常都有書寫規範,全部字母都大寫。
在Java中提供了四種訪問權限,使用不一樣的訪問權限修飾符修飾時,被修飾的內容會有不一樣的訪問權限。
public | protected | defalut(空的) | private | |
---|---|---|---|---|
同一類中 | √ | √ | √ | √ |
同一包中(子類與無關類) | √ | √ | √ | |
不一樣包的子類 | √ | √ | ||
不一樣包中的無關類 | √ |
可見,public具備最大權限。private則是最小權限。
編寫代碼時,若是沒有特殊的考慮,建議這樣使用權限:
private
,隱藏細節。public
,方便建立對象。public
,方便調用方法。小貼士:不加權限修飾符,其訪問能力與default修飾符相同
將一個類A定義在另外一個類B裏面,裏面的那個類A就稱爲內部類,B則稱爲外部類。
定義格式:
class 外部類 { class 內部類 { } }
在描述事物時,若一個事物內部還包含其餘事物,就可使用內部類這種結構。好比,汽車類 Car
中包含發動機類 Engine
,這時, Engine
就可使用內部類來描述,定義在成員位置。
代碼舉例:
class Car { //外部類 class Engine { //內部類 } }
建立內部類對象格式:
外部類名.內部類名 對象名 = new 外部類型().new 內部類型();
訪問演示代碼以下:
定義類:
public class Person { private boolean live = true; class Heart { public void jump() { //直接訪問外部類成員 if (live) { System.out.println("心臟在跳動"); } else { Sysetm.out.println("心臟不跳了"); } } } public boolean isLive() { return live; } public void setLive(boolean live) { this.live = live; } }
測試類:
public class InnerDemo { public static void main(String[] args) { //建立外部類對象 Person p = new Person(); //建立內部類對象 Heart heart = p.new Heart(); //調用內部類方法 heart.jump(); //調用外部類方法 p.setLive(false); //調用內部類方法 heart.jump; } } 輸出結果: 心臟在跳動 心臟不跳了
內部類仍然是一個獨立的類,在編譯以後會內部類會被編譯成獨立的.class文件,可是前面冠之外部類的類名和$符號 。
好比,Person$Heart.class
帶具體實現的
父類或者父接口的
匿名的
子類對象。咱們的目的,最終只是爲了調用方法,那麼能不能簡化一下,把以上四步合成一步呢?匿名內部類就是作這樣的快捷方式。
匿名內部類必須繼承一個父類或者實現一個父接口。
new 父類名或者接口名() { //方法重寫 @Override public void method() { //執行語句 } };
以接口爲例,匿名內部類的使用,代碼以下:
定義接口:
public abstract class FlyAble{ public abstract void fly(); }
建立匿名內部類並調用:
public class InnerDemo { public static void main(String[] args) { /* 1.等號右邊:是匿名內部類,定義並建立該接口的子類對象 2.等號左邊:是多態賦值,接口類型引用指向子類對象 */ FlyAble f = new FlyAble() { public void fly() { System.out.println("fly~"); } }; //調用fly方法,執行重寫後的方法 f.fly(); } }
一般在方法的形式參數是接口或者抽象類時,也能夠將匿名內部類做爲參數傳遞。代碼以下:
public class InnerDemo2 { public static void main(String[] args) { /* 1.等號右邊:定義並建立該接口的子類對象 2.等號左邊:是多態,接口類型引用指向子類對象 */ FlyAble f = new FlyAble() { public void fly() { System.out.println("fly~~"); } }; //將f傳遞給showFly方法中 showFly(f); } public static void showFly(FlyAble f) { f.fly(); } }
以上兩步,也能夠簡化爲一步,代碼以下:
public class InnerDemo3 { public static void main(String[] args) { /* 建立匿名內部類,直接傳遞給showFly(FlyAble f) */ showFly(new FlyAble(){ public void fly() { System.out.println("fly~~"); } }); } public static void showFly(FlyAble f) { f.fly(); } }
實際的開發中,引用類型的使用很是重要,也是很是廣泛的。咱們能夠在理解基本類型的使用方式基礎上,進一步去掌握引用類型的使用方式。基本類型能夠做爲成員變量、做爲方法的參數、做爲方法的返回值,那麼固然引用類型也是能夠的。
在定義一個類Role(遊戲角色)時,代碼以下:
class Role { int id; //角色id int blood; //生命值 String name; //角色名稱 }
使用 int
類型表示 角色id和生命值,使用 String
類型表示姓名。此時, String
自己就是引用類型,因爲使用的方式相似常量,因此每每忽略了它是引用類型的存在。若是咱們繼續豐富這個類的定義,給 Role
增長武器,穿戴裝備等屬性,咱們將如何編寫呢?
定義武器類,將增長攻擊能力:
class Weapon { String name; //武器名稱 int hurt; //傷害值 }
定義穿戴盔甲類,將增長防護能力,也就是提高生命值:
class Armour { String name; //裝備名稱 int protect; //防護值 }
定義角色類:
class Role { int id; int blood; String name; //添加武器屬性 Weapon wp; //添加盔甲屬性 Armour ar; //提供get/set方法 public Weapon getWp() { return wp; } public void setWeapon(Weapon wp) { this.wp = wp; } public Armour getArmour() { return ar; } public void setArmour(Armour ar) { this.ar = ar; } //攻擊方法 public void attack() { System.out.println("使用"+wp.getName() + ", 形成"+wp.getHurt()+"點傷害"); } //穿戴盔甲 public void wear() { //增長防護就是增長blood值 this.blood += ar.getProtect(); System.out.println("穿上"+ar.getName()+", 生命值增長"+ar.getProtect()); } }
測試類:
public class Test { public static void main(String[] args) { //建立Weapon對象 Weapon wp = new Weapon("屠龍寶刀", 99999); //建立Armour對象 Armour ar = new Armour("麒麟甲",10000); //建立Role對象 Role r = new Role(); //設置屬性 r.setWeapon(wp); r.setArmour(ar); r.attack(); r.wear(); } } 輸出結果: 使用屠龍寶刀,形成99999點傷害 穿上麒麟甲,生命值增長10000
類做爲成員變量時,對它進行賦值的操做,其實是賦給它該類的一個對象。
接口是對方法的封裝,對應遊戲當中,能夠看做是擴展遊戲角色的技能。因此,若是想擴展更強大技能,咱們在Role
中,能夠增長接口做爲成員變量,來設置不一樣的技能。
定義接口:
//法術攻擊 public interface FaShuSkill { public abstract void FaShuAttack(); }
定義角色類:
public class Role { FaShuSkill fs; public void setFaShuSkill(FaShuSkill fs) { this.fs = fs; } //法術攻擊 public void FaShuSkillAttack() { System.out.print("發動法術攻擊:"); fa.FaShuAttack(); System.out.println("攻擊完畢"); } }
測試類:
public class Test { public static void main(String[] args) { //建立遊戲角色 Role role = new Role(); //設置角色法術技能 role.setFaShuSkill(new FaShuSkill(){ @Override public void FaShuAttack() { System.out.println("縱橫天下"); } }); //發動法術攻擊 role.FaShuSkillAttack(); //更換技能 role.setFaShuSkill(new FaShuSkill() { @Override public void FaShuAttack() { System.out.println("逆轉乾坤"); } }); //發動法術攻擊 role.FaShuSkillAttack(); } } 輸出結果: 發動法術攻擊:縱橫天下 攻擊完畢 發動法術攻擊:逆轉乾坤 攻擊完畢
咱們使用一個接口,做爲成員變量,以便隨時更換技能,這樣的設計更爲靈活,加強了程序的擴展性。
接口做爲成員變量時,對它進行賦值的操做,實際上,是賦給它該接口的一個子類對象。
當接口做爲方法的參數時,須要傳遞什麼呢?當接口做爲方法的返回值類型時,須要返回什麼呢?對,其實都是它的子類對象。 ArrayList
類咱們並不陌生,查看API咱們發現,實際上,它是 java.util.List
接口的實現類。因此,當咱們看見 List
接口做爲參數或者返回值類型時,固然能夠將 ArrayList
的對象進行傳遞或返回。
請觀察以下方法:獲取某集合中全部的偶數。
定義方法:
public static List<Integer> getEvenNum(List<Integer> list){ //建立保存偶數的集合 ArrayList<Integer> evenList = new ArrayList<>(); for (int i = 0; i < list.size(); i++){ Integer integer = list.get(i); if(integer % 2 == 0) { evenList.add(integer); } } /* 返回偶數集合 由於getEvenNum方法的返回值類型是List,而ArrayList是List的子類,因此evenList能夠返回 */ return evenList; }
調用方法:
public static void main(String[] args) { //建立ArrayList集合,並添加數字 ArrayList<Integer> srcList = new ArrayList<>(); for(int i = 0; i < 10; i++) { srcList.add(i); } /* 獲取偶數集合 由於getEvenNum方法的參數是List,而ArrayList是List的子類, 因此srcList能夠傳遞 */ List list = getEvenNum(srcList); System.out.println(list); }
接口做爲參數時,傳遞它的子類對象。
接口做爲返回值類型時,返回它的子類對象。