第一章 static關鍵字
2.1 概述
以前我們定義過如下類:
public class Student {// 成員變量public String name;public char sex; // '男' '女'public int age;// 無參數構造方法public Student() {}// 有參數構造方法public Student(String a) {}
}
我們已經知道面向對象中,存在類和對象的概念,我們在類中定義了一些成員變量,例如name,age,sex ,結果發現這些成員變量,每個對象都存在(因為每個對象都可以訪問)。
而像name ,age , sex確實是每個學生對象都應該有的屬性,應該屬于每個對象。
所以Java中成員(變量和方法)等是存在所屬性的,Java是通過static關鍵字來區分的。static關鍵字在Java開發非常的重要,對于理解面向對象非常關鍵。
關于 static
關鍵字的使用,它可以用來修飾的成員變量和成員方法,被static修飾的成員是屬于類的是放在靜態區中,沒有static修飾的成員變量和方法則是屬于對象的。我們上面案例中的成員變量都是沒有static修飾的,所以屬于每個對象。
2.2 定義格式和使用
static是靜態的意思。 static可以修飾成員變量或者修飾方法。
2.2.1 靜態變量及其訪問
有static修飾成員變量,說明這個成員變量是屬于類的,這個成員變量稱為類變量或者靜態成員變量。 直接用 類名訪問即可。因為類只有一個,所以靜態成員變量在內存區域中也只存在一份。所有的對象都可以共享這個變量。
如何使用呢
例如現在我們需要定義傳智全部的學生類,那么這些學生類的對象的學校屬性應該都是“傳智”,這個時候我們可以把這個屬性定義成static修飾的靜態成員變量。
定義格式
修飾符 static 數據類型 變量名 = 初始值;
public class Student {public static String schoolName = "傳智播客"; // 屬于類,只有一份。// .....
}
靜態成員變量的訪問:
格式:類名.靜態變量
public static void main(String[] args){System.out.println(Student.schoolName); // 傳智播客Student.schoolName = "黑馬程序員";System.out.println(Student.schoolName); // 黑馬程序員
}
2.2.2 實例變量及其訪問
無static修飾的成員變量屬于每個對象的, 這個成員變量叫實例變量,之前我們寫成員變量就是實例成員變量。
需要注意的是:實例成員變量屬于每個對象,必須創建類的對象才可以訪問。
格式:對象.實例成員變量
2.2.3 靜態方法及其訪問
有static修飾成員方法,說明這個成員方法是屬于類的,這個成員方法稱為類方法或者靜態方法**。 直接用 類名訪問即可。因為類只有一個,所以靜態方法在內存區域中也只存在一份。所有的對象都可以共享這個方法。
與靜態成員變量一樣,靜態方法也是直接通過類名.方法名稱即可訪問。
舉例
public class Student{public static String schoolName = "傳智播客"; // 屬于類,只有一份。// .....public static void study(){System.out.println("我們都在黑馬程序員學習"); }
}
靜態成員變量的訪問:
格式:類名.靜態方法
public static void main(String[] args){Student.study();
}
2.2.4 實例方法及其訪問
無static修飾的成員方法屬于每個對象的,這個成員方法也叫做實例方法。
需要注意的是:實例方法是屬于每個對象,必須創建類的對象才可以訪問。
格式:對象.實例方法
示例:
public class Student {// 實例變量private String name ;// 2.方法:行為// 無 static修飾,實例方法。屬于每個對象,必須創建對象調用public void run(){System.out.println("學生可以跑步");}// 無 static修飾,實例方法public void sleep(){System.out.println("學生睡覺");}public static void study(){}
}
public static void main(String[] args){// 創建對象 Student stu = new Student ;stu.name = "徐干";// Student.sleep();// 報錯,必須用對象訪問。stu.sleep();stu.run();
}
2.3 小結
1.當 static
修飾成員變量或者成員方法時,該變量稱為靜態變量,該方法稱為靜態方法。該類的每個對象都共享同一個類的靜態變量和靜態方法。任何對象都可以更改該靜態變量的值或者訪問靜態方法。但是不推薦這種方式去訪問。因為靜態變量或者靜態方法直接通過類名訪問即可,完全沒有必要用對象去訪問。
2.無static修飾的成員變量或者成員方法,稱為實例變量,實例方法,實例變量和實例方法必須創建類的對象,然后通過對象來訪問。
3.static修飾的成員屬于類,會存儲在靜態區,是隨著類的加載而加載的,且只加載一次,所以只有一份,節省內存。存儲于一塊固定的內存區域(靜態區),所以,可以直接被類名調用。它優先于對象存在,所以,可以被所有對象共享。
4.無static修飾的成員,是屬于對象,對象有多少個,他們就會出現多少份。所以必須由對象調用。
第三章 繼承
3.1 概述
3.1.1 引入
假如我們要定義如下類:
學生類,老師類和工人類,分析如下。
- 學生類
屬性:姓名,年齡
行為:吃飯,睡覺 - 老師類
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,教書 - 班主任
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,管理
如果我們定義了這三個類去開發一個系統,那么這三個類中就存在大量重復的信息(屬性:姓名,年齡。行為:吃飯,睡覺)。這樣就導致了相同代碼大量重復,代碼顯得很臃腫和冗余,那么如何解決呢?
假如多個類中存在相同屬性和行為時,我們可以將這些內容抽取到單獨一個類中,那么多個類無需再定義這些屬性和行為,只要繼承那一個類即可。如圖所示:
其中,多個類可以稱為子類,單獨被繼承的那一個類稱為父類、超類(superclass)或者基類。
3.1.2 繼承的含義
繼承描述的是事物之間的所屬關系,這種關系是:is-a
的關系。例如,兔子屬于食草動物,食草動物屬于動物。可見,父類更通用,子類更具體。我們通過繼承,可以使多種事物之間形成一種關系體系。
繼承:就是子類繼承父類的屬性和行為,使得子類對象可以直接具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。
3.1.3 繼承的好處
- 提高代碼的復用性(減少代碼冗余,相同代碼重復利用)。
- 使類與類之間產生了關系。
3.2 繼承的格式
通過 extends
關鍵字,可以聲明一個子類繼承另外一個父類,定義格式如下:
class 父類 {...
}class 子類 extends 父類 {...
}
需要注意:Java是單繼承的,一個類只能繼承一個直接父類,跟現實世界很像,但是Java中的子類是更加強大的。
3.3 繼承案例
3.3.1 案例
請使用繼承定義以下類:
- 學生類
屬性:姓名,年齡
行為:吃飯,睡覺 - 老師類
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,教書 - 班主任
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,管理
3.3.2案例圖解分析
老師類,學生類,還有班主任類,實際上都是屬于人類的,我們可以定義一個人類,把他們相同的屬性和行為都定義在人類中,然后繼承人類即可,子類特有的屬性和行為就定義在子類中了。
如下圖所示。
3.3.3 案例代碼實現
1.父類Human類
public class Human {// 合理隱藏private String name ;private int age ;// 合理暴露public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
** 2.子類Teacher類**
public class Teacher extends Human { // 工資 private double salary ; // 特有方法 public void teach(){ System.out.println("老師在認真教技術!"); }? public double getSalary() { return salary; }? public void setSalary(double salary) { this.salary = salary; }}
3.子類Student類
public class Student extends Human{}
4.子類BanZhuren類
public class Teacher extends Human {// 工資private double salary ;// 特有方法public void admin(){System.out.println("班主任強調紀律問題!");}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}
}
5.測試類
public class Test {public static void main(String[] args) {Teacher dlei = new Teacher();dlei.setName("播仔");dlei.setAge("31");dlei.setSalary(1000.99);System.out.println(dlei.getName());System.out.println(dlei.getAge());System.out.println(dlei.getSalary());dlei.teach();BanZhuRen linTao = new BanZhuRen();linTao.setName("靈濤");linTao.setAge("28");linTao.setSalary(1000.99);System.out.println(linTao.getName());System.out.println(linTao.getAge());System.out.println(linTao.getSalary());linTao.admin();Student xugan = new Student();xugan.setName("播仔");xugan.setAge("31");//xugan.setSalary(1000.99); // xugan沒有薪水屬性,報錯!System.out.println(xugan.getName());System.out.println(xugan.getAge());}}
3.4 子類不能繼承的內容
3.4.1 引入
并不是父類的所有內容都可以給子類繼承的:
子類不能繼承父類的構造方法。
值得注意的是子類可以繼承父類的私有成員(成員變量,方法),只是子類無法直接訪問而已,可以通過getter/setter方法訪問父類的private成員變量。
3.4.1 演示代碼
public class Demo03 {public static void main(String[] args) {Zi z = new Zi();System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子類無法使用// 通過getter/setter方法訪問父類的private成員變量System.out.println(z.getNum2());z.show1();// z.show2(); // 私有的子類無法使用}
}class Fu {public int num1 = 10;private int num2 = 20;public void show1() {System.out.println("show1");}private void show2() {System.out.println("show2");}public int getNum2() {return num2;}public void setNum2(int num2) {this.num2 = num2;}
}class Zi extends Fu {
}
3.5 繼承后的特點—成員變量
當類之間產生了繼承關系后,其中各類中的成員變量,又產生了哪些影響呢?
3.5.1 成員變量不重名
如果子類父類中出現不重名的成員變量,這時的訪問是沒有影響的。代碼如下:
class Fu {// Fu中的成員變量int num = 5;
}
class Zi extends Fu {// Zi中的成員變量int num2 = 6;// Zi中的成員方法public void show() {// 訪問父類中的numSystem.out.println("Fu num="+num); // 繼承而來,所以直接訪問。// 訪問子類中的num2System.out.println("Zi num2="+num2);}
}
class Demo04 {public static void main(String[] args) {// 創建子類對象Zi z = new Zi(); // 調用子類中的show方法z.show(); }
}演示結果:
Fu num = 5
Zi num2 = 6
3.5.2 成員變量重名
如果子類父類中出現重名的成員變量,這時的訪問是有影響的。代碼如下:
class Fu1 {// Fu中的成員變量。int num = 5;
}
class Zi1 extends Fu1 {// Zi中的成員變量int num = 6;public void show() {// 訪問父類中的numSystem.out.println("Fu num=" + num);// 訪問子類中的numSystem.out.println("Zi num=" + num);}
}
class Demo04 {public static void main(String[] args) {// 創建子類對象Zi1 z = new Zi1(); // 調用子類中的show方法z1.show(); }
}
演示結果:
Fu num = 6
Zi num = 6
子父類中出現了同名的成員變量時,子類會優先訪問自己對象中的成員變量。如果此時想訪問父類成員變量如何解決呢?我們可以使用super關鍵字。
3.5.3 super訪問父類成員變量
子父類中出現了同名的成員變量時,在子類中需要訪問父類中非私有成員變量時,需要使用super
關鍵字,修飾父類成員變量,類似于之前學過的 this
。
需要注意的是:super代表的是父類對象的引用,this代表的是當前對象的引用。
使用格式:
super.父類成員變量名
子類方法需要修改,代碼如下:
class Fu {// Fu中的成員變量。int num = 5;
}class Zi extends Fu {// Zi中的成員變量int num = 6;public void show() {int num = 1;// 訪問方法中的numSystem.out.println("method num=" + num);// 訪問子類中的numSystem.out.println("Zi num=" + this.num);// 訪問父類中的numSystem.out.println("Fu num=" + super.num);}
}class Demo04 {public static void main(String[] args) {// 創建子類對象Zi1 z = new Zi1(); // 調用子類中的show方法z1.show(); }
}演示結果:
method num=1
Zi num=6
Fu num=5
3.6 繼承后的特點—成員方法
3.6.1 成員方法不重名
如果子類父類中出現不重名的成員方法,這時的調用是沒有影響的。對象調用方法時,會先在子類中查找有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。代碼如下:
class Fu {public void show() {System.out.println("Fu類中的show方法執行");}
}
class Zi extends Fu {public void show2() {System.out.println("Zi類中的show2方法執行");}
}
public class Demo05 {public static void main(String[] args) {Zi z = new Zi();//子類中沒有show方法,但是可以找到父類方法去執行z.show(); z.show2();}
}
3.6.2 成員方法重名
如果子類父類中出現重名的成員方法,則創建子類對象調用該方法的時候,子類對象會優先調用自己的方法。
代碼如下:
class Fu {public void show() {System.out.println("Fu show");}
}
class Zi extends Fu {//子類重寫了父類的show方法public void show() {System.out.println("Zi show");}
}
public class ExtendsDemo05{public static void main(String[] args) {Zi z = new Zi();// 子類中有show方法,只執行重寫后的show方法z.show(); // Zi show}
}
3.7 方法重寫
3.7.1 概念
方法重寫 :子類中出現與父類一模一樣的方法時(返回值類型,方法名和參數列表都相同),會出現覆蓋效果,也稱為重寫或者復寫。聲明不變,重新實現。
3.7.2 使用場景與案例
發生在子父類之間的關系。
子類繼承了父類的方法,但是子類覺得父類的這方法不足以滿足自己的需求,子類重新寫了一個與父類同名的方法,以便覆蓋父類的該方 法。
例如:我們定義了一個動物類代碼如下:
public class Animal {public void run(){System.out.println("動物跑的很快!");}public void cry(){System.out.println("動物都可以叫~~~");}
}
然后定義一個貓類,貓可能認為父類cry()方法不能滿足自己的需求
代碼如下:
public class Cat extends Animal {public void cry(){System.out.println("我們一起學貓叫,喵喵喵!喵的非常好聽!");}
}public class Test {public static void main(String[] args) {// 創建子類對象Cat ddm = new Cat();// 調用父類繼承而來的方法ddm.run();// 調用子類重寫的方法ddm.cry();}
}
3.7.2 @Override重寫注解
- @Override:注解,重寫注解校驗!
- 這個注解標記的方法,就說明這個方法必須是重寫父類的方法,否則編譯階段報錯。
- 建議重寫都加上這個注解,一方面可以提高代碼的可讀性,一方面可以防止重寫出錯!
加上后的子類代碼形式如下:
public class Cat extends Animal {// 聲明不變,重新實現// 方法名稱與父類全部一樣,只是方法體中的功能重寫寫了!@Overridepublic void cry(){System.out.println("我們一起學貓叫,喵喵喵!喵的非常好聽!");}
}
3.7.3 注意事項
- 方法重寫是發生在子父類之間的關系。
- 子類方法覆蓋父類方法,必須要保證權限大于等于父類權限。
- 子類方法覆蓋父類方法,返回值類型、函數名和參數列表都要一模一樣。
3.8 繼承后的特點—構造方法
3.8.1 引入
當類之間產生了關系,其中各類中的構造方法,又產生了哪些影響呢?
首先我們要回憶兩個事情,構造方法的定義格式和作用。
- 構造方法的名字是與類名一致的。所以子類是無法繼承父類構造方法的。
- 構造方法的作用是初始化對象成員變量數據的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中默認有一個
super()
,表示調用父類的構造方法,父類成員變量初始化后,才可以給子類使用。(先有爸爸,才能有兒子)
繼承后子類構方法器特點:子類所有構造方法的第一行都會默認先調用父類的無參構造方法
3.8.2 案例演示
按如下需求定義類:
- 人類
成員變量: 姓名,年齡
成員方法: 吃飯 - 學生類
成員變量: 姓名,年齡,成績
成員方法: 吃飯
代碼如下:
class Person {private String name;private int age;public Person() {System.out.println("父類無參");}// getter/setter省略
}class Student extends Person {private double score;public Student() {//super(); // 調用父類無參,默認就存在,可以不寫,必須再第一行System.out.println("子類無參");}public Student(double score) {//super(); // 調用父類無參,默認就存在,可以不寫,必須再第一行this.score = score; System.out.println("子類有參");}}public class Demo07 {public static void main(String[] args) {Student s1 = new Student();System.out.println("----------");Student s2 = new Student(99.9);}
}輸出結果:
父類無參
子類無參
----------
父類無參
子類有參
3.8.3 小結
- 子類構造方法執行的時候,都會在第一行默認先調用父類無參數構造方法一次。
- 子類構造方法的第一行都隱含了一個**super()**去調用父類無參數構造方法,**super()**可以省略不寫。
3.9 super(…)和this(…)
class Person {private String name;private int age;public Person() {System.out.println("父類無參");}// getter/setter省略
}class Student extends Person {private double score;public Student() {//super(); // 調用父類無參構造方法,默認就存在,可以不寫,必須再第一行System.out.println("子類無參");}public Student(double score) {//super(); // 調用父類無參構造方法,默認就存在,可以不寫,必須再第一行this.score = score; System.out.println("子類有參");}// getter/setter省略
}public class Demo07 {public static void main(String[] args) {// 調用子類有參數構造方法Student s2 = new Student(99.9);System.out.println(s2.getScore()); // 99.9System.out.println(s2.getName()); // 輸出 nullSystem.out.println(s2.getAge()); // 輸出 0}
}
我們發現,子類有參數構造方法只是初始化了自己對象中的成員變量score,而父類中的成員變量name和age依然是沒有數據的,怎么解決這個問題呢,我們可以借助與super(…)去調用父類構造方法,以便初始化繼承自父類對象的name和age.
3.9.2 super和this的用法格式+
super和this完整的用法如下,其中this,super訪問成員我們已經接觸過了。
this.成員變量 – 本類的
super.成員變量 – 父類的
this.成員方法名() – 本類的
super.成員方法名() – 父類的
接下來我們使用調用構造方法格式:
super(…) – 調用父類的構造方法,根據參數匹配確認
this(…) – 調用本類的其他構造方法,根據參數匹配確認
3.9.3 super(…)用法演示
class Person {private String name ="鳳姐";private int age = 20;public Person() {System.out.println("父類無參");}public Person(String name , int age){this.name = name ;this.age = age ;}// getter/setter省略
}class Student extends Person {private double score = 100;public Student() {//super(); // 調用父類無參構造方法,默認就存在,可以不寫,必須再第一行System.out.println("子類無參");}public Student(String name , int age,double score) {super(name ,age);// 調用父類有參構造方法Person(String name , int age)初始化name和agethis.score = score; System.out.println("子類有參");}// getter/setter省略
}public class Demo07 {public static void main(String[] args) {// 調用子類有參數構造方法Student s2 = new Student("張三",20,99);System.out.println(s2.getScore()); // 99System.out.println(s2.getName()); // 輸出 張三System.out.println(s2.getAge()); // 輸出 20}
}
注意:
子類的每個構造方法中均有默認的super(),調用父類的空參構造。手動調用父類構造會覆蓋默認的super()。
super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
super(…)是根據參數去確定調用父類哪個構造方法的。
3.9.5 this(…)用法演示
this(…)
- 默認是去找本類中的其他構造方法,根據參數來確定具體調用哪一個構造方法。
- 為了借用其他構造方法的功能。
package com.itheima._08this和super調用構造方法;
/*** this(...):* 默認是去找本類中的其他構造方法,根據參數來確定具體調用哪一個構造方法。* 為了借用其他構造方法的功能。**/
public class ThisDemo01 {public static void main(String[] args) {Student xuGan = new Student();System.out.println(xuGan.getName()); // 輸出:徐干System.out.println(xuGan.getAge());// 輸出:21System.out.println(xuGan.getSex());// 輸出: 男}
}class Student{private String name ;private int age ;private char sex ;public Student() {// 很弱,我的兄弟很牛逼啊,我可以調用其他構造方法:Student(String name, int age, char sex)this("徐干",21,'男');}public Student(String name, int age, char sex) {this.name = name ;this.age = age ;this.sex = sex ;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public char getSex() {return sex;}public void setSex(char sex) {this.sex = sex;}
}
3.9.6 小結
- 子類的每個構造方法中均有默認的super(),調用父類的空參構造。手動調用父類構造會覆蓋默認的super()。
- super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
- super(…)和this(…)是根據參數去確定調用父類哪個構造方法的。
- super(…)可以調用父類構造方法初始化繼承自父類的成員變量的數據。
- this(…)可以調用本類中的其他構造方法。
3.10 繼承的特點
- Java只支持單繼承,不支持多繼承。
// 一個類只能有一個父類,不可以有多個父類。
class A {}
class B {}
class C1 extends A {} // ok
// class C2 extends A, B {} // error
- 一個類可以有多個子類
// A可以有多個子類
class A {}
class C1 extends A {}
class C2 extends A {}
- 可以多層繼承
class A {}
class C1 extends A {}
class D extends C1 {}
頂層父類是Object類。所有的類默認繼承Object,作為父類。
第一章 多態
1.1 多態的形式
多態是繼封裝、繼承之后,面向對象的第三大特性。
多態是出現在繼承或者實現關系中的。
多態體現的格式:
父類類型 變量名 = new 子類/實現類構造器;
變量名.方法名();
多態的前提:有繼承關系,子類對象是可以賦值給父類類型的變量。例如Animal是一個動物類型,而Cat是一個貓類型。Cat繼承了Animal,Cat對象也是Animal類型,自然可以賦值給父類類型的變量。
1.2 多態的使用場景
如果沒有多態,在下圖中register方法只能傳遞學生對象,其他的Teacher和administrator對象是無法傳遞給register方法方法的,在這種情況下,只能定義三個不同的register方法分別接收學生,老師和管理員。
有了多態之后,方法的形參就可以定義為共同的父類Person。
要注意的是:
- 當一個方法的形參是一個類,我們可以傳遞這個類所有的子類對象。
- 當一個方法的形參是一個接口,我們可以傳遞這個接口所有的實現類對象(后面會學)。
- 而且多態還可以根據傳遞的不同對象來調用不同類中的方法。
父類:
public class Person {private String name;private int age;// 空參構造//帶全部參數的構造//get和set方法//此處省略public void show(){System.out.println(name + ", " + age);}
}子類1:
public class Administrator extends Person {@Overridepublic void show() {System.out.println("管理員的信息為:" + getName() + ", " + getAge());}
}子類2:
public class Student extends Person{@Overridepublic void show() {System.out.println("學生的信息為:" + getName() + ", " + getAge());}
}子類3:
public class Teacher extends Person{@Overridepublic void show() {System.out.println("老師的信息為:" + getName() + ", " + getAge());}
}測試類:
public class Test {public static void main(String[] args) {//創建三個對象,并調用register方法Student s = new Student();s.setName("張三");s.setAge(18);Teacher t = new Teacher();t.setName("王建國");t.setAge(30);Administrator admin = new Administrator();admin.setName("管理員");admin.setAge(35);register(s);register(t);register(admin);}//這個方法既能接收老師,又能接收學生,還能接收管理員//只能把參數寫成這三個類型的父類public static void register(Person p){p.show();}
}
1.3 多態的定義和前提
多態: 是指同一行為,具有多個不同表現形式。
從上面案例可以看出,Cat和Dog都是動物,都是吃這一行為,但是出現的效果(表現形式)是不一樣的。
前提【重點】
-
有繼承或者實現關系
-
方法的重寫【意義體現:不重寫,無意義】
-
父類引用指向子類對象【格式體現】
1.4 多態的運行特點
調用成員變量時:編譯看左邊,運行看左邊
調用成員方法時:編譯看左邊,運行看右邊
代碼示例:
Fu f = new Zi();
//編譯看左邊的父類中有沒有name這個屬性,沒有就報錯
//在實際運行的時候,把父類name屬性的值打印出來
System.out.println(f.name);
//編譯看左邊的父類中有沒有show這個方法,沒有就報錯
//在實際運行的時候,運行的是子類中的show方法
f.show();
1.5 多態的弊端
我們已經知道多態編譯階段是看左邊父類類型的,如果子類有些獨有的功能,此時多態的寫法就無法訪問子類獨有功能了。
class Animal{public void eat(){System.out.println("動物吃東西!")}
}
class Cat extends Animal { public void eat() { System.out.println("吃魚"); } public void catchMouse() { System.out.println("抓老鼠"); }
} class Dog extends Animal { public void eat() { System.out.println("吃骨頭"); }
}class Test{public static void main(String[] args){Animal a = new Cat();a.eat();a.catchMouse();//編譯報錯,編譯看左邊,Animal沒有這個方法}
}
1.6 引用類型轉換
1.6.1 為什么要轉型
多態的寫法就無法訪問子類獨有功能了。
當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能調用子類擁有,而父類沒有的方法。編譯都錯誤,更別說運行了。這也是多態給我們帶來的一點"小麻煩"。所以,想要調用子類特有的方法,必須做向下轉型。
回顧基本數據類型轉換
- 自動轉換: 范圍小的賦值給范圍大的.自動完成:double d = 5;
- 強制轉換: 范圍大的賦值給范圍小的,強制轉換:int i = (int)3.14
? 多態的轉型分為向上轉型(自動轉換)與向下轉型(強制轉換)兩種。
1.6.2 向上轉型(自動轉換)
- 向上轉型:多態本身是子類類型向父類類型向上轉換(自動轉換)的過程,這個過程是默認的。
當父類引用指向一個子類對象時,便是向上轉型。
使用格式:
父類類型 變量名 = new 子類類型();
如:Animal a = new Cat();
原因是:父類類型相對與子類來說是大范圍的類型,Animal是動物類,是父類類型。Cat是貓類,是子類類型。Animal類型的范圍當然很大,包含一切動物。 所以子類范圍小可以直接自動轉型給父類類型的變量。
1.6.3 向下轉型(強制轉換)
- 向下轉型:父類類型向子類類型向下轉換的過程,這個過程是強制的。
一個已經向上轉型的子類對象,將父類引用轉為子類引用,可以使用強制類型轉換的格式,便是向下轉型。
使用格式:
子類類型 變量名 = (子類類型) 父類變量名;
如:Aniaml a = new Cat();Cat c =(Cat) a;
1.6.4 案例演示
想要調用子類特有的方法,必須做向下轉型。
轉型演示,代碼如下:
定義類:
abstract class Animal { abstract void eat();
} class Cat extends Animal { public void eat() { System.out.println("吃魚"); } public void catchMouse() { System.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(); // 調用的是 Cat 的 catchMouse}
}
1.6.5 轉型的異常
轉型的過程中,一不小心就會遇到這樣的問題,請看如下代碼:
public class Test {public static void main(String[] args) {// 向上轉型 Animal a = new Cat(); a.eat(); // 調用的是 Cat 的 eat// 向下轉型 Dog d = (Dog)a; d.watchHouse(); // 調用的是 Dog 的 watchHouse 【運行報錯】}
}
這段代碼可以通過編譯,但是運行時,卻報出了 ClassCastException
,類型轉換異常!這是因為,明明創建了Cat類型對象,運行時,當然不能轉換成Dog對象的。
1.6.6 instanceof關鍵字
為了避免ClassCastException的發生,Java提供了 instanceof
關鍵字,給引用變量做類型的校驗,格式如下:
變量名 instanceof 數據類型
如果變量屬于該數據類型或者其子類類型,返回true。
如果變量不屬于該數據類型或者其子類類型,返回false。
所有在轉換前,我們最好先做個判斷,代碼如下:
public class Test {public static void main(String[] args) {// 向上轉型 Animal a = new Cat(); a.eat(); // 調用的是 Cat 的 eat// 向下轉型 if (a instanceof Cat){Cat c = (Cat)a; c.catchMouse(); // 調用的是 Cat 的 catchMouse} else if (a instanceof Dog){Dog d = (Dog)a; d.watchHouse(); // 調用的是 Dog 的 watchHouse}}
}
1.6.7 instanceof新特性
JDK14的時候提出了新特性,把判斷和強轉合并成了一行
//新特性
//先判斷a是否為Dog類型,如果是,則強轉成Dog類型,轉換之后變量名為d
//如果不是,則不強轉,結果直接是false
if(a instanceof Dog d){d.lookHome();
}else if(a instanceof Cat c){c.catchMouse();
}else{System.out.println("沒有這個類型,無法轉換");
}
1.7綜合練習
需求:根據需求完成代碼:
1.定義狗類
屬性:
年齡,顏色
行為:
eat(String something)(something表示吃的東西)
看家lookHome方法(無參數)
2.定義貓類
屬性:
年齡,顏色
行為:
eat(String something)方法(something表示吃的東西)
逮老鼠catchMouse方法(無參數)
3.定義Person類//飼養員
屬性:
姓名,年齡
行為:
keepPet(Dog dog,String something)方法
功能:喂養寵物狗,something表示喂養的東西
行為:
keepPet(Cat cat,String something)方法
功能:喂養寵物貓,something表示喂養的東西
生成空參有參構造,set和get方法
4.定義測試類(完成以下打印效果):
keepPet(Dog dog,String somethind)方法打印內容如下:
年齡為30歲的老王養了一只黑顏色的2歲的狗
2歲的黑顏色的狗兩只前腿死死的抱住骨頭猛吃
keepPet(Cat cat,String somethind)方法打印內容如下:
年齡為25歲的老李養了一只灰顏色的3歲的貓
3歲的灰顏色的貓瞇著眼睛側著頭吃魚
5.思考:
1.Dog和Cat都是Animal的子類,以上案例中針對不同的動物,定義了不同的keepPet方法,過于繁瑣,能否簡化,并體會簡化后的好處?
2.Dog和Cat雖然都是Animal的子類,但是都有其特有方法,能否想辦法在keepPet中調用特有方法?
畫圖分析:
代碼示例
//動物類(父類)
public class Animal {private int age;private String color;public Animal() {}public Animal(int age, String color) {this.age = age;this.color = color;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public void eat(String something){System.out.println("動物在吃" + something);}
}//貓類(子類)
public class Cat extends Animal {public Cat() {}public Cat(int age, String color) {super(age, color);}@Overridepublic void eat(String something) {System.out.println(getAge() + "歲的" + getColor() + "顏色的貓瞇著眼睛側著頭吃" + something);}public void catchMouse(){System.out.println("貓抓老鼠");}}//狗類(子類)
public class Dog extends Animal {public Dog() {}public Dog(int age, String color) {super(age, color);}//行為//eat(String something)(something表示吃的東西)//看家lookHome方法(無參數)@Overridepublic void eat(String something) {System.out.println(getAge() + "歲的" + getColor() + "顏色的狗兩只前腿死死的抱住" + something + "猛吃");}public void lookHome(){System.out.println("狗在看家");}
}//飼養員類
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//飼養狗/* public void keepPet(Dog dog, String something) {System.out.println("年齡為" + age + "歲的" + name + "養了一只" + dog.getColor() + "顏色的" + dog.getAge() + "歲的狗");dog.eat(something);}//飼養貓public void keepPet(Cat cat, String something) {System.out.println("年齡為" + age + "歲的" + name + "養了一只" + cat.getColor() + "顏色的" + cat.getAge() + "歲的貓");cat.eat(something);}*///想要一個方法,能接收所有的動物,包括貓,包括狗//方法的形參:可以寫這些類的父類 Animalpublic void keepPet(Animal a, String something) {if(a instanceof Dog d){System.out.println("年齡為" + age + "歲的" + name + "養了一只" + a.getColor() + "顏色的" + a.getAge() + "歲的狗");d.eat(something);}else if(a instanceof Cat c){System.out.println("年齡為" + age + "歲的" + name + "養了一只" + c.getColor() + "顏色的" + c.getAge() + "歲的貓");c.eat(something);}else{System.out.println("沒有這種動物");}}
}//測試類
public class Test {public static void main(String[] args) {//創建對象并調用方法/* Person p1 = new Person("老王",30);Dog d = new Dog(2,"黑");p1.keepPet(d,"骨頭");Person p2 = new Person("老李",25);Cat c = new Cat(3,"灰");p2.keepPet(c,"魚");*///創建飼養員的對象Person p = new Person("老王",30);Dog d = new Dog(2,"黑");Cat c = new Cat(3,"灰");p.keepPet(d,"骨頭");p.keepPet(c,"魚");}
}
第二章 包
2.1 包
? 包在操作系統中其實就是一個文件夾。包是用來分門別類的管理技術,不同的技術類放在不同的包下,方便管理和維護。
在IDEA項目中,建包的操作如下:
包名的命名規范:
路徑名.路徑名.xxx.xxx
// 例如:com.itheima.oa
- 包名一般是公司域名的倒寫。例如:黑馬是www.itheima.com,包名就可以定義成com.itheima.技術名稱。
- 包名必須用”.“連接。
- 包名的每個路徑名必須是一個合法的標識符,而且不能是Java的關鍵字。
2.2 導包
什么時候需要導包?
? 情況一:在使用Java中提供的非核心包中的類時
? 情況二:使用自己寫的其他包中的類時
什么時候不需要導包?
? 情況一:在使用Java核心包(java.lang)中的類時
? 情況二:在使用自己寫的同一個包中的類時
2.3 使用不同包下的相同類怎么辦?
假設demo1和demo2中都有一個Student該如何使用?
代碼示例:
//使用全類名的形式即可。
//全類名:包名 + 類名
//拷貝全類名的快捷鍵:選中類名crtl + shift + alt + c 或者用鼠標點copy,再點擊copy Reference
com.itheima.homework.demo1.Student s1 = new com.itheima.homework.demo1.Student();
com.itheima.homework.demo2.Student s2 = new com.itheima.homework.demo2.Student();
第三章 權限修飾符
3.1 權限修飾符
? 在Java中提供了四種訪問權限,使用不同的訪問權限修飾符修飾時,被修飾的內容會有不同的訪問權限,我們之前已經學習過了public 和 private,接下來我們研究一下protected和默認修飾符的作用。
-
public:公共的,所有地方都可以訪問。
-
protected:本類 ,本包,其他包中的子類都可以訪問。
-
默認(沒有修飾符):本類 ,本包可以訪問。
注意:默認是空著不寫,不是default
-
private:私有的,當前類可以訪問。
public > protected > 默認 > private
3.2 不同權限的訪問能力
public | protected | 默認 | private | |
---|---|---|---|---|
同一類中 | √ | √ | √ | √ |
同一包中的類 | √ | √ | √ | |
不同包的子類 | √ | √ | ||
不同包中的無關類 | √ |
可見,public具有最大權限。private則是最小權限。
編寫代碼時,如果沒有特殊的考慮,建議這樣使用權限:
- 成員變量使用
private
,隱藏細節。 - 構造方法使用
public
,方便創建對象。 - 成員方法使用
public
,方便調用方法。
小貼士:不加權限修飾符,就是默認權限
第四章 final關鍵字
4.1 概述
? 學習了繼承后,我們知道,子類可以在父類的基礎上改寫父類內容,比如,方法重寫。
如果有一個方法我不想別人去改寫里面內容,該怎么辦呢?
Java提供了final
關鍵字,表示修飾的內容不可變。
- final: 不可改變,最終的含義。可以用于修飾類、方法和變量。
- 類:被修飾的類,不能被繼承。
- 方法:被修飾的方法,不能被重寫。
- 變量:被修飾的變量,有且僅能被賦值一次。
4.2 使用方式
4.2.1 修飾類
final修飾的類,不能被繼承。
格式如下:
final class 類名 {
}
代碼:
final class Fu {
}
// class Zi extends Fu {} // 報錯,不能繼承final的類
查詢API發現像 public final class String
、public final class Math
、public final class Scanner
等,很多我們學習過的類,都是被final修飾的,目的就是供我們使用,而不讓我們所以改變其內容。
4.2.2 修飾方法
final修飾的方法,不能被重寫。
格式如下:
修飾符 final 返回值類型 方法名(參數列表){//方法體
}
class Fu2 {final public void show1() {System.out.println("Fu2 show1");}public void show2() {System.out.println("Fu2 show2");}
}class Zi2 extends Fu2 {
// @Override
// public void show1() {
// System.out.println("Zi2 show1");
// }@Overridepublic void show2() {System.out.println("Zi2 show2");}
}
4.2.3 修飾變量-局部變量
- 局部變量——基本類型
基本類型的局部變量,被final修飾后,只能賦值一次,不能再更改。代碼如下:
public class FinalDemo1 {public static void main(String[] args) {// 聲明變量,使用final修飾final int a;// 第一次賦值 a = 10;// 第二次賦值a = 20; // 報錯,不可重新賦值// 聲明變量,直接賦值,使用final修飾final int b = 10;// 第二次賦值b = 20; // 報錯,不可重新賦值}
}
4.2.4 修飾變量-成員變量
成員變量涉及到初始化的問題,初始化方式有顯示初始化和構造方法初始化,只能選擇其中一個:
- 顯示初始化(在定義成員變量的時候立馬賦值)(常用);
public class Student {final int num = 10;
}
構造方法初始化(在構造方法中賦值一次)(不常用,了解即可)。
注意:每個構造方法中都要賦值一次!
public class Student {final int num = 10;final int num2 = 20;public Student() {this.num2 = 20;
// this.num2 = 20;}public Student(String name) {this.num2 = 20;
// this.num2 = 20;}
}
被final修飾的常量名稱,一般都有書寫規范,所有字母都大寫
第一章、抽象類
1.1 概述
1.1.1 抽象類引入
? 父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那么父類的方法聲明和方法主體,只有聲明還有意義,而方法主體則沒有存在的意義了(因為子類對象會調用自己重寫的方法)。換句話說,父類可能知道子類應該有哪個功能,但是功能具體怎么實現父類是不清楚的(由子類自己決定),父類只需要提供一個沒有方法體的定義即可,具體實現交給子類自己去實現。我們把沒有方法體的方法稱為抽象方法。Java語法規定,包含抽象方法的類就是抽象類。
- 抽象方法 : 沒有方法體的方法。
- 抽象類:包含抽象方法的類。
1.2 abstract使用格式
abstract是抽象的意思,用于修飾方法方法和類,修飾的方法是抽象方法,修飾的類是抽象類。
1.2.1 抽象方法
使用abstract
關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值類型 方法名(參數列表);
代碼舉例:
public abstract void run();
1.2.2 抽象類
如果一個類包含抽象方法,那么該類必須是抽象類。注意:抽象類不一定有抽象方法,但是有抽象方法的類必須定義成抽象類。
定義格式:
abstract class 名字{}
public abstract class Animal{public abstract void run();
}
1.2.3 抽象類的使用
要求:繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須聲明為抽象類。
代碼舉例:
// 父類,抽象類
abstract class Employee {private String id;private String name;private double salary;public Employee() {}public Employee(String id, String name, double salary) {this.id = id;this.name = name;this.salary = salary;}// 抽象方法// 抽象方法必須要放在抽象類中abstract public void work();
}// 定義一個子類繼承抽象類
class Manager extends Employee {public Manager() {}public Manager(String id, String name, double salary) {super(id, name, salary);}// 2.重寫父類的抽象方法@Overridepublic void work() {System.out.println("管理其他人");}
}// 定義一個子類繼承抽象類
class Cook extends Employee {public Cook() {}public Cook(String id, String name, double salary) {super(id, name, salary);}@Overridepublic void work() {System.out.println("廚師炒菜多加點鹽...");}
}// 測試類
public class Demo10 {public static void main(String[] args) {// 創建抽象類,抽象類不能創建對象// 假設抽象類讓我們創建對象,里面的抽象方法沒有方法體,無法執行.所以不讓我們創建對象
// Employee e = new Employee();
// e.work();// 3.創建子類Manager m = new Manager();m.work();Cook c = new Cook("ap002", "庫克", 1);c.work();}
}
此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法。
1.3 抽象類的特征
抽象類的特征總結起來可以說是 有得有失
有得:抽象類得到了擁有抽象方法的能力。
有失:抽象類失去了創建對象的能力。
其他成員(構造方法,實例方法,靜態方法等)抽象類都是具備的。
1.4 抽象類的細節
不需要背,只要當idea報錯之后,知道如何修改即可。
關于抽象類的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
-
抽象類不能創建對象,如果創建,編譯無法通過而報錯。只能創建其非抽象子類的對象。
理解:假設創建了抽象類的對象,調用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
-
抽象類中,可以有構造方法,是供子類創建對象時,初始化父類成員使用的。
理解:子類的構造方法中,有默認的super(),需要訪問父類構造方法。
-
抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
理解:未包含抽象方法的抽象類,目的就是不想讓調用者創建該類對象,通常用于某些特殊的類結構設計。
-
抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則子類也必須定義成抽象類,編譯無法通過而報錯。
理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那么創建對象后,調用抽象的方法,沒有意義。
-
抽象類存在的意義是為了被子類繼承。
理解:抽象類中已經實現的是模板中確定的成員,抽象類不確定如何實現的定義成抽象方法,交給具體的子類去實現。
1.5 抽象類存在的意義
? 抽象類存在的意義是為了被子類繼承,否則抽象類將毫無意義。抽象類可以強制讓子類,一定要按照規定的格式進行重寫。
第二章 接口
2.1 概述
我們已經學完了抽象類,抽象類中可以用抽象方法,也可以有普通方法,構造方法,成員變量等。那么什么是接口呢?接口是更加徹底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同樣是不能創建對象的。
2.2定義格式
//接口的定義格式:
interface 接口名稱{// 抽象方法
}// 接口的聲明:interface
// 接口名稱:首字母大寫,滿足“駝峰模式”
2.3 接口成分的特點
在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量
2.3.1.抽象方法
? 注意:接口中的抽象方法默認會自動加上public abstract修飾程序員無需自己手寫!!
? 按照規范:以后接口中的抽象方法建議不要寫上public abstract。因為沒有必要啊,默認會加上。
2.3.2 常量
在接口中定義的成員變量默認會加上: public static final修飾。也就是說在接口中定義的成員變量實際上是一個常量。這里是使用public static final修飾后,變量值就不可被修改,并且是靜態化的變量可以直接用接口名訪問,所以也叫常量。常量必須要給初始值。常量命名規范建議字母全部大寫,多個單詞用下劃線連接。
2.3.3 案例演示
public interface InterF {// 抽象方法!// public abstract void run();void run();// public abstract String getName();String getName();// public abstract int add(int a , int b);int add(int a , int b);// 它的最終寫法是:// public static final int AGE = 12 ;int AGE = 12; //常量String SCHOOL_NAME = "黑馬程序員";}
2.4 基本的實現
2.4.1 實現接口的概述
類與接口的關系為實現關系,即類實現接口,該類可以稱為接口的實現類,也可以稱為接口的子類。實現的動作類似繼承,格式相仿,只是關鍵字不同,實現使用 implements
關鍵字。
2.4.2 實現接口的格式
/**接口的實現:在Java中接口是被實現的,實現接口的類稱為實現類。實現類的格式:*/
class 類名 implements 接口1,接口2,接口3...{}
從上面格式可以看出,接口是可以被多實現的。
2.4.3 類實現接口的要求和意義
- 必須重寫實現的全部接口中所有抽象方法。
- 如果一個類實現了接口,但是沒有重寫完全部接口的全部抽象方法,這個類也必須定義成抽象類。
- 意義:接口體現的是一種規范,接口對實現類是一種強制性的約束,要么全部完成接口申明的功能,要么自己也定義成抽象類。這正是一種強制性的規范。
2.4.4 類與接口基本實現案例
假如我們定義一個運動員的接口(規范),代碼如下:
/**接口:接口體現的是規范。* */
public interface SportMan {void run(); // 抽象方法,跑步。void law(); // 抽象方法,遵守法律。String compittion(String project); // 抽象方法,比賽。
}
接下來定義一個乒乓球運動員類,實現接口,實現接口的實現類代碼如下:
```java
package com.itheima._03接口的實現;
/*** 接口的實現:* 在Java中接口是被實現的,實現接口的類稱為實現類。* 實現類的格式:* class 類名 implements 接口1,接口2,接口3...{*** }* */
public class PingPongMan implements SportMan {@Overridepublic void run() {System.out.println("乒乓球運動員稍微跑一下!!");}@Overridepublic void law() {System.out.println("乒乓球運動員守法!");}@Overridepublic String compittion(String project) {return "參加"+project+"得金牌!";}
}```java
package com.itheima._03接口的實現;
/*** 接口的實現:* 在Java中接口是被實現的,實現接口的類稱為實現類。* 實現類的格式:* class 類名 implements 接口1,接口2,接口3...{*** }* */
public class PingPongMan implements SportMan {@Overridepublic void run() {System.out.println("乒乓球運動員稍微跑一下!!");}@Overridepublic void law() {System.out.println("乒乓球運動員守法!");}@Overridepublic String compittion(String project) {return "參加"+project+"得金牌!";}
}
測試代碼
public class TestMain {public static void main(String[] args) {// 創建實現類對象。PingPongMan zjk = new PingPongMan();zjk.run();zjk.law();System.out.println(zjk.compittion("全球乒乓球比賽"));}
}
2.4.5 類與接口的多實現案例
類與接口之間的關系是多實現的,一個類可以同時實現多個接口。
首先我們先定義兩個接口,代碼如下:
/** 法律規范:接口*/
public interface Law {void rule();
}/** 這一個運動員的規范:接口*/
public interface SportMan {void run();
}
然后定義一個實現類:
/*** Java中接口是可以被多實現的:* 一個類可以實現多個接口: Law, SportMan** */
public class JumpMan implements Law ,SportMan {@Overridepublic void rule() {System.out.println("尊長守法");}@Overridepublic void run() {System.out.println("訓練跑步!");}
}
從上面可以看出類與接口之間是可以多實現的,我們可以理解成實現多個規范,這是合理的。
2.5 接口與接口的多繼承
Java中,接口與接口之間是可以多繼承的:也就是一個接口可以同時繼承多個接口。大家一定要注意:
類與接口是實現關系
接口與接口是繼承關系
接口繼承接口就是把其他接口的抽象方法與本接口進行了合并。
案例演示:
public interface Abc {void go();void test();
}/** 法律規范:接口*/
public interface Law {void rule();void test();
}** 總結:* 接口與類之間是多實現的。* 接口與接口之間是多繼承的。* */
public interface SportMan extends Law , Abc {void run();
}
2.6擴展:接口的細節
不需要背,只要當idea報錯之后,知道如何修改即可。
關于接口的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
- 當兩個接口中存在相同抽象方法的時候,該怎么辦?
只要重寫一次即可。此時重寫的方法,既表示重寫1接口的,也表示重寫2接口的。
- 實現類能不能繼承A類的時候,同時實現其他接口呢?
繼承的父類,就好比是親爸爸一樣
實現的接口,就好比是干爹一樣
可以繼承一個類的同時,再實現多個接口,只不過,要把接口里面所有的抽象方法,全部實現。
- 實現類能不能繼承一個抽象類的時候,同時實現其他接口呢?
實現類可以繼承一個抽象類的同時,再實現其他多個接口,只不過要把里面所有的抽象方法全部重寫。
- 實現類Zi,實現了一個接口,還繼承了一個Fu類。假設在接口中有一個方法,父類中也有一個相同的方法。子類如何操作呢?
處理辦法一:如果父類中的方法體,能滿足當前業務的需求,在子類中可以不用重寫。
處理辦法二:如果父類中的方法體,不能滿足當前業務的需求,需要在子類中重寫。
- 如果一個接口中,有10個抽象方法,但是我在實現類中,只需要用其中一個,該怎么辦?
可以在接口跟實現類中間,新建一個中間類(適配器類)
讓這個適配器類去實現接口,對接口里面的所有的方法做空重寫。
讓子類繼承這個適配器類,想要用到哪個方法,就重寫哪個方法。
因為中間類沒有什么實際的意義,所以一般會把中間類定義為抽象的,不讓外界創建對象
第三章 內部類
3.1 概述
3.1.1 什么是內部類
將一個類A定義在另一個類B里面,里面的那個類A就稱為內部類,B則稱為外部類。可以把內部類理解成寄生,外部類理解成宿主。
3.1.2 什么時候使用內部類
一個事物內部還有一個獨立的事物,內部的事物脫離外部的事物無法獨立使用
- 人里面有一顆心臟。
- 汽車內部有一個發動機。
- 為了實現更好的封裝性。
3.2 內部類的分類
按定義的位置來分
- 成員內部類,類定義在了成員位置 (類中方法外稱為成員位置,無static修飾的內部類)
- 靜態內部類,類定義在了成員位置 (類中方法外稱為成員位置,有static修飾的內部類)
- 局部內部類,類定義在方法內
- 匿名內部類,沒有名字的內部類,可以在方法中,也可以在類中方法外。
3.3 成員內部類
成員內部類特點:
- 無static修飾的內部類,屬于外部類對象的。
- 宿主:外部類對象。
內部類的使用格式:
外部類.內部類。 // 訪問內部類的類型都是用 外部類.內部類
獲取成員內部類對象的兩種方式:
方式一:外部直接創建成員內部類的對象
外部類.內部類 變量 = new 外部類().new 內部類();
方式二:在外部類中定義一個方法提供內部類的對象
案例演示
3.4 成員內部類的細節
編寫成員內部類的注意點:
- 成員內部類可以被一些修飾符所修飾,比如: private,默認,protected,public,static等
- 在成員內部類里面,JDK16之前不能定義靜態變量,JDK16開始才可以定義靜態變量。
- 創建內部類對象時,對象中有一個隱含的Outer.this記錄外部類對象的地址值。(請參見3.6節的內存圖)
詳解:
? 內部類被private修飾,外界無法直接獲取內部類的對象,只能通過3.3節中的方式二獲取內部類的對象
? 被其他權限修飾符修飾的內部類一般用3.3節中的方式一直接獲取內部類的對象
? 內部類被static修飾是成員內部類中的特殊情況,叫做靜態內部類下面單獨學習。
? 內部類如果想要訪問外部類的成員變量,外部類的變量必須用final修飾,JDK8以前必須手動寫final,JDK8之后不需要手動寫,JDK默認加上。
3.5例題:
public class Test {public static void main(String[] args) {Outer.inner oi = new Outer().new inner();oi.method();}
}class Outer { // 外部類private int a = 30;// 在成員位置定義一個類class inner {private int a = 20;public void method() {int a = 10;System.out.println(???); // 10 答案:aSystem.out.println(???); // 20 答案:this.aSystem.out.println(???); // 30 答案:Outer.this.a}}
}
3.6 成員內部類內存圖
3.7 靜態內部類
靜態內部類特點:
- 靜態內部類是一種特殊的成員內部類。
- 有static修飾,屬于外部類本身的。
- 總結:靜態內部類與其他類的用法完全一樣。只是訪問的時候需要加上外部類.內部類。
- 拓展1:靜態內部類可以直接訪問外部類的靜態成員。
- 拓展2:靜態內部類不可以直接訪問外部類的非靜態成員,如果要訪問需要創建外部類的對象。
- 拓展3:靜態內部類中沒有銀行的Outer.this。
內部類的使用格式:
外部類.內部類。
靜態內部類對象的創建格式:
外部類.內部類 變量 = new 外部類.內部類構造器;
調用方法的格式:
- 調用非靜態方法的格式:先創建對象,用對象調用
- 調用靜態方法的格式:外部類名.內部類名.方法名();
案例演示:
// 外部類:Outer01
class Outer01{private static String sc_name = "黑馬程序";// 內部類: Inner01public static class Inner01{// 這里面的東西與類是完全一樣的。private String name;public Inner01(String name) {this.name = name;}public void showName(){System.out.println(this.name);// 拓展:靜態內部類可以直接訪問外部類的靜態成員。System.out.println(sc_name);}}
}public class InnerClassDemo01 {public static void main(String[] args) {// 創建靜態內部類對象。// 外部類.內部類 變量 = new 外部類.內部類構造器;Outer01.Inner01 in = new Outer01.Inner01("張三");in.showName();}
}
3.8 局部內部類
- 局部內部類 :定義在方法中的類。
定義格式:
class 外部類名 { 數據類型 變量名; 修飾符 返回值類型 方法名(參數列表) { // … class 內部類 { // 成員變量 // 成員方法 } }}
3.9 匿名內部類【重點】
3.9.1 概述
匿名內部類 :是內部類的簡化寫法。他是一個隱含了名字的內部類。開發中,最常用到的內部類就是匿名內部類了。
3.9.2 格式
new 類名或者接口名() {重寫方法;
};
包含了:
-
繼承或者實現關系
-
方法重寫
-
創建對象
所以從語法上來講,這個整體其實是匿名內部類對象
3.9.2 什么時候用到匿名內部類
實際上,如果我們希望定義一個只要使用一次的類,就可考慮使用匿名內部類。匿名內部類的本質作用
是為了簡化代碼。
之前我們使用接口時,似乎得做如下幾步操作:
- 定義子類
- 重寫接口中的方法
- 創建子類對象
- 調用重寫后的方法
interface Swim {public abstract void swimming();
}// 1. 定義接口的實現類
class Student implements Swim {// 2. 重寫抽象方法@Overridepublic void swimming() {System.out.println("狗刨式...");}
}public class Test {public static void main(String[] args) {// 3. 創建實現類對象Student s = new Student();// 4. 調用方法s.swimming();}
}
我們的目的,最終只是為了調用方法,那么能不能簡化一下,把以上四步合成一步呢?匿名內部類就是做這樣的快捷方式。
3.9.3 匿名內部類前提和格式
匿名內部類必須繼承一個父類或者實現一個父接口。
匿名內部類格式
x new 父類名或者接口名(){// 方法重寫 @Override public void method() { // 執行語句 }};
3.9.4 使用方式
以接口為例,匿名內部類的使用,代碼如下:
interface Swim{public abstract void swimming();
}
public class Demo7{public static void main(String[] args) {// 使用匿名內部類new Swim() {@Overridepublic void swimming() {System.out.println("自由泳...");}}.swimming();// 接口 變量 = new 實現類(); // 多態,走子類的重寫方法Swim s2 = new Swim() {@Overridepublic void swimming() {System.out.println("蛙泳...");}};s2.swimming();s2.swimming();}
}
3.9.5 匿名內部類的特點
- 定義一個沒有名字的內部類
- 這個類實現了父類,或者父類接口
- 匿名內部類會創建這個沒有名字的類的對象
3.9.6 匿名內部類的使用場景
通常在方法的形式參數是接口或者抽象類時,也可以將匿名內部類作為參數傳遞。代碼如下:
interface Swim {public abstract void swimming();
}public class Demo07 {public static void main(String[] args) {// 普通方式傳入對象// 創建實現類對象Student s = new Student();goSwimming(s);// 匿名內部類使用場景:作為方法參數傳遞Swim s3 = new Swim() {@Overridepublic void swimming() {System.out.println("蝶泳...");}};// 傳入匿名內部類goSwimming(s3);// 完美方案: 一步到位goSwimming(new Swim() {public void swimming() {System.out.println("大學生, 蛙泳...");}});goSwimming(new Swim() {public void swimming() {System.out.println("小學生, 自由泳...");}});}// 定義一個方法,模擬請一些人去游泳public static void goSwimming(Swim s) {s.swimming();}
}