JAVA編程講義.抽象類、接口和內部類

      網友投稿 761 2025-03-31

      前面我們學習了類、對象、封裝、繼承、多態等面向對象編程的基本概念,初步了解了面向對象程序設計理念,接下來我們繼續學習面向對象編程的一些重要概念:抽象類、接口和內部類。其中,抽象類是從多個具體類中抽象出來的父類,可以作為子類的模板,實現更加豐富的類繼承;接口是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

      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小時內刪除侵權內容。

      上一篇:Excel CEILING函數舍入為最接通近的整數或基數的使用教程
      下一篇:如何將郵政編碼轉換為Excel中的狀態?
      相關文章
      亚洲日韩在线中文字幕综合| 亚洲Av无码一区二区二三区| 亚洲高清乱码午夜电影网| 久久精品国产99国产精品亚洲| 亚洲欧洲日产国码久在线观看| 亚洲人成伊人成综合网久久久| 中文字幕精品亚洲无线码一区 | 亚洲理论片在线观看| 亚洲黄色免费在线观看| 久久久久亚洲精品无码系列| 亚洲AV综合色一区二区三区| 亚洲AV永久无码精品水牛影视| 亚洲中文字幕无码一区| 亚洲成AV人在线观看天堂无码| 亚洲精品色午夜无码专区日韩| 亚洲国产另类久久久精品小说| 久久精品国产亚洲AV麻豆王友容| 亚洲国产精品无码专区影院 | 亚洲精品国产高清在线观看| 亚洲丰满熟女一区二区哦| 国产亚洲欧美日韩亚洲中文色| 亚洲Av永久无码精品黑人| 午夜亚洲国产成人不卡在线| 国产成人毛片亚洲精品| 亚洲欧洲日产国码无码网站| 久久91亚洲精品中文字幕| 亚洲色成人网一二三区| 亚洲一区二区久久| 亚洲人成色777777精品| mm1313亚洲精品国产| 怡红院亚洲怡红院首页| 久久亚洲精品无码| 亚洲欧洲日本精品| 91在线亚洲综合在线| 国产亚洲精品AAAA片APP| 久久国产成人亚洲精品影院| 亚洲AV综合色区无码一区| 亚洲美女在线观看播放| 亚洲一区二区三区成人网站| 成人亚洲综合天堂| 日韩va亚洲va欧洲va国产|