全面掌握 Java 內部類

一直以來以為自己對 Java 基礎甚是清楚,然而面試時卻連內部類和靜態內部類的區別都無法回答圓滿,so~重新學習一遍,徹底掌握內部類。

內部類是一種非常有用的特性,它可以把一些邏輯相關的類組織在一起,并控制位于內部的類的可視性,下文中內部類均指非靜態內部類。

內部類的學習分為以下10個知識點:

1.創建內部類與連接外部類 
2.內部類與向上轉型 
3.局部內部類 
4.匿名內部類 
5.局部內部類和匿名內部類 
6.嵌套類 
7.內部類的繼承 
8.內部類可以被覆蓋嗎 
9.為什么需要內部類 
10.內部類標識符

香港6合采免费公开透码:1、創建內部類與連接外部類

 香港彩票透码 www.kptln.icu public class Car {    private int speed = 100;

    class Tyre {        int getSpeed() { //訪問外部類成員
            return speed;
        }
        Car getCar() { //通過.this獲取外部類對象
            return Car.this;
        }
    }    public static void main(String[] args) {
        Car car = new Car();
        Car.Tyre tyre = car.new Tyre();
        tyre.getSpeed();
        System.out.println(car == tyre.getCar()); // true
    }
}1234567891011121314151617181912345678910111213141516171819

要想創建內部類的對象,必須使用外部類的對象,如上,且必須地具體指明這個對象的類型:OuterClassName.InnerClassName,然后通過 OuterClassInstance.new InnerClassName() 來創建內部類對象。

內部類對象會暗暗連接到創建它的外部類對象上,捕獲外部類的對象引用,然后在內部類中訪問此外部類的成員時,就是通過這個引用來訪問的,并且擁有其外部類的所有元素的訪問權。正是由于此原因,Android 中非靜態內部類創建靜態實例才會造成內存泄漏。

在內部類中通過 OuterClassName.this 可獲取外部類對象,如上代碼中通過 Car.this 獲取 Car 類對象。

2、內部類與向上轉型

當將內部類向上轉型為基類,尤其是轉型為一個接口的時候,此內部類能夠隱藏這個接口的實現,如下例:

interface cost{
    double getPrice();
}123123
public class Car {
    private int speed = 100;    class Tyre implements cost{
        double originalPrice = 100;

        @Override        public double getPrice() {            return originalPrice * 0.75;
        }
    }    public static void main(String[] args) {
        Car car = new Car();
        cost tyre = car.new Tyre(); //向上轉型
        tyre.getPrice();  // 75.0
    }
}123456789101112131415161718123456789101112131415161718

3、局部內部類

前面提到的都是處于外部類中的內部類,而內部類也可以定義在一個方法里面或者在任意的作用域中,這么做有以下兩個理由:

1.如實現某接口的內部類,可以在方法中創建并返回對其的引用

2.要解決一個復雜的問題,需要創建一個類來輔助,但不希望這個類是公共可用的

例1:在方法中定義內部類:

public class Car {
    private int speed = 100;

    cost getTyre() { //方法內部
        class Tyre implements cost {
            double originalPrice = 100;
            @Override            public double getPrice() { //打折操作
                return originalPrice * 0.75;
            }
        }        return new Tyre();
    }    public static void main(String[] args) {
        Car car = new Car();
        cost tyre = car.getTyre();
        tyre.getPrice();  // 75.0
    }
}12345678910111213141516171819201234567891011121314151617181920

例2:在任意作用域中定義內部類:

public class Car {
    private boolean hadTyre = true; //是否有輪胎

    cost getTyre() {        if (hadTyre) { //任意作用域中
            class Tyre implements cost {
                double originalPrice = 100;

                @Override                public double getPrice() { //打折操作
                    return originalPrice * 0.75;
                }
            }            return new Tyre();
        }else{            return null;
        }
    }    public static void main(String[] args) {
        Car car = new Car();                 
        cost tyre = car.getTyre();
        tyre.getPrice();  // 75.0
    }
}1234567891011121314151617181920212223242512345678910111213141516171819202122232425

定義在一個方法里面或者在任意的作用域中的內部類也叫作局部內部類,局部內部類不能有 private 等訪問說明符,因為它不是外部類的一部分,但是它可以訪問當前代碼塊內的常量以及外部類中的所有成員:

public class Car {
    private boolean hadTyre = true;    private double coupon1 = 10; // 外部類成員(10元優惠券)

    cost getTyre() {        if (hadTyre) {            double coupon2 = 5; // 代碼塊內常量(5元優惠券)
            class Tyre implements cost {
                double originalPrice = 100;
                @Override                public double getPrice() {                    return originalPrice * 0.75 - coupon1 - coupon2;
                }
            }            return new Tyre();
        } else {            return null;
        }
    }    public static void main(String[] args) {
        Car car = new Car();
        cost tyre = car.getTyre();
        System.out.print(tyre.getPrice());  // 60.0
    }
}12345678910111213141516171819202122232425261234567891011121314151617181920212223242526

書中說可以訪問當前代碼塊內的常量,這里有點不解,coupon2 是常量?

4、匿名內部類

上例中 getTyre() 方法要創建一個 cost 對象,從 cost tyre = car.getTyre(); 看出我們并不關心內部類的名字 Tyre,只要返回的是 cost 類對象就足夠了,所以這里可以用匿名內部類來實現,顧名思義,它沒有名字,如下例:

public class Car {
    cost getTyre(double p) {        return new cost() {            double coupon = 10; // 10元優惠券
            private double price = p; //原價
            @Override            public double getPrice() {                return price - coupon;
            }
        };
    }    public static void main(String[] args) {
        Car car = new Car();
        cost tyre = car.getTyre(100);
        System.out.print(tyre.getPrice());  // 90.0
    }
}12345678910111213141516171234567891011121314151617

你可能會有疑問,這段代碼可以編譯通過嗎?應該是 getTyre(final double p) 吧?確實,在匿名內部類中使用一個在其外部定義的對象,那么編譯器必須要求其參數引用是 final 類型,以上代碼在低于 Java 8 的版本編譯不會通過,但是在 Java 8 版本不用 final 修飾局部變量也可以編譯通過,只不過不能修改值,只能打印輸出或賦值給其他變量。

5、局部內部類和匿名內部類

首先來看局部內部類和匿名內部類的對比實現:

public class Car {
    private double coupon2 = 5;
    cost getTyre1(double p) { //局部內部類
        class Tyre implements cost {
            double coupon = 10;            private double price = p;

            @Override            public double getPrice() {                return price - coupon - coupon2;
            }
        }        return new Tyre();
    }
    cost getTyre2(double p) { //匿名內部類
        return new cost() {            double coupon = 10;            private double price = p;

            @Override            public double getPrice() {                return price - coupon - coupon2;
            }
        };
    }
}12345678910111213141516171819202122232425261234567891011121314151617181920212223242526

getTyre() 方法用來創建一個 cost 類對象,我們分別使用局部內部類和匿名內部類實現了這個功能,它們具有相同的行為和能力,既然局部內部類的名字在方法外是不可見的,那為什么我們仍然使用局部內部類而不是匿名內部類呢?唯一的理由是:我們需要一個已命名的構造器,或者需要重載狗仔器,而匿名內部類只能用于實例初始化,所以使用局部內部類而不使用匿名內部類的另一個理由就是:需要不止一個該內部類的對象。

6、嵌套類

如果不需要內部類對象與其外部類對象之間有聯系,那么可以將內部類聲明為 static,即靜態內部類,也稱嵌套類,靜態內部類和非靜態內部類的最大區別就是非靜態內部類對象隱士的保存了一個外部類對象的引用,這意味著:

1.不需要外部類的對象就可以創建靜態內部類的對象

2.不能從靜態內部類的對象中訪問非靜態的外部類對象

3.靜態內部類中可以定義靜態或者非靜態的成員,而非靜態內部類則不能有靜態成員。

public class Car {
    private static double price = 100; 
    static class Tyre implements cost {
        private static double coupon = 5; // 3.若為非靜態內部類則無法定義為 static 類型
        @Override        public double getPrice() {            return price; // 2.若 price 不為 static 則無法訪問
        }
    }    public static void main(String[] args) {
        Car.Tyre tyre = new Car.Tyre(); // 1.靜態內部類的創建不依賴外部類對象
    }
}1234567891011121312345678910111213

這也是靜態內部類和內部類的關鍵區別。此外靜態內部類也可定義在接口內部:

interface cost {
    class Price { } // 默認為 public 、static}12341234

如果你想要創建某些公共代碼,使得它們可以被某個接口的所有不同實現所公用,那么使用接口內部嵌套類會顯得很方便。

7、內部類的繼承

怎么繼承自一個內部類?內部類的構造必須依賴其外部類對象,所以在繼承內部類的時候,事情會變得復雜,比如我們要繼承自 Size 類:

class Tyre{
    class Size{}}123123

可以這樣寫:

public class TyreSize extends Tyre.Size{}123123

編譯器會報錯: No enclosing instance of type ‘com.example.Tyre’ is in scope ,即缺少 Tyre 類的實例,若要創建位于 Tyre 內部的 Size 類,則必須要有 Tyre 的實例對象,要解決這個問題,需要引入 Tyre 實例且說清它們之間的關聯:

public class TyreSize extends Tyre.Size{
    TyreSize(Tyre tyre) {
        tyre.super();
    }
}1234512345

編譯通過。

8、內部類可以被覆蓋嗎

如果創建了一個內部類,然后繼承其外部類并重新定義此內部類時,內部類可以被覆蓋嗎?例如:

class Car {
    Car() {
        System.out.println("new Car()");        new Tyre();
    }    class Tyre { // 我會被覆蓋嗎
        Tyre() { System.out.println("new Tyre()"); }
    }
}public class BigCar extends Car {
    class Tyre {
        Tyre() {System.out.println("BigCar new Tyre()"); }
    }    public static void main(String[] args) {        new BigCar();
    }
}1234567891011121314151617181912345678910111213141516171819

在 Car 的構造器中新建的 Tyre 是 Car 中的 Tyre 還是 BigCar 中的 Tyre 呢?運行程序輸出:

new Car() 
new Tyre()

BigCar 中定義的 Tyre 內部類并沒有覆蓋 Car 中的 Tyre 內部類,實際上這兩個內部類是完全獨立的兩個實體,各自在自己的命名空間內,沒有誰覆蓋誰之說。

9、為什么需要內部類

也就是說,內部類存在的意義是什么呢?為什么 Sun 公司如此費心地增加這項語言特性呢?這里將內部類的意義總結為以下四點:

A. 邏輯上被包含且對外隱藏

如果一個類在邏輯上被包含于另一個類,那么將此類設置為另一個類的內部類,比如輪胎類可以寫作汽車類的內部類:

public class Car {    public class Tyre{}
}123123

上面代碼中只存在被包含關系,也可通過組合方式實現,寫作內部類是沒有必要的,當想對外保密汽車使用何種輪胎時,寫作內部類才是有必要的:

public class Car {    private class Tyre{}
}123123

B. 實現多重繼承

每個內部類都能獨立的繼承一個接口,無論外部類是否已經繼承了某個接口的實現,對于內部類都沒有影響。內部類提供可以繼承多個抽象類或具體的類的能力,有效的實現了多重繼承,網上一個實例簡單直觀的展示了通過內部類實現多重繼承(兒子利用多重繼承來繼承父親和母親的優良基因):

public class Father { //父親
    public int strong(){        return 9;
    }
}public class Mother { //母親
    public int kind(){        return 8;
    }
}12345678910111234567891011
public class Son { // 兒子通過內部類實現多重繼承

    class Father_1 extends Father{
        public int strong(){            return super.strong() + 1;
        }
    }    class Mother_1 extends  Mother{
        public int kind(){            return super.kind() - 2;
        }
    }    public int getStrong(){        return new Father_1().strong();
    }    public int getKind(){        return new Mother_1().kind();
    }
}1234567891011121314151617181920212212345678910111213141516171819202122

C. 閉包與回調

閉包是一個可調用的對象,它記錄了一些信息,這些信息來自于創建它的作用域。通過這個定義,可以看出內部類是面向對象的閉包,因為它不僅包含外部類對象(創建內部類的作用域)的信息,還自動擁有一個指向此外部類對象的引用,在此作用域內,內部類有權操作所有的成員,包括private成員。

通過內部類提供閉包功能比指針更靈活、更安全

例如:一個接口程序員和一個基類作家都有一個相同的方法work,相同的方法名,但是其含義完全不同,這時候就需要閉包。

class Writer { //作家基類
  void work(){}
}interface programmer{ //程序員接口
  void work();
}123456123456

閉包實現代碼如下:

public class WriterProgrammer extends Writer {
  @Override  public void work(){      //寫作
  }  public void code(){      //寫代碼
  }  class ProgrammerInner implements programmer{
      @Override      public void work(){
          code();
      }
  }
}123456789101112131415123456789101112131415

WriterProgrammer 繼承自 Writer , 直接實現父類作家的work()方法,然后使用內部類實現程序員的work()方法回調code()方法。如果WriterProgrammer 同時繼承自 Writer 且實現 programmer 接口,那么就不能同時實現作家和程序員的意義不同的 work()方法:

class WriterProgrammer extends Writer implements programmer{
       @Override       public void work() { //programmer的 work

       }
   }123456123456

D. 控制框架

應用程序框架就是被設計用以解決某類特定問題的一個類或一組類,而控制框架就是一類特殊的應用程序框架,它用來解決響應事件的需求,主要用來響應事件的系統被稱作事件驅動系統。 Java Swing 庫就是一個控制框架,它優雅的解決了 GUI 的問題,并使用了大量的內部類

控制框架的完整實現是由單個類創建的,內部類用來表示解決問題所必須的各種不同的 action,另外內部類能夠很容易的訪問外部類的任意成員,可以讓這種實現更輕松。

例如控制溫室的運作:控制燈光、水、溫度調節器的開關等,每個行為都是完全不同的,使用內部類可以在單一的類中產生對同一個基類 Event 的多種導出版本,對于溫室系統的每一種行為,都繼承一個新的 Event 內部類,并在要實現的 action() 中編寫控制代碼:

public class GreenhouseControls{

    public class LightOn extends Event {
        public void action() {            //開燈...
        }
    }    public class LightOff extends Event {
        public void action() {            //關燈...
        }
    }    public class WaterOn extends Event {
        public void action() {            //開水閘...
        }
    }    // 其他操作...}12345678910111213141516171819202122231234567891011121314151617181920212223

10、內部類標識符

每個類都會產生一個.class文件,其中包含了如何創建該類型的對象的全部信息,內部類也必須生成一個 .class 文件,從而可以包含它自己的 Class 對象信息,這些類文件的命名有嚴格的規則:外部類名字+“$”+ 內部類名字,例如:

class Car {
    class Tyre {}}123123

生成的 .class 文件包括:

Car.class 
Car$Tyre.class

如果內部類是匿名的,編譯器會簡單的產生一個數字作為其標識符,如果內部類是嵌套在別的內部類之中,只需直接將它們的名字加在其外部類標識符與”$”后面,這是 Java 的標準命名方式,產生的文件自動都是平臺無關的。

總結

本文主要參考 Java 編程思想書中對內部類部分的講解思路,將各部分知識點轉換為自以為更易理解的例子~嗯~總算較為全面的理解內部類了~以后應該不會怕啦hhh~

來源:csdn

上一篇: 如何學習Javascript

下一篇: 關于Java應用相關不同產品的架構

分享到: 更多
藏分真的能出款吗 重庆时时开奖直播现场 黑龙江时时组选图表 赛车北京pk10官方网站 大乐透复式中奖计算表 时时彩计划群稳赚平台 极速快三大小单双经验 手机炸金花怎么才能赢 体球网即时比分手机版 pk10人工计划群 中福快三计划软件下载 经典麻将单机版手机 重庆时时彩后一技巧 双色球怎么算中奖 七星彩单双头尾规律 通比牛牛口诀