今日內容
-
多態
-
內部類
-
權限修飾符
-
代碼塊
教學目標
-
能夠說出多態的前提
-
能夠寫出多態的格式
-
能夠理解多態向上轉型和向下轉型
-
能夠說出內部類概念
-
能夠理解匿名內部類的編寫格式
-
能夠說出每種權限修飾符的作用
第一章 多態(必須掌握)
1.1 概述
引入
面嚮對象語言三大特徵:封裝(private)、繼承(extends)、多態。
多態:表示的是一個事物的多種表現形態。同一個事物,以不同的形態表現出來.
多態來源於生活,在生活中我們經常會對某一類事物使用它的共性統稱來表示某個具體的事物,這時這個具體的事物就以其他的形式展示出來。
蘋果:說蘋果,說水果。
狗:說狗,說動物。
貓:說貓,說動物。
前提【重點】
- 繼承(extends)或者實現(implements)【二選一】
- 方法的重寫【意義體現:不重寫,無意義】
- 父類引用指向子類對象【格式體現】
1.2 多態的體現
多態體現的格式:
父類類型 變量名 = new 子類對象;
變量名.方法名();
父類類型:指子類對象繼承的父類類型,或者實現的父接口類型。
代碼如下:
Fu f = new Zi();
f.method();
當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,執行的是子類重寫後方法。
代碼如下:
定義父類:
public abstract class Animal {
public abstract void eat();
}
定義子類:
class Cat extends Animal {
public void eat() {
System.out.println("喫魚");
}
}
class 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();
}
}
多態在代碼中的體現爲父類引用指向子類對象。
1.3 多態的好處
實際開發的過程中,父類類型作爲方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展性與便利。代碼如下:
定義父類:
public abstract class Animal {
public abstract void eat();
}
定義子類:
class Cat extends Animal {
public void eat() {
System.out.println("喫魚");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("喫骨頭");
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 多態形式,創建對象
Cat c = new Cat();
Dog d = new Dog();
// 調用showCatEat
showCatEat(c);
// 調用showDogEat
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都可以完成。
所以,多態的好處,體現在,可以使程序編寫的更簡單,並有良好的擴展。
小結:多態的好處是提高程序的靈活性,擴展性
1.4 引用類型轉換
多態的轉型分爲向上轉型與向下轉型兩種:
向上轉型
- 向上轉型:多態本身是子類類型向父類類型向上轉換的過程,這個過程是默認的。
當父類引用指向一個子類對象時,便是向上轉型。
使用格式:
父類類型 變量名 = 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() {
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
}
}
轉型的異常
轉型的過程中,一不小心就會遇到這樣的問題,請看如下代碼:
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對象的。這兩個類型並沒有任何繼承關係,不符合類型轉換的定義。
爲了避免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.5筆記本案例
-
案例目的:接口也可以體現多態
-
案例代碼:
USB接口:
/* * 定義一個USB接口 */ public interface USB { public void open(); public void close(); }
鼠標類:
/* * 定義一個鼠標類 */ public class Mouse implements USB{ //行爲:開啓 public void open() { System.out.println("mouse open...."); } //行爲:關閉 public void close() { System.out.println("mouse close...."); } }
鍵盤類:
/* * 定義一個鍵盤類 */ public class Keyboard implements USB{ // 行爲:開啓 public void open() { System.out.println("keyboard open...."); } // 行爲:關閉 public void close() { System.out.println("keyboard close...."); } }
電腦類:
/* * 描述電腦類 */ public class Computer { //屬性 //行爲 public void run() { System.out.println("computer run...."); } //給電腦外圍設備提供預留的功能,該功能要遵守USB接口的規則 public void useUSB(USB u)//USB u=m { if(u!=null) { u.open(); u.close(); } } }
測試類:
public class ComputerTest { public static void main(String[] args) { // 創建筆記本電腦類的對象 Computer c = new Computer(); c.run(); Mouse m = new Mouse(); //電腦使用鼠標 c.useUSB(m); Keyboard k = new Keyboard(); // //電腦使用鍵盤 c.useUSB(k); } }
總結:發現接口的出現:
1、 擴展了筆記本電腦的功能.
2、 定義了規則.
3、降低了筆記本電腦和外圍設備之間的耦合性.
解耦
第二章 內部類
2.1 概述
什麼是內部類
類是描述事物的,內部類,它也是用來描述事物,只不過在描述的這個事物需要依賴在其他事物中。
內部類:在其他類中類。
使用地方:
當我們在描述事物的時候,事物中還有其他的事物,這個位於事物中的那個事物,就可以使用Java中提供的內部類來描述。
比如:人,可以使用Person類描述。人這個事物還有心臟這個事物,可是心臟這個事物不僅僅人所具備,而其他的動物也會具備心臟這個事物。
因此我們在描述比如人這類事物的時候,如果還需要描述心臟,那麼就必須把心臟這個類描述在人這個類的內部。
這個心臟就需要使用內部類描述。
簡單來講:在一個類中包含了另一個類。
2.2 成員內部類
- 成員內部類 :定義在類中方法外的類。
定義格式:
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 {
System.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();
// 創建內部類對象
Person.Heart heart = p.new Heart();
// 調用內部類方法
heart.jump();
// 調用外部類方法
p.setLive(false);
// 調用內部類方法
heart.jump();
}
}
輸出結果:
心臟在跳動
心臟不跳了
內部類仍然是一個獨立的類,在編譯之後會內部類會被編譯成獨立的.class文件,但是前面冠以外部類的類名和$符號 。
比如,Person$Heart.class
小結:內部類是定義在一個類中的類。
2.3 匿名內部類(重點)
- 匿名內部類 :是內部類的簡化寫法。它的本質是一個
帶具體實現的
父類或者父接口的
匿名的
子類對象。
開發中,最常用到的內部類就是匿名內部類了。以接口舉例,當你使用一個接口時,似乎得做如下幾步操作,
- 定義子類
- 重寫接口中的方法
- 創建子類對象
- 調用重寫後的方法
我們的目的,最終只是爲了調用方法,那麼能不能簡化一下,把以上四步合成一步呢?匿名內部類就是做這樣的快捷方式。
前提
存在一個類或者接口,這裏的類可以是具體類也可以是抽象類。
格式
new 父類名或者接口名(){
// 方法重寫
@Override
public void method() {
// 執行語句
}
};
使用方式
以接口爲例,匿名內部類的使用,代碼如下:
定義接口:
public abstract class FlyAble{
public abstract void fly();
}
匿名內部類可以通過多態的形式接受
public class InnerDemo01 {
public static void main(String[] args) {
/*
1.等號右邊:定義並創建該接口的子類對象
2.等號左邊:是多態,接口類型引用指向子類對象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飛了~~~");
}
};
}
}
匿名內部類直接調用方法
public class InnerDemo02 {
public static void main(String[] args) {
/*
1.等號右邊:定義並創建該接口的子類對象
2.等號左邊:是多態,接口類型引用指向子類對象
*/
new FlyAble(){
public void fly() {
System.out.println("我飛了~~~");
}
}.fly();
}
}
方法的形式參數是接口或者抽象類時,也可以將匿名內部類作爲參數傳遞
public class InnerDemo3 {
public static void main(String[] args) {
/*
1.等號右邊:定義並創建該接口的子類對象
2.等號左邊:是多態,接口類型引用指向子類對象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飛了~~~");
}
};
// 將f傳遞給showFly方法中
showFly(f);
}
public static void showFly(FlyAble f) {
f.fly();
}
}
以上可以簡化,代碼如下:
public class InnerDemo2 {
public static void main(String[] args) {
/*
創建匿名內部類,直接傳遞給showFly(FlyAble f)
*/
showFly( new FlyAble(){
public void fly() {
System.out.println("我飛了~~~");
}
});
}
public static void showFly(FlyAble f) {
f.fly();
}
}
小結:匿名內部類做的事情是創建一個類的子類對象
第三章 權限修飾符
3.1 概述
在Java中提供了四種訪問權限,使用不同的訪問權限修飾符修飾時,被修飾的內容會有不同的訪問權限,
-
public:公共的。沒有限制。在同一個包中同一個類中 或 在同一個包中不同類之間 或 不同包之間的子類中 或 不同包不同類之間都可以訪問(簡而言之-------》任何包中的類都可以訪問);修飾成員變量 方法 構造方法 類
-
protected:受保護的。在本類中訪問 或 同一個包中的其它類中訪問 或 不同包中的子類訪問;
-
默認的權限(空的):就是什麼權限修飾符也不寫。在本類訪問或同一個包中的其它類中訪問;
-
private:私有的。只能在本類(同一類)中訪問。其它類(包含子類)都不能訪問;修飾成員變量 方法 構造方法
public > protected>默認的權限(空的)>private
3.2 不同權限的訪問能力
public | protected | (空的) | private | |
---|---|---|---|---|
同一類中 | √ | √ | √ | √ |
同一包中(子類與無關類) | √ | √ | √ | |
不同包的子類 | √ | √ | ||
不同包中的無關類 | √ |
可見,public具有最大權限。private則是最小權限。
說明:關於protected(受保護權限)的演示,可以在不同包的子類中訪問。
1、public訪問權限:沒有任何限制(權限最大)
代碼舉例:
需求:
1)定義一個com.fu包,在這個包中創建一個使用public關鍵字來修飾的Fu類,在這個父類中定義一個使用public修飾的method函數,在這個函數中隨便打印一句話;
2)再重新定義一個com.zi包,在這個包中引入com.fu包下面的Fu類,定義一個使用public關鍵字來修飾的Zi類繼承Fu類,在Zi類中定義一個public修飾的函數run,在這個函數中調用父類中method方法;
3)在重新定義一個com.test包,在這個包中引入com.fu包下面的Fu類和com.zi包下面的Zi類,定義一個測試類Test,在這個測試類中分別創建子父類對象,並使用對象分別調用各自中的函數;
父類:
package com.fu;
public class Fu
{
public void method()
{
System.out.println("父類中的方法....");
}
}
子類:
package com.zi;
import com.fu.Fu;//導入Fu類
public class Zi extends Fu
{
public void run()
{
System.out.println("子類中的方法....");
method(); //子類繼承了父類 是可以直接去訪問父類中的方法
}
}
測試類:
package com.test;
import com.fu.Fu;//導入Fu類
import com.zi.Zi;//導入子類
class Test
{
public static void main(String[] args)
{
Zi z = new Zi();
z.run();//子類對象調用子類中的方法
/*
測試類沒有繼承Fu這個類,但是可以創建對象,那麼就可以調用Fu類中的method方法,
這樣導致Fu類中的方法不僅僅子類可以直接去 訪問,不是Fu的子類也可以訪問了。
*/
Fu f = new Fu();
f.method();
}
}
問題:
com.test包中的Test類沒有繼承com.fu包中的Fu這個類,但是可以在com.test包中的Test類中創建Fu類對象,那麼就可以在com.test包中的Test類中調用com.fu包中的Fu類中的method方法,這樣導致Fu類中的方法不僅僅在com.zi包中的子類Zi可以直接去訪問,而不是Fu的子類也可以訪問了。
上述現象導致Zi類繼承Fu類沒有任何意義了。
在java中當定義一個類的時候,如果當前這個類中的方法只讓自己的子類使用,而其他的類不讓使用,這時可以在這個方法前面加上protected關鍵字。
protected訪問權限:專門給子類使用的訪問權限。
將上述Fu類中的method函數的修飾符public換成protected。
父類:
package com.fu;
public class Fu
{
protected void method()
{
System.out.println("父類中的方法....");
}
}
子類:
package com.zi;
import com.fu.Fu;//導入Fu類
public class Zi extends Fu
{
public void run()
{
System.out.println("子類中的方法....");
method(); //子類繼承了父類 是可以直接去訪問父類中的方法
}
}
測試類:
package com.test;
import com.fu.Fu;//導入Fu類
import com.zi.Zi;//導入子類
class Test
{
public static void main(String[] args)
{
Zi z = new Zi();
z.run();//子類對象調用子類中的方法
Fu f = new Fu();
f.method();//報錯
}
}
注意:
1)如果父類中的函數使用protected關鍵字修飾,那麼這個函數可以被不同包下的子類訪問,而不同包中的其他類不能訪問。
2)protected只能修飾成員函數或成員變量,不能修飾類。
3) 四種訪問權限,能夠跨包訪問的只有 public protected(必須是子類)
4)public修飾符和默認修飾符注意事項
package com.itheima.sh.demo_03;
/*
1.public 修飾的類,要求當前.java源文件名必須和public修飾的類名一致
2.如果一個類是默認修飾符,那麼當前源文件名可以隨便定義
3.如果一個源文件中具有多個class類,那麼只能有一個使用public修飾,並且源文件名必須是public修飾的類的名字
*/
class Demo02{
}
public class Demo01 {
}
編寫代碼時,如果沒有特殊的考慮,建議這樣使用權限:
-
成員變量使用
private
,隱藏細節。 -
構造方法使用
public
,方便創建對象。 -
成員方法使用
public
,方便調用方法。
第四章 代碼塊
4.1 構造代碼塊
-
構造代碼塊:
-
位置:定義在成員位置的代碼塊{}
-
執行:每次創建對象都會執行構造代碼塊
-
作用:一般是一個類的多個構造方法重複的代碼放到構造代碼塊中
-
使用場景:
舉例:統計當前類一共創建了幾個對象
-
public class Teacher {
String name;
int age;
//定義計數器
static int count = 0;
//構造代碼塊
//構造代碼塊會在構造方法之前執行,並且每次執行構造方法都會先執行構造代碼塊
{
System.out.println("構造代碼塊");
//統計
count++;
}
//無參構造
public Teacher(){
System.out.println("構造方法");
}
//有參構造
public Teacher(String name, int age){
this.name = name;
this.age = age;
}
}
public class Demo02 {
public static void main(String[] args) {
//創建對象
Teacher t1 = new Teacher();
Teacher t2 = new Teacher();
Teacher t3 = new Teacher("柳巖",35);
System.out.println("一共創建過" + Teacher.count +"次");
}
}
4.2 靜態代碼塊(掌握)
-
靜態代碼塊:
-
格式:
//靜態代碼塊 static{ }
-
位置:定義在成員位置,類中方法外。
-
執行:在當前類第一次被使用時,靜態代碼塊會執行。並且靜態代碼塊只會執行一次。
-
使用場景:比如我們加載驅動(驅動需要在第一次使用前加載,只需要加載一次),這種功能就可以放在靜態代碼塊中。
-
格式:
public class Person {
private String name;
private int age;
//靜態代碼塊
static{
System.out.println("靜態代碼塊執行了");
}
}
4.3 局部代碼塊
- 格式:
定義在方法中:
//局部代碼塊
{
}
public class Demo03 {
public static void main(String[] args) {
int a = 10;
//局部代碼塊
//局部代碼塊的作用就是限制變量的作用域
{
int b = 20;
}
//int a = 30; //在同一個區域不能定義重名變量
//不報錯,因爲作用域不同
int b = 40;
}
}
4.4 執行順序演示
public class AAA {
//靜態代碼塊
//靜態代碼塊只會執行一次 並在最開始第一個執行
static{
System.out.println(2);
}
//構造代碼塊
//在每次執行構造方法之前先執行構造代碼塊
{
System.out.println(3);
}
//構造方法
public AAA() {
System.out.println(1);
}
}
public class Demo04{
public static void main(String[] args) {
//創建對象
AAA a1 = new AAA();
AAA a2 = new AAA();
}
}
執行結果: 2 3 1 3 1