首頁 > Java > java教程 > 主體

Java 內部類別的詳細介紹

黄舟
發布: 2017-03-14 11:51:30
原創
1195 人瀏覽過


摘要

#  多重繼承指的是一個類別可以同時從多於一個的父類別那裡繼承行為和特徵,然而我們知道Java為了保證資料安全性,它只允許單繼承。但有時候,我們確實是需要實現多重繼承,而且現實生活中也真正地存在這樣的情況,例如遺傳:我們即繼承了父親的行為和特徵也繼承了母親的行為和特徵。可幸的是,Java 提供了兩種方式讓我們曲折地實現多重繼承:介面和內部類別。 事實上,實作多重繼承是內部類別的一個極為重要的應用。 除此之外,內部類別還可以很好的實作隱藏(例如,私有成員內部類別)。 內部類別共有四種類型,分別是成員內部類別、靜態內部類別、局部內部類別和匿名內部類別。特別地,

  • 成員內部類別:成員內部類別是外圍類別的成員,是 依附在外圍類別的 ,所以,只有先創建了外圍類別物件才能夠建立內部類別物件。也正是因為這個原因,成員內部類別也不能含有static變數和方法;

  • ## 靜態內部類:靜態內部類,就是修飾為static 的內部類,該內部類對象 不依賴外部類對象,就是說我們可以直接創建內部類對象,但其只可以直接存取外部類別的所有靜態成員和靜態方法;

  • #局部內部類別:局部內部類別和成員內部類別一樣被編譯,只是它的 作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效;

  • 匿名內部類別:定義匿名內部類別的前提是,內部類別必須要繼承一個類別或實作接口,格式為new 父類別或介面(){定義子類別的內容(如函數等)}。 也就是說,匿名內部類別最終提供給我們的是一個匿名子類別的物件。

一. 內部類別概述

1、、內部類別基礎

  

內部類別指的是在一個類別的內部所定義的類,類別名稱不需要和原始檔名相同。 內部類別是一個編譯時的概念,一旦編譯成功,內部類別和外部類別就會變成兩個完全不同的類別。 例如,對於一個名為Outer 的外部類別和在其內部定義的名為Inner 的內部類,在編譯完成後,會出現Outer.class 和Outer$inner. class 兩個類別。 因此,內部類別的成員變數/方法名稱可以和外部類別的相同。 內部類別可以是靜態static的,也可用 public,default,protected 和 private 修飾。 特別地,關於Java來源檔案名稱與類別名稱的關係( java來源檔案名稱的命名與內部類別無關,以下3條規則中所涉及的類別和介面均指的是外部類別/接口),需要符合下面三條規則:

  • #如果java來源檔案包含public類別(public介面),則來源檔案名稱必須與public類別名稱(public介面名稱)相同。   一個java來源檔案中,如果有public類別或public接口,那麼就只能有一個public類別或一個public接口,不能有多個public的類別或接口。當然,一個java來源檔案中可以有多個套件可見的類別或接口,即預設存取權限修飾符(類別名稱前沒有存取權限修飾符)。 public類別(介面) 與 套件可見的類別(介面)在檔案中的順序可以隨意,即public類別(介面)可以不在第一個的位置。

  • 如果java來源檔案不包含public類別(public介面),則java來源檔案名稱沒有限制。
      只要符合檔案名稱的命名規格就可以,可以不與檔案中任一個類別或介面同名,當然,也可以與其中之一同名。

  • 類別和介面的命名不能衝突。
      同一個套件中的任何一個類別或介面的命名都不能相同。不同包中的類別或介面的命名可以相同,因為透過包可以把它們區分開來。


2、 內部類別的作用

使用內部類別可以帶給我們以下優點:

  • #內部類別可以很好的實作隱藏(一般的非內部類,是不允許有private 與protected 權限的,但內部類別可以);

  • 內部類別擁有外圍類別的所有元素的存取權限;

  • 可以實現多重繼承;

  • 可以避免修改介面而實作同一個類別中兩種同名方法的呼叫。


1)內部類別可以很好的實作隱藏

  平時我們對類別的存取權限,都是透過類別前面的存取修飾符來限制的,一般的非內部類,是不允許有private 與protected 權限的,但內部類別可以,所以我們能透過內部類別來隱藏我們的資訊。可以看下面的例子:

//测试接口public interface InterfaceTest {
    public void test();
}//外部类public class Example {

    //内部类
    private class InnerClass implements InterfaceTest{
        @Override
        public void test() {
            System.out.println("I am Rico.");
        }
    }    //外部类方法
    public InterfaceTest getInnerInstance(){        return new InnerClass();
    }
}//客户端public class Client {
    public static void main(String[] args) {
        Example ex = new Example();
        InterfaceTest test = ex.getInnerInstance();
        test.test();
    }
}/* Output:
        I am Rico.
 *///:~
登入後複製

  對客戶端而言,我們可以透過Example 的getInnerInstance()方法得到一個InterfaceTest 實例,但我們並不知道這個實例是如何實現的,也感受不到對應的具體實現類別的存在。由於 InnerClass 是 private 的,所以,我們如果不看原始碼的話,連實作這個介面的具體類別的名字都看不到,所以說內部類別可以很好的實作隱藏。


2)內部類別擁有外圍類別的所有元素的存取權限

//外部类public class Example {
    private String name = "example";    //内部类
    private class Inner{
        public Inner(){
            System.out.println(name);   // 访问外部类的私有属性
        }
    }    //外部类方法
    public Inner getInnerInstance() {        return new Inner();
    }
}//客户端public class Client {
    public static void main(String[] args) {
        Example ex = new Example();
        ex.getInnerInstance();
    }
}/* Output:
        example
 *///:~
登入後複製

  name 這個成員變數是在Example裡面定義的私有變量,這個變數在內部類中可以無條件地存取。

  

3)可以實現多重繼承

  對多重繼承而言,可以這樣說,介面只是解決了部分問題,而內部類別使得多重繼承的解決方案變得更加完整。內部類別使得Java的繼承機制更加完善,是內部類別存在的最大理由。 Java中的類別只能繼承一個類別,它的多重繼承在我們沒有學習內部類別之前是用介面來實現的。但使用介面有時候有很多不方便的地方,例如,我們實作一個介面就必須實作它裡面的所有方法;而內部類別可以使我們的類別繼承多個具體類別或抽象類別,規避接口的限制性。 看下面的範例:

//父类Example1public class Example1 {
    public String name() {        return "rico";
    }
}//父类Example2public class Example2 {
    public int age() {        return 25;
    }
}//实现多重继承的效果public class MainExample {

    //内部类Test1继承类Example1
    private class Test1 extends Example1 {
        public String name() {            return super.name();
        }
    }    //内部类Test2继承类Example2
    private class Test2 extends Example2 {
        public int age() {            return super.age();
        }
    }    public String name() {        return new Test1().name();
    }    public int age() {        return new Test2().age();
    }    public static void main(String args[]) {
        MainExample mexam = new MainExample();
        System.out.println("姓名:" + mexam.name());
        System.out.println("年龄:" + mexam.age());
    }
}/* Output:
        姓名:rico
        年龄:25
 *///:~
登入後複製

  注意到類別 MainExample,在這個類別中,包含兩個內部類別 Test1 和 Test2。其中,類別Test1繼承了類別Example1,類別Test2繼承了類別Example2。這樣,類別MainExample 就擁有了 類別Example1 和 類別Example2 的方法,也就間接地實作了多繼承。


4) 避免修改介面而實作同一個類別中兩種同名方法的呼叫

  考慮這樣一種情形,一個類別要繼承一個類,還要實作一個接口,可是它所繼承的類別和介面裡面有兩個相同的方法(方法簽名一致),那我們該怎麼區分它們呢?這就需要使用內部類別了。例如,

//Test 所实现的接口public interface InterfaceTest {
    public void test();
}//Test 所实现的类public class MyTest {
    public void test(){
        System.out.println("MyTest");
    }
}//不使用内部类的情形public class Test extends MyTest implements InterfaceTest{
    public void test(){
        System.out.println("Test");
    }
}
登入後複製

  此時,Test中的 test() 方法是屬於覆寫 MyTest 的 test() 方法呢,還是實作 InterfaceTest 中的 test() 方法呢?我們怎麼能調到 MyTest 這裡的方法?顯然這是不好區分的。而我們如果用內部類別就很好解決這個問題了。看下面程式碼:

//Test 所实现的接口public interface InterfaceTest {
    public void test();
}//Test 所实现的类public class MyTest {
    public void test(){
        System.out.println("MyTest");
    }
}//使用内部类的情形public class AnotherTest extends MyTest {

    private class InnerTest implements InterfaceTest {
        @Override
        public void test() {
            System.out.println("InterfaceTest");
        }
    }    public InterfaceTest getCallbackReference() {        return new InnerTest();
    }    public static void main(String[] args) {
        AnotherTest aTest = new AnotherTest();
        aTest.test(); // 调用类MyTest 的 test() 方法
        aTest.getCallbackReference().test(); // 调用InterfaceTest接口中的 test() 方法
    }
}
登入後複製

  透過使用內部類別來實作接口,就不會與外圍類別所繼承的同名方法衝突了。


3、 內部類別的種類

  在Java中,內部類別的使用共有兩種情況:

  (1) 在類別中定義一個類別(成員內部類,靜態內部類);
  (2) 在方法中定義一個類(局部內部類,匿名內部類)。


二.成員內部類別

1、定義與原則

#  成員內部類別是最普通的內部類,它是外圍類別的一個成員,在實際使用中,一般將其可見性設為private。 成員內部類別是依附於外圍類別的,所以,只有先建立了外圍類別物件才能夠建立內部類別物件。也正是因為這個原因,成員內部類別也不能含有 static 的變數與方法,請看下面範例:

public class Outter {
    private class Inner {

        private final static int x=1;   // OK

        /* compile errors for below declaration
        * "The field x cannot be declared static in a non-static inner type, 
        * unless initialized with a constant expression" */
        final static Inner a = new Inner();     // Error  

        static Inner a1=new Inner();     // Error  

        static int y;     // Error  
    }
}
登入後複製

  如果上面的代码编译无误, 那么我们就可以直接通过 Outter.Inner.a 拿到内部类Inner的实例。 由于内部类的实例一定要绑定到一个外部类的实例的,所以矛盾。因此,成员内部类不能含有 static 变量/方法。此外,成员内部类与 static 的关系还包括:

  • 包含 static final 域,但该域的初始化必须是一个常量表达式

  • 内部类可以继承含有static成员的类。


2、交互

 成员内部类与外部类的交互关系为:

  • 成员内部类可以直接访问外部类的所有成员和方法,即使是 private 的;

  • 外部类需要通过内部类的对象访问内部类的所有成员变量/方法。

//外部类class Out {    private int age = 12;    private String name = "rico"; 

    //内部类
    class In {        private String name = "livia"; 
        public void print() {
            String name = "tom"; 
            System.out.println(age);
            System.out.println(Out.this.name);
            System.out.println(this.name);
            System.out.println(name);
        }
    }    // 推荐使用getxxx()来获取成员内部类的对象 
    public In getInnerClass(){        return new In();
    }
}public class Demo {
    public static void main(String[] args) {

        Out.In in = new Out().new In();   // 片段 1
        in.print();        //或者采用注释内两种方式访问
        /* 
         * 片段 2
        Out out = new Out();

        out.getInnerClass().print();  // 推荐使用外部类getxxx()获取成员内部类对象 

        Out.In in = out.new In();
        in.print();

        */
    }
}/* Output:
        12
        rico
        livia
        tom
 *///:~
登入後複製

 对于代码片段 1和2,可以用来生成内部类的对象,这种方法存在两个小知识点需要注意:

1) 开头的 Out 是为了标明需要生成的内部类对象在哪个外部类当中;
2) 必须先有外部类的对象才能生成内部类的对象。


因此,成员内部类,外部类和客户端之间的交互关系为:

  • 在成员内部类使用外部类对象时,使用 outer.this 来表示外部类对象;

  • 在外部类中使用内部类对象时,需要先进行创建内部类对象;

  • 在客户端创建内部类对象时,需要先创建外部类对象。

特别地,对于成员内部类对象的获取,外部类一般应提供相应的 getxxx() 方法。


3、私有成员内部类

  如果一个成员内部类只希望被外部类操作,那么可以使用 private 将其声明私有内部类。例如,

class Out {    private int age = 12;    private class In {
        public void print() {
            System.out.println(age);
        }
    }    public void outPrint() {        new In().print();
    }
}public class Demo {
    public static void main(String[] args) {        /*
        * 此方法无效
        Out.In in = new Out().new In();
        in.print();
        */

        Out out = new Out();
        out.outPrint();
    }
}/* Output:
        12
 *///:~
登入後複製

  在上面的代码中,我们必须在Out类里面生成In类的对象进行操作,而无法再使用Out.In in = new Out().new In() 生成内部类的对象。也就是说,此时的内部类只对外部类是可见的,其他类根本不知道该内部类的存在。


三. 静态内部类

1、定义与原理

   静态内部类,就是修饰为 static 的内部类,该内部类对象不依赖于外部类对象,就是说我们可以直接创建内部类对象。看下面例子:
  
            Java 內部類別的詳細介紹


2、交互

 静态内部类与外部类的交互关系为:

  • 静态内部类可以直接访问外部类的所有静态成员和静态方法,即使是 private 的;

  • 外部类可以通过内部类对象访问内部类的实例成员变量/方法;对于内部类的静态域/方法,外部类可以通过内部类类名访问。


3、成员内部类和静态内部类的区别

 成员内部类和静态内部类之间的不同点包括:

  • 静态内部类对象的创建不依赖外部类的实例,但成员内部类对象的创建需要依赖外部类的实例;

  • 成员内部类能够访问外部类的静态和非静态成员,静态内部类不能访问外部类的非静态成员;


四. 局部内部类

1、定义与原理

   有这样一种内部类,它是嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,但又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

// 例 1:定义于方法内部public class Parcel4 { 
    public Destination destination(String s) { 
        class PDestination implements Destination { 
            private String label; 

            private PDestination(String whereTo) { 
                label = whereTo; 
            } 

            public String readLabel() { 
                return label; 
            } 
        } 
        return new PDestination(s); 
    } 

    public static void main(String[] args) { 
        Parcel4 p = new Parcel4(); 
        Destination d = p.destination("Tasmania"); 
    } 
}
登入後複製
// 例 2:定义于作用域内部public class Parcel5 { 
    private void internalTracking(boolean b) { 
        if (b) { 
            class TrackingSlip { 
                private String id; 
                TrackingSlip(String s) { 
                    id = s; 
                } 
                String getSlip() { 
                    return id; 
                } 
            } 
            TrackingSlip ts = new TrackingSlip("slip"); 
            String s = ts.getSlip(); 
        } 
    } 

    public void track() { 
        internalTracking(true); 
    } 

    public static void main(String[] args) { 
        Parcel5 p = new Parcel5(); 
        p.track(); 
    } 
}
登入後複製

2、final 参数

   对于final参数,若是将引用类型参数声明为final,我们无法在方法中更改参数引用所指向的对象;若是将基本类型参数声明为final,我们可以读参数,但却无法修改参数(这一特性主要用来向局部内部类和匿名内部类传递数据)。

  如果定义一个局部内部类,并且希望它的方法可以直接使用外部定义的数据,那么我们必须将这些数据设为是 final 的;特别地,如果只是局部内部类的构造器需要使用外部参数,那么这些外部参数就没必要设置为 final,例如:
      Java 內部類別的詳細介紹


五. 匿名内部类

   有时候我为了免去给内部类命名,便倾向于使用匿名内部类,因为它没有名字。匿名内部类的使用需要注意以下几个地方:

  • 匿名内部类是没有访问修饰符的;

  • 匿名内部类是没有构造方法 (因为匿名内部类连名字都没有)

  • 定义匿名内部类的前提是,内部类必须是继承一个类或者实现接口,格式为 new 父类或者接口(){子类的内容(如函数等)}。也就是说,匿名内部类最终提供给我们的是一个匿名子类的对象,例如:

// 例 1abstract class AbsDemo
{    abstract void show();
}public class Outer{
    int x=3;    public void function()//可调用函数
    {        new AbsDwmo()//匿名内部类
        {            void show()
            {
                System.out.println("x==="+x);
            }            void abc()
            {
                System.out.println("haha");
            }
        }.abc();  //匿名内部类调用函数,匿名内部类方法只能调用一次
    }   
}
登入後複製
// 例 2interface Inner {    //注释后,编译时提示类Inner找不到 
    String getName(); 
}public class Outer { 

    public Inner getInner(final String name, String city) { 
        return new Inner() { 
            private String nameStr = name; 

            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 

    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName()); 

        System.out.println(inner instanceof Inner);  //匿名内部类实质上是一个匿名子类的对象
    } /* Output:
        Inner
        true
      *///:~ }
登入後複製

  • 若匿名内部类 (匿名内部类没有构造方法) 需要直接使用其所在的外部类方法的参数时,该形参必须为 final 的;如果匿名内部类没有直接使用其所在的外部类方法的参数时,那么该参数就不必为final 的,例如:

// 情形 1:匿名内部类直接使用其所在的外部类方法的参数 namepublic class Outer { 
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName()); 
    } 

    public Inner getInner(final String name, String city) {  // 形参 name 被设为 final
        return new Inner() { 
            private String nameStr = name;       // OK
            private String cityStr = city;       // Error: 形参 city 未被设为 final

            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 
} 



// 情形 2:匿名内部类没有直接使用其所在的外部类方法的参数public class Outer { 
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName()); 
    } 

    //注意这里的形参city,由于它没有被匿名内部类直接使用,而是被抽象类Inner的构造函数所使用,所以不必定义为final
    public Inner getInner(String name, String city) { 
        return new Inner(name, city) {     // OK,形参 name 和 city 没有被匿名内部类直接使用
            private String nameStr = name; 

            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 
} 

abstract class Inner { 
    Inner(String name, String city) { 
        System.out.println(city); 
    } 

    abstract String getName(); 
}
登入後複製

  从上述代码中可以看到,当匿名内部类直接使用其所在的外部类方法的参数时,那么这些参数必须被设为 final的。为什么呢?本文所引用到的一篇文章是这样解释的:

  “这是一个编译器设计的问题,如果你了解java的编译原理的话很容易理解。首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用,例如:

public void dosome(final String a,final int b){  
  class Dosome{       public void dosome(){
            System.out.println(a+b)
       }
  }; 

  Dosome some=new Dosome();  
  some.dosome();  
}
登入後複製

  从代码来看,好像是内部类直接调用的a参数和b参数,但是实际上不是,在java编译器编译以后实际的操作代码是:

class Outer$Dosome{  
  public Dosome(final String a,final int b){  
      this.Dosome$a=a;  
      this.Dosome$b=b;  
  }  
  public void dosome(){  
      System.out.println(this.Dosome$a+this.Dosome$b);  
  }  
}
登入後複製

  从以上代码来看,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。这样就很容易理解为什么要用final了,因为两者从外表看起来是同一个东西,实际上却不是这样,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,因为从编程人员的角度来看他们是同一个东西,如果编程人员在程序设计的时候在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这就让人非常的难以理解和接受,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。”


  以上关于匿名内部类的每个例子使用的都是默认无参构造函数,下面我们介绍 带参数构造函数的匿名内部类

public class Outer { 
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName()); 
    } 

    public Inner getInner(final String name, String city) { 
        return new Inner(name, city) {   //匿名内部类
            private String nameStr = name; 
            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 
} 

abstract class Inner { 
    Inner(String name, String city) {   // 带有参数的构造函数
        System.out.println(city); 
    } 

    abstract String getName(); 
}
登入後複製

  特别地,匿名内部类通过实例初始化 (实例语句块主要用于匿名内部类中),可以达到类似构造器的效果,如下:

public class Outer { 
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName()); 
        System.out.println(inner.getProvince()); 
    } 

    public Inner getInner(final String name, final String city) { 
        return new Inner() { 
            private String nameStr = name; 
            private String province; 

            // 实例初始化 
            { 
                if (city.equals("gz")) { 
                    province = "gd"; 
                }else { 
                    province = ""; 
                } 
            } 

            public String getName() { 
                return nameStr; 
            } 

            public String getProvince() { 
                return province; 
            } 
        }; 
    } 
}
登入後複製

六. 内部类的继承

  内部类的继承,是指内部类被继承,普通类 extents 内部类。而这时候代码上要有点特别处理,具体看以下例子:
              Java 內部類別的詳細介紹

  可以看到,子类的构造函数里面要使用父类的外部类对象.super() [成员内部类对象的创建依赖于外部类对象];而这个外部类对象需要从外面创建并传给形参。


以上是Java 內部類別的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!