JAVA編程講義.抽象類、接口和內部類
前面我們學習了類、對象、封裝、繼承、多態等面向對象編程的基本概念,初步了解了面向對象程序設計理念,接下來我們繼續學習面向對象編程的一些重要概念:抽象類、接口和內部類。其中,抽象類是從多個具體類中抽象出來的父類,可以作為子類的模板,實現更加豐富的類繼承;接口是Java語言中實現多重繼承的重要工具,Java是單繼承的語言,而實際應用中某些類往往需要繼承多個類的屬性與行為,接口很好地解決了單繼承的這一缺陷;內部類是定義在類中的類,它同樣有著非常重要的作用,如更好地實現隱藏、實現多重繼承、實現同一個類中兩種同名方法的調用等。
7.1 抽象類
在面向對象程序設計中,常常會遇到這樣的問題:對于父類中的某些方法,其不同子類中的這些方法肯定有所不同,這些方法必須在子類中重寫,故而根本無需在父類中實現這些方法。面對這類問題,Java提供了抽象類,對一系列看上去不同但本質上相同的具體概念進行抽象,用來表征對問題領域進行分析、設計中得出的抽象概念,使類設計的體系結構更加清晰。
Java語言中提供了abstract關鍵字,表示抽象的意思。用abstract關鍵字修飾的類稱為抽象類,抽象類有類似“模板”的作用,我們可以定義抽象類來表征某些子類或對象的廣義屬類的共有特征,但是抽象類不能直接使用new關鍵字創建對象,只能被繼承,并在其子類中將其特征具體化;用abstract關鍵字修飾的方法稱為抽象方法,它是一個不完整的方法,只有聲明,沒有方法體。抽象類可以包含抽象方法,也可以不包含。定義抽象類和抽象方法的具體語法如下:
abstract class 抽象類名{ // 定義抽象類,用abstract修飾
[訪問修飾符]abstract 返回類型方法名稱(參數); // 定義抽象方法,用abstract修飾,無方法體
}
通過abstract關鍵字標記,抽象類和抽象方法可以讓開發人員明確該類或方法的作用,進而在繼承抽象類的時候重寫其抽象方法,以針對性地解決具體問題。定義抽象類或抽象方法的時候,需要注意以下幾點:
? ?抽象類和抽象方法必須使用abstract修飾符來修飾,不能使用private修飾符,因為子類需要有繼承抽象類的權限。
? ?由于抽象類是需要被繼承的,所以抽象類不能用final修飾,即關鍵字abstract和final不能同時使用。
? ?抽象類不能被實例化,可以有構造方法和普通方法,且構造方法不能聲明為抽象的。
? ?普通方法內有方法體,而抽象方法不能有方法體。
? ?抽象方法不能使用static修飾符來修飾,因為static修飾的方法可以通過類名調用,而調用一個沒有方法體的方法會報錯。
? ?如果一個類繼承了抽象類,則該類必須實現抽象類中的全部抽象方法,除非子類也是抽象類。
接下來,我們通過具體問題來體會抽象類與抽象方法的具體應用。幾何形狀是大家熟悉不過的,它們形狀千姿百態,有圓形、正方形、三角形等,這些幾何形狀都可以計算面積,但是計算方法卻不同。可見,計算面積是幾何形狀的共有特征,而不同的幾何形狀又有不同的具體實現。于是,為了設計出結構清晰的幾何形狀面積計算類體系,我們可以先定義抽象的幾何形狀類Shape類,該類包含公共的顏色屬性color,也包公共的面積計算方法getArea()方法,顯然該方法也必須是抽象的。接著,我們進一步定義圓形類Circle類和正方形類Square類,讓這兩個類繼承Shape類,并實現getArea()方法。
下面我們給出上述問題的具體實現,如例7-1所示。
例7-1 Demo0701.java
1 ?package com.aaa.p0701;
2
3 ?abstract class Shape { // 定義抽象的幾何形狀類Shape類
4 ? String color;
5 ? public Shape(String color){
6 ? this.color = color;
7 ? System.out.println("當前圖形顏色是:" + color);
8 ? }
9 ? abstract double getArea(); // 求面積的抽象方法
10 ?}
11 ?class Circle extends Shape{ // 定義Shape類的子類Circle類
12 ? private final double PI = 3.1415926; // 聲明圓周率常量
13 ? private double r; // 聲明圓半徑變量
14 ? public Circle(String color,double radius) {
15 ? super(color);
16 ? this.r = radius;
17 ? }
18 ? @Override
19 ? double getArea() { // 重寫抽象類中的getArea()方法
20 ? return PI * r * r;
21 ? }
22 ?}
23 ?class Square extends Shape{ // 定義Shape類的子類Square類
24 ? private double e; // 聲明正方形的邊長
25 ? public Square(String color,double edge) {
26 ? super(color);
27 ? this.e = edge;
28 ? }
29 ? @Override
30 ? double getArea() { // 重寫抽象類中的getArea()方法
31 ? return e * e;
32 ? }
33 ?}
34
35 ?public class Demo0701{
36 ? public static void main(String[] args) {
37 ? Circle circle = new Circle("紅色",3);
38 ? System.out.println("圓的面積是:" + circle.getArea());
39 ? Shape square = new Square("綠色",4);
40 ? System.out.println("正方形的面積是:" + square.getArea());
41 ? }
42 ?}
程序運行結果如下:
當前圖形顏色是:紅色
圓的面積是:28.274333400000003
當前圖形顏色是:綠色
正方形的面積是:16.0
示例7-1中,定義了抽象的Shape類,并在抽象類中定義了抽象的getArea()方法。在子類Circle類和Square類中分別實現了適合其自身特性的getArea()方法。
注意:抽象類不可以直接用new關鍵字創建對象,但并不能說明抽象類不可以創建對象。例如在例7-1中,main()方法中第39行代碼處創建了抽象類Shape類的對象,但指向的是其子類的對象。
7.2 接口
Java是一門單繼承的語言,一個子類只能繼承一個父類。但是在編程實踐中,對于某些類,只繼承一個抽象類顯然無法滿足要求,需要實現多個抽象類的抽象方法才能解決問題,這就需要通過接口來實現。在Java中,允許通過一個類實現多個接口來實現類似于多重繼承的概念。
7.2.1 接口的定義
接口可以看作是從多個相似的類中抽象出來的規范,不提供任何實現,體現了規范和實現分離的思想。例如,計算機上提供了USB插槽,只要一個硬件遵守USB規范,就能插入USB插槽,可以是鼠標、鍵盤、數據線等,計算機無須關心是和哪個硬件對象建立了聯系。同樣,軟件系統的開發也需要采用規范和實現分離的思想,即采用面向接口的編程思想,從而降低軟件系統模塊之間的耦合度,提高系統的擴展性和可維護性。
在Java語言中,提供了interface關鍵字,用于聲明接口,其語法格式如下:
[public]interface 接口名[extends 接口1,接口2...]{
[public][static][final]數據類型 常量名 = 值;
[public][abstract] 返回值的數據類型 方法名(參數列表);
默認方法...
}
在上述語法中,當interface關鍵字前加上public修飾符時,接口可以被任何類的成員訪問。如果省略public,則接口只能被與它處在同一包中的成員訪問。extends語句與類繼承中的extends語句基本相同,不同點在于接口可以繼承自多個父接口,父接口之間使用逗號分隔。
接口中定義的變量和方法都包含默認的修飾符,其中定義的變量默認聲明為“public static final”,即全局靜態常量,定義的方法默認聲明為“public abstract”,即抽象方法。例如,定義一個Charge接口(收費接口),內有接口常量PORT_STYLE和成員方法getCharge(),代碼如下:
interface Charge(){
int PORT_STYLE = 1; // 接口常量
void getCharge(); // 接口方法聲明
}
7.2.2 接口實現
接口與抽象類相似,也包含抽象方法,因此不能直接實例化接口,即不能使用new創建接口的實例。但是,可以利用接口的特性來創造一個新的類,然后再用新類來創建對象,利用接口創建新類的過程稱為接口的實現。實現接口的目的主要是在新類中重寫抽象的方法,當然也可以使用抽象類來實現抽象方法的重寫。
接口的實現需要使用implements關鍵字,即在聲明一個類的同時用關鍵字implements來實現接口,實現接口的類一般稱為接口的實現類,具體語法如下:
[修飾符]class 類名 implements 接口1,接口2, 接口3,...{ // 如果實現多個接口,以逗號隔開
...
}
一個類實現一個接口時,需要注意如下問題:
? ?如果實現接口的類不是抽象類,則該類必須實現接口的所有抽象方法。
? ?在類中實現接口的抽象方法時,必須使用與接口中完全一致的方法名,否則只是定義一個新的方法,而不是實現已有的抽象方法。
接下來,通過案例來演示接口的實現,如例7-2所示。
例7-2 Demo0702.java
1 ?package com.aaa.p070202;
2
3 ?interface PCI { // 定義PCI接口
4 ? String serialNumber = "9CC0AC186027";
5 ? void start();
6 ? void run();
7 ? void stop();
8 ?}
9 ?public class VideoCard implements PCI{ // 定義顯卡類實現PCI接口
10 ? @Override
11 ? public void start() {
12 ? System.out.println("顯卡開始啟動");
13 ? }
14 ? @Override
15 ? public void run() {
16 ? System.out.println("顯卡序列號是:" + serialNumber);
17 ? System.out.println("顯卡開始工作");
18 ? }
19 ? @Override
20 ? public void stop() {
21 ? System.out.println("顯卡停止工作");
22 ? }
23 ?}
24 ?public class Demo0702{
25 ? public static void main(String[] args) {
26 ? VideoCard videoCard=new VideoCard();
27 ? videoCard.start();
28 ? videoCard.run();
29 ? videoCard.stop();
30 ? }
31 ?}
程序的運行結果如下:
顯卡序列號是:9CC0AC186027
顯卡開始啟動
顯卡開始工作
顯卡停止工作
從運行結果可以看到,程序中定義了一個PCI接口,其定義了一個全局常量serialNumber和3個抽象方法start()、run()、stop(),顯卡類VideoCard實現了PCI接口的這3個抽象方法。在實現類的方法中調用接口的常量,在Demo0702測試類中創建了顯卡類VideoCard的實例并輸出運行結果。
7.2.3 接口的繼承
在現實世界中,網絡通信具有一定的標準,手機只有遵守相應的標準規范才可以使用相應的網絡。然而,隨著移動互聯網技術的發展,網絡通信標準從之前的2G、3G到目前的4G、5G,而且6G也已在研發之中。Java程序中的接口與網絡通信標準類似,定義之后,隨著技術的不斷發展以及應用需求的不斷增加,接口往往也需要更新迭代,主要是功能擴展,增加新的方法,以適應新的需求。但是,使用直接在原接口中增加方法的途徑來擴展接口可能會帶來問題:所有實現原接口的實現類都將因為原來接口的改變而不能正常工作。為了既能擴展接口,又不影響原接口的實現類,一種可行的方法是通過創建原接口的子接口來增加新的方法。
接口的繼承與類的繼承相似,都是使用extends關鍵字來實現繼承,當一個接口繼承父接口時,該接口會獲得父接口中定義的所有抽象方法和常量。但是,接口的繼承比類的繼承要靈活,一個接口可以繼承多個父接口,這樣可以通過接口繼承將多個接口合并為一個接口。接口繼承的語法格式如下:
1 ?interface 接口名 extends 接口1,接口2,接口3,... {
2 ?...
3 ?}
接下來,通過案例來演示接口的繼承,如例7-3所示。
例7-3 Demo0703.java
1 ?package com.aaa.p070203;
2
3 ?interface I3G {
4 ? void onLine(); // 上網
5 ? void call(); // 打電話
6 ? void sendMsg(); // 發短信
7 ?}
8 ?interface I4G extends I3G{
9 ? void watchVideo(); // 看視頻
10 ?}
11 ?class Nokia implements I3G{
12 ? @Override
13 ? public void call() {
14 ? System.out.println("打電話功能");
15 ? }
16 ? @Override
17 ? public void sendMsg() {
18 ? System.out.println("打發信息功能");
19 ? }
20 ? @Override
21 ? public void onLine() {
22 ? System.out.println("上網功能");
23 ? }
24 ?}
25 ?class Mi implements I4G{
26 ? @Override
27 ? public void call() {
28 ? System.out.println("打電話功能");
29 ? }
30 ? @Override
31 ? public void sendMsg() {
32 ? System.out.println("打發信息功能");
33 ? }
34 ? @Override
35 ? public void onLine() {
36 ? System.out.println("上網功能");
37 ? }
38 ? @Override
39 ? public void watchVideo() {
40 ? System.out.println("看視頻功能");
41 ? }
42 ?}
43 ?public class Demo0703 {
44 ? public static void main(String[] args) {
45 ? System.out.println("Nokia手機使用第3代通信技術");
46 ? Nokia nokia = new Nokia();
47 ? nokia.call();
48 ? nokia.onLine();
49 ? nokia.sendMsg();
50 ? System.out.println("小米手機使用第4代通信技術");
51 ? Mi mi = new Mi();
52 ? mi.call();
53 ? mi.onLine();
54 ? mi.sendMsg();
55 ? mi.watchVideo();
56 ? }
57 ?}
程序的運行結果如圖下:
Nokia手機使用第3代通信技術
打電話功能
上網功能
打發信息功能
小米手機使用第4代通信技術
打電話功能
上網功能
打發信息功能
看視頻功能
從運行結果可以看到,I4G接口繼承了I3G接口,直接繼承了I3G接口中的3個抽象方法call()、onLine()、sendMsg(),并新增了一個抽象方法watchVide(),在main()方法中Nokia類實現I3G接口,從而實現父接口的3個抽象方法,而Mi類實現了I4G接口,實現了子接口的4個抽象方法。
編程技巧: 如果一個類同時繼承類并繼承某個接口,需要先extends父類,再implements接口,格式如下:
子類 extends 父類 implements [接口列表]{
...
}
7.2.4 利用接口實現多重繼承
Java語言規定一個類只能繼承一個父類,這給實際開發帶來了許多困擾,因為許多類需要繼承多個父類的成員才能滿足需求,這種問題稱為多重繼承問題。然而,我們也不能將多個父類簡單地合并成一個父類,因為每個父類都有自己的一套代碼,合并到一起之后可能會出現同一方法的多種不同實現,由此會產生代碼沖突,增加代碼的不可靠性。有了接口以后多重繼承問題就迎刃而解了,由于一個了可以實現多個接口,所以在程序設計的過程中我們可以把一些“特殊類”設計成接口,進而通過接口間接地解決多重繼承問題。一個類實現多個接口時,在implements語句中分隔各個接口名,此時這些接口就可以被理解成特殊的類,而這種做法實際上就是使子類獲得了多個父類的成員,并且由于接口成員沒有實現細節,實現接口的類只能有一個具體的實現細節,從而避免了代碼沖突,保證了Java代碼的安全性和可靠性。
接下來,通過案例來演示利用接口實現多重繼承,如例7-4所示。
例7-4 Demo0704.java
1 ?package com.aaa.p070204;
2
3 ?interface IFly { // 定義IFly接口
4 ? void takeoff(); // 起飛方法
5 ? void land(); // 落地方法
6 ? void fly(); // 飛行方法
7 ?}
8 ?interface ISail{ // 定義ISail接口
9 ? void dock(); // 停靠方法
10 ? void cruise(); // 航行方法
11 ?}
12 ?class Vehicle{ // 定義交通工具Vehicle類
13 ? private double speed;
14 ? void setSpeed(int sd){ // 設置速度方法
15 ? this.speed = sd;
16 ? System.out.println("設置速度為" + speed);
17 ? }
18 ? void speedUp(int num){ // 加速方法
19 ? this.speed += num;
20 ? System.out.println("加速" + num + ",速度變為" + speed);
21 ? }
22 ? void speedDown(int num){ // 減速方法
23 ? this.speed -= num;
24 ? System.out.println("減速" + num + ",速度變為" + speed);
25 ? }
26 ?}
27 ?class SeaPlane extends Vehicle implements IFly,ISail{ // 定義水上飛機類
28 ? public void takeoff() {
29 ? System.out.println("水上飛機開始起飛");
30 ? }
31 ? public void land() {
32 ? System.out.println("水上飛機開始落地");
33 ? }
34 ? public void fly() {
35 ? System.out.println("水上飛機可以飛行");
36 ? }
37 ? public void dock() {
38 ? System.out.println("水上飛機可以停靠");
39 ? }
40 ? public void cruise() {
41 ? System.out.println("水上飛機可以航行");
42 ? }
43 ?}
44 ?public class Demo0704 {
45 ? public static void main(String[] args) {
46 ? SeaPlane sp = new SeaPlane();
47 ? sp.takeoff();
48 ? sp.setSpeed(2)
49 ? sp.speedUp(2)
50 ? sp.fly();
51 ? sp.speedDown(2)
52 ? sp.land();
53 ? sp.cruise();
54 ? sp.speedDown(2)
55 ? sp.dock();
56 ? }
57 ?}
程序的運行結果如圖下:
水上飛機開始起飛
設置速度為2
加速2,速度變為4
水上飛機可以飛行
減速2,速度變為2
水上飛機開始落地
水上飛機可以航行
減速2,速度變為0
水上飛機可以停靠
例7-4中,水上飛機類SeaPlane繼承了交通工具類Vehicle,并且實現了IFly接口和ISail接口。從程序運行結果中可以看到,它時具有了交通工具的功能,還增加了飛行功能和航行功能。
7.2.5 接口默認方法
在程序開發中,如果之前創建了一個接口,并且已經被大量的類實現,當需要再添加新的方法以擴充這個接口的功能的時候,就會導致所有已經實現的子類都要重寫這個方法。但是,在接口中使用默認方法就不會有這個問題,所以從 JDK8 開始新加了接口默認方法,便于接口的擴展。
接口默認方法是一個默認實現的方法,并且不強制實現類重寫此方法,使用default關鍵字來修飾。接口新增的默認方法在實現類中可以直接使用。
接下來,通過案例來演示接口默認方法的使用,如例7-5所示。
例7-5 Demo0705.java
1 ?package com.aaa.p070205;
2
3 ?public interface ICat { // 定義ICat接口
4 ? void play(); // 抽象方法
5 ? default void run(){ // 默認方法
6 ? System.out.println("貓咪在跑,貓步...");
7 ? }
8 ?}
9 ?class BlackCat implements ICat{ // 黑貓類實現了ICat接口
10 ? @Override
11 ? public void play() { // 重寫ICat接口的抽象方法
12 ? System.out.println("黑貓在玩耍...");
13 ? }
14 ?}
15 ?public class Demo0705 {
16 ? public static void main(String[] args) {
17 ? BlackCat cat = new BlackCat();
18 ? cat.play();
19 ? cat.run();
20 ? }
21 ?}
程序的運行結果如下:
黑貓在玩耍...
貓咪在跑,貓步...
例7-5中,ICat接口定義了抽象方法play()和默認方法run(), BlackCat類實現了ICat接口,并重寫了抽象方法play(),通過測試類Demo0702中的main()方法創建了BlackCat類的實例,調用play()方法和run()方法后發現,ICat接口接口的默認方法run()可以被它的實現類的對象直接調用。
注意:接口允許定義多個默認方法,其子類可以實現多個接口,因此接口默認方法可能會出現同名情況,此時子類在實現或者調用默認方法時通常遵循以下原則:
(1)子類中的同名方法優先級最高。
(2)如果第一條無法進行判斷,那么子接口的優先級更高;函數簽名相同時,優先選擇擁有最具體實現的默認方法的接口,即如果接口B繼承了接口A,那么接口B就比接口A更加具體。
7.2.6 接口實現多態
在之前的章節中,我們講解了使用繼承機制來實現多態。事實上,使用接口也同樣可以實現多態。
接下來,通過某汽車銷售店案例演示如何通過接口實現多態,如例7-6所示。
例7-6 Demo0706.java
1 ?package com.aaa.p070206;
2
3 ?interface ICar{ // 定義ICar接口
4 ? String showName();
5 ? double getPrice();
6 ?}
7 ?class Haval implements ICar{ // 定義Haval汽車類
8 ? @Override
9 ? public String showName() {
10 ? return "哈佛SUV";
11 ? }
12 ? @Override
13 ? public double getPrice() {
14 ? return 150000;
15 ? }
16 ?}
17 ?class GreatWall implements ICar{ // 定義GreatWall汽車類
18 ? @Override
19 ? public String showName() {
20 ? return "長城汽車";
21 ? }
22 ? @Override
23 ? public double getPrice() {
24 ? return 68000;
25 ? }
26 ?}
27 ?class CarShop{ // 定義汽車銷售店CarShop類
28 ? private double money = 0; // 定義銷售金額成員
29 ? public void sellCar(ICar car){ // 定義銷售汽車方法
30 ? System.out.println("車型:" + car.showName() + "價格:" + car.getPrice());
31 ? money += car.getPrice();
32 ? }
33
34 ? public double getMoney(){ // 定義獲取金額方法
35 ? return money;
36 ? }
37 ?}
38
39 ?public class Demo0706 { // 測試類
40 ? public static void main(String[] args) {
41 ? CarShop shop = new CarShop();
42 ? Haval haval = new Haval(); // Haval類的對象
43 ? shop.sellCar(haval);
44 ? GreatWall greatWall = new GreatWall(); // GreatWall類對象
45 ? shop.sellCar(greatWall);
46 ? System.out.println("銷售總收入:" + shop.getMoney());
47 ? }
48 ?}
程序的運行結果如下:
車型:哈佛SUV價格:150000.0
車型:長城汽車價格:68000.0
銷售總收入:218000.0
例7-6中,ICar接口定義了抽象方法showName()和getPrice(), Haval類和GreatWall類實現了ICar接口,汽車銷售店類CarShop針對實現ICar接口的實現類進行銷售并匯總金額。在測試類 Demo0705的main 方法中創建 CarShop類的實例shop、Haval類的實例haval、GreatWall類的greatWall,通過shop對象的sellCar()方法對汽車對象havel和greatWall銷售,并調用getMoney()方法統計銷售總收入。這樣,我們便通過ICar接口實現了多態,
7.2.7 抽象類和接口的比較
抽象類與接口是Java中對于抽象類定義進行支持的兩種機制,抽象類和接口都用于為對象定義共同的行為,二者比較如下:
? ?抽象類和接口都包含抽象方法。
? ?抽象類可以有非抽象方法,接口中如果要定義為非抽象方法,需要標注為接口默認方法。
? ?接口中只能有常量,不能有變量;抽象類中既可以有常量,也可以有變量。
? ?一個類可以實現多個接口,但只能繼承一個抽象類。
在程序設計時,應該根據具體業務需求來確定是使用抽象類還是接口。如果子類需要從父類繼承一些變量或繼承一些抽象方法、非抽象方法,可以考慮使用抽象類;如果一個類不需要繼承,只需要實現某些重要的抽象方法,可以考慮使用接口。
知識點撥:在面向接口編程的思想中,接口只用關心操作,但不用關心這些操作的具體實現細節,可以使開發者將主要精力用來程序設計。通過面向接口編程,可以降低類與類、類與接口、層與層之間的耦合度。當設計和實現分離的時候,面向接口編程是一種解決問題的很好方式。
7.3 內部類
大多數情況下,類被定義為一個獨立的程序單元。但在某些情況下,也可以將一個類定義在另一個類里面或者一個方法里面,這樣的類稱為內部類(也稱嵌套類),包含內部類的類也被稱為外部類。內部類包括4種:成員內部類、局部內部類、靜態內部類和匿名內部類。
一般情況下,內部類有如下幾個屬性:
? ?內部類和外部類由Java編譯器編譯后生成的兩個類是獨立的。
? ?內部類是外部類的一個成員,可以使用外部類的類變量和實例變量,也可以使用外部類的局部變量。
? ?內部類可以被protected或private修飾。當一個類中嵌套另一個類時,訪問保護并不妨礙內部類使用外部類的成員。
? ?內部類被static修飾后,不能再使用局部范圍中或其他內部類中的數據和變量。
7.3.1 成員內部類
成員內部類是最普通的內部類,它定義于另一個類的內部,與外部類的成員變量、成員方法同級。成員內部類可以訪問外部類的所有成員,外部類同樣可以其成員內部類的所有成員。但是,成員內部類是依附外部類而存在的,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象。
在外部類外創建一個內部類對象的語法格式如下:
外部類名.內部類名 引用變量名 = new 外部類名().new 內部類名()
接下來,通過案例來演示成員內部類的使用,如例7-7所示。
例7-7 Demo0707.java
1 ?package com.aaa.p070301;
2
3 ?class Outer { // 定義外部類
4 ? private String name = "外部類Outer";
5 ? private int num = 666;
6
7 ? class Inner{ // 定義內部類
8 ? private String name = "內部類Inner"; // 定義類成員
9 ? public void accessOuter()
10 ? {
11 ? System.out.println("在成員內部類中訪問內部類的name:" + name);
12 ? // Outer.this表示外部類對象
13 ? System.out.println("在成員內部類中訪問外部類的name:" + Outer.this.name);
14 ? System.out.println("在成員內部類中訪問外部類的num:" + num);
15 ? }
16 ? }
17 ?}
18
19 ?public class Demo0707 {
20 ? public static void main(String[] args) {
21 ? Outer.Inner inner = new Outer().new Inner();
22 ? inner.accessOuter();
23 ? }
24 ?}
程序的運行結果如下:
在成員內部類中訪問內部類的name:內部類Inner
在成員內部類中訪問外部類的name:外部類Outer
在成員內部類中訪問外部類的num:666
例7-7中,外部類Outer中定義了一個成員內部類Inner,在Inner類的成員方法accessOuter ()中訪問其自身的成員變量name以及其外部類Outer的成員變量name和num,由于內部類和外部類的成員變量name重名,所以不能直接訪問,只能用“Other.this.name”的形式進行訪問,其中“Outer.this”表示外部類對象,而num只存在于外部類,內部類可以直接訪問。
注意:成員內部類不能定義靜態變量、靜態方法和靜態內部類。
7.3.2 局部內部類
局部內部類是指在成員方法中定義的類,這種類是局部的,和局部變量類似,只能在該方法或條件的作用域內使用,超出這些作用域就無法引用。
局部內部類的優勢是,對于外部類完全隱藏,即使是包含他的外部類也是不可見的,是不能直接訪問的,只有在方法中才可以創建內部類的實例并訪問類中的方法和屬性。
局部內部類的特點如下:
? ?局部內部類不允許使用訪問權限修飾符(public、private、protected)
? ?局部內部類對外部完全隱藏,除了創建這個類的方法可以訪問它以外,其他地方均不能訪問。
接下來,通過案例來演示局部內部類的使用,如例7-8所示。
例7-8 Demo0708.java
1 ?package com.aaa.p070302;
2
3 ?class Outer{ // 定義外部類
4 ? private static String name = "外部類Outer";
5 ? private int num = 666;
6 ? public void display(){
7 ? int count = 5;
8 ? //局部內部類即嵌套在方法里面
9 ? class Inner{
10 ? public void accessOuter(){
11 ? System.out.println("在局部內部類中訪問外部方法的變量:" + (count));
12 ? System.out.println("在局部內部類中訪問外部類的name:" + Outer.name);
13 ? System.out.println("在局部內部類中訪問外部類的num:" + num);
14 ? }
15 ? }
16 ? //局部內部類,在方法內部調用
17 ? new Inner().accessOuter();
18 ? }
19 ?}
20 ?public class Demo0708 {
21 ? public static void main(String[] args) {
22 ? Outer outer = new Outer();
23 ? outer.dispaly();
24 ? }
25 ?}
程序的運行結果如下:
在局部內部類中訪問外部方法的變量:5
在局部內部類中訪問外部類的name:外部類Outer
在局部內部類中訪問外部類的num:666
例7-8中,外部類Outer的display()方法定義了一個內部類Inner,Inner類只能在display()方法中創建其實例對象并調用自身方法accessOuter(),該方法調用了外部類Outer的成員變量name和num以及display()方法內的局部變量count。從運行結果中發現,都可以正常輸出,但是如果把第11行代碼中的“count”后面加上“++”之后,會編譯報錯,因為局部變量是隨著方法的調用而調用,隨著調用結束而消失,但是我們調用局部內部類時創建的對象依舊在堆內存中,并沒有被回收,如果訪問的局部變量不是用final修飾的,當方法調用完畢后,依然存在堆內存中的對象就會出現找不到局部變量的問題,而被final修飾的變量可以看成是一個常量,存在于常量池中,不會被立刻回收。所以,針對局部內部類來說,它可以訪問方法中的局部變量但不能進行修改。
注意:JDK1.8之后,即使不加final修飾符,系統也會默認加上。
7.3.3 靜態內部類
靜態內部類是指用static關鍵字修飾的成員內部類。靜態內部類可以包含靜態成員和非靜態成員(實例成員),根據靜態成員不能訪問非靜態成員的規則,靜態內部類不能直接訪問外部類的非靜態成員,只能訪問外部類的靜態成員(即類成員)。創建靜態內部類對象的語法格式如下:
外部類名.內部類名 引用變量名 = new 外部類名.內部類名()
接下來,通過案例來演示靜態內部類的使用,如例7-9所示。
例7-9 Demo0709.java
1 ?Package com.aaa.p070303;
2
3 ?class Outer{
4 ? private static String name = "外部類Outer"; // 定義類靜態成員
5 ? private static int num = 666;
6 ? static class Inner { // 定義靜態內部類
7 ? public static String name = "內部類Inner"; // 定義類靜態成員
8 ? public void accessOuter() {
9 ? // 靜態內部類成員方法中訪問外部類私有成員變量
10 ? System.out.println("在靜態內部類中訪問外部類的name:"+Outer.name);
11 ? System.out.println("在靜態內部類中訪問外部類的num:" + num);
12 ? }
13 ? }
14 ?}
15 ?public class Demo0709 {
16 ? public static void main(String[] args) {
17 ? System.out.println("靜態內部類:" + Outer.Inner.name);
18 ? Outer.Inner obj = new Outer.Inner(); // 創建靜態內部類對象
19 ? obj. accessOuter();
20
21 ? }
22 ?}
程序的運行結果如下所示。
靜態內部類:內部類Inner
在靜態內部類中訪問外部類的name:外部類Outer
在靜態內部類中訪問外部類的num:666
例7-9中,Outer外部類中定義了一個靜態內部類Inner,Inner類包含了靜態成員變量name和成員方法accessOuter ()。訪問靜態內部類的靜態成員變量,可以使用“外部類名.靜態內部類名.靜態成員變量”的形式;訪問靜態內部類的實例成員,則要先創建靜態內部類對象,通過“new 外部類名.靜態內部類名()”的形式訪問。如果將第5行代碼的“static”去掉,則在第11行代碼調用的時候會報錯,因為num屬于外部類的非靜態變量,不可以被其靜態內部類直接訪問。
注意:靜態內部類不需要依賴外部類就可以直接創建;靜態內部類不可以使用任何外部類的非static成員(包括變量和方法)。
7.3.4 匿名內部類
匿名內部類是一個沒有顯式名字的內部類。本質上看,它會隱式地繼承一個類或者實現一個接口。換句話說,匿名內部類是一個繼承了某個類或者實現了某接口的子類匿名對象。創建匿名內部類的語法格式如下:
new 類名/接口名/抽象類名(){
… // 匿名內部類實現部分
}
匿名內部類具有局部內部類的所有特點,同時它還具有如下特點:
? ?匿名內部類必須繼承一個類或者實現一個接口,類名前面不能有修飾符。
? ?匿名內部類沒有類名,因此沒有構造方法。
? ?匿名內部類創建之后只能使用一次,不能重復使用。
匿名內部類是我們平時編寫代碼時用得比較多的內部類,在編寫事件監聽的代碼時使用匿名內部類不但可簡化程序,而且可使代碼更加容易維護。
接下來,通過案例來演示匿名內部類的使用,如例7-10所示。
例7-10 Demo0710.java
1 ?package com.aaa.p070304;
2
3 ?interface Inner { // 定義接口
4 ? void getName(String name);
5 ?}
6 ?public class Demo0710 {
7 ? public static void main(String[] args) {
8 ? new Inner(){ // 定義匿名類,并實現Inner接口
9 ? public void getName(String name) { // 重寫getName()方法
10 ? System.out.println("我是匿名類的方法,獲取name為:" + name);
11 ? }
12 ? }.getName("張三");
13 ? }
14 ?}
程序的運行結果如下:
我是匿名類的方法,獲取name為:張三
例7-10中,在外部類Demo0710的main方法中創建了匿名內部類“Inner的對象”,并調用該類的成員方法getName (),傳入參數“張三”,在創建Inner對象的時候,并沒有給對象賦予名稱,即“匿名”之意。
想一想:使用匿名內部類的優點和缺點有哪些?匿名內部類的使用場景有哪些?
7.4 本章小結
? ?抽象類的主要作用是建立對象的抽象模型,定義通用的方法,起到類似“模板”的作用。抽象類中包含一般方法和抽象方法。抽象方法是沒有方法體的方法,由抽象類的子類來定義實現。抽象類不能直接產生對象。包含抽象方法的類必須是抽象類,但抽象類可以不包含抽象的方法。
? ?接口只規定了一個類的基本形式,不涉及任何實現細節。通過接口來創建類,稱為接口的實現,該類為接口的實現類。Java語言中不允許類的多重繼承,但可以通過接口實現多重繼承。
? ?Java支持在一個類A中聲明一個類B,這樣的類B稱為內部類,類A稱為內部類的外部類。內部類可以分為成員內部類、局部內部類、靜態內部類、匿名內部類。內部類隱藏可以不想讓用戶知道的操作,極高的封裝性。內部類對象可以訪問創建它的外部類對象的內容,為開發者在設計時提供了更多的思路和捷徑。匿名內部類一般用在寫事件監聽的代碼時候,可以簡化代碼并使代碼容易維護。
7.5 理論習題與實踐練習
1.填空題
1.1 Java中使用____________關鍵字,來表示抽象的意思。
1.2 Java中使用____________關鍵字,來實現接口的繼承。
1.3 __________是定義在類中的類,主要作用是將邏輯上相關的類放在一起。
2.選擇題
2.1 以下關于Java語言抽象類的說法正確的是( )
A.抽象類可以直接實例化? B.抽象類不能有子類
C.抽象類可以多繼承? D.抽象類可以有非抽象方法
2.2 內部類不包括()
A.成員內部類? B.靜態內部類? C.匿名內部類? D.超級內部類
2.3 下列選項中,用于定義接口的關鍵字是( )
A.interface B.implements C.abstract D.class
2.4 下列選項中,接口使用時不可以加的關鍵字是( )
A.public B.static C.private D.final
2.5 下列選項中,可以實現類的多重繼承?的是( )
A.類 B.基本類型 C.抽象類? D.接口
3.思考題
3.1 請描述什么是抽象類?
3.2 請描述如何使用接口實現多態?
3.3 請描述向上轉型和向下轉型的區別?
3.4 什么是接口?請簡述抽象類和接口的區別?
3.5 ?采用內部類的好處是什么?
4.編程題
4.1 陸地上最常用的交通工具是汽車,海上最常用的交通工具是輪船,空中最常用的交通工具是飛機。現在要求實現一種海陸空三棲航行的交通工具。可以通過接口實現需求,實現思路參考如下:
? ?創建汽車接口Motor,定義一個run (方法。
? ?創建輪船接口Steamer,定義一個sailing ( )方法。
? ?創建飛機接口P1ane,定義一個f1y (方法。
? ?創建類SuperVehic 1e實現上述三個接口。
? ?創建測試類進行測試。
4.2 模擬攝影師照相的過程,要求攝影師可以通過相機拍照也可以通過其他的設備照相,譬如:手機:攝影師可以拍人像、風景等任何物體。要求該系統具備良好的可維護性可擴展性。
? ?攝影師可以對任何物體拍照,并沒有局限于一個具體類別,所以先設計一個接口。
? ?創建一個拍照接口,實現該接口的類都可以拍照,手機、相機、iPad等等。
? ?創建攝影師類,實現攝影方法。
? ?創建接口的實現類。
? ?創建測試類,運行系統。
Java
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。