1. 程式人生 > >Java內部類作用用法詳解

Java內部類作用用法詳解

Java中,可以將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。

內部類包括四種:成員內部類、區域性內部類、匿名內部類和靜態內部類。

內部類的作用:

1.Java只支援單重繼承,想擴充套件功能,去實現介面吧。很快Java的設計者就發現了他們犯了矯枉過正的錯誤,多重繼承還是有一定用處的。比如每一個人都是同時繼承父親和母親兩個類,要不然你的身體裡怎麼能留著父母的血呢?Java內部類應運而生。

《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。如果沒有內部類提供的可以繼承多個具體的或抽象的類的能力,一些設計與程式設計問題就很難解決。從這個角度看,內部類使得多重繼承的解決方案變得完整。介面解決了部分問題,而內部類有效地實現了"多重繼承"。

2.當父類和實現的接口出現同名函式時,你又不想父類的函式被覆蓋,回撥可以幫你解決這個問題。

比如,你想繼承Run介面,來實現run方法,但是你自己也有一個run方法,這時就可以使用內部類來實現介面,通過回撥執行。

public interface Run{ public void run(); }
public class Penguin{
    public void run(){ //do something }
    public void swim(){ //do something }
    public class Running implements Run{ 
        public void run(){Penguin.this.run();} 
        }
    public class Swimming implements Run{ 
        public void run(){Penguin.this.swim();} 
    }
    public Running getRunner(){return new Running();}
    public Swumming getSwimmer(){return new Swimming();}
}

3.高內聚低耦合,如果一個類A呼叫了另一個類B,並且類B的存在意義只是讓A來呼叫,那麼可以講這個類B寫在類A的內部,成為類A的內部類。

比如,把只和這個類相關的型別放到這個類內部是很合理的,如果把它放在外面,那麼兩個類就成了耦合關係
 

public class LinkedList<T> {
    class Node {
        T data;
        Node prev;
        Node next;
    }

    Node fst;
    Node lst;
}
class LinkedListNode<T> {
    T data;
    LinkedListNode<T> prev;
    LinkedListNode<T> next;
}

public class LinkedList<T> {
    LinkedListNode<T> fst;
    LinkedListNode<T> lst;
}

很明顯內部類方式更加合理。

成員內部類:

成員內部類定義在一個類的內部,相當於一個外部類的成員。

class Outerclass{
	private String string;
	private Innerclass innerclass = new Innerclass();
	public Outerclass() {
		System.out.println(innerclass.a);
	}
	protected class Innerclass{
		private String string;
		public void play() {
			string = "a";
			Outerclass.this.string = "b";
		}
		int a = 0;
	}
}

public class Test {
	public static void main(String[]args) {
		Outerclass outerclass = new Outerclass();
		Outerclass.Innerclass innerclass = outerclass.new Innerclass();
		System.out.println(innerclass.a);
	}
}

成員內部類可以無條件的訪問外部類的成員變數或方法,即使是private的。但是如果外部類要訪問內部類的成員變數或方法,必須要例項化一個內部類的物件,通過這個物件才能訪問。

成員內部類是依附外部類而存在的,也就是說,如果要建立成員內部類的物件,前提是必須存在一個外部類的物件。

另外,當成員內部類擁有和外部類同名的成員變數或者方法時,會發生隱藏現象,即預設情況下訪問的是成員內部類的成員。如果要訪問外部類的同名成員,需要以下面的形式進行訪問:

外部類.this.成員變數
外部類.this.成員方法

成員內部類中要注意兩點,第一:成員內部類中不能存在任何static的變數和方法,但是可以存在同時被static 和 fianl修飾的變數;第二:成員內部類是依附於外圍類的,所以只有先建立了外圍類才能夠建立內部類。

原因:java類載入順序,首先載入類,執行static變數初始化,接下來執行物件的建立,如果我們要執行程式碼中的變數初始化,那麼必須先執行載入外部類,再載入內部類,最後初始化靜態變數,問題就出在載入內部類上面,我們可以把內部類看成外部類的非靜態成員,它的初始化必須在外部類物件建立後以後進行,要載入內部類必須在例項化外部類之後完成 ,java虛擬機器要求所有的靜態變數必須在物件建立之前完成,這樣便產生了矛盾。而java常量放在記憶體中常量池,它的機制與變數是不同的,編譯時,載入常量是不需要載入類的,所以有了final修飾就沒有上面那種矛盾。

區域性內部類:

作用:區域性內部類是巢狀在方法和作用域(比如if(),while())內的,對於這個類的使用主要是應用與解決比較複雜的問題,想建立一個類來輔助我們的解決方案,到那時又不希望這個類是公共可用的,並且可能只使用一次,所以就產生了區域性內部類,區域性內部類和成員內部類一樣被編譯,只是它的作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效。

class People{
	public People() {
	}
}
class Man{
	public Man(){
	}
	
	public People getWoman(){
		class Woman extends People{   //區域性內部類
			int age =0;
		}
		return new Woman();
	}
}
public class Test2 {
	public static void main(String []args) {
		Man man = new Man();
		man.getWoman();
	}
}

區域性內部類就像是方法裡面的一個區域性變數一樣,是不能有public、protected、private以及static修飾符的。

區域性內部類也不能有static型別的成員變數或者方法,除非是static final一起修飾。

匿名內部類:

匿名內部類常應用在Swing的新增事件監聽中。

public class OuterClass {
    public InnerClass getInnerClass(final int num,String str2){
        return new InnerClass(){
            int number = num + 3;
            public int getNumber(){
                return number;
            }
        };        /* 注意:分號不能省 */
    }
    
    public static void main(String[] args) {
        OuterClass out = new OuterClass();
        InnerClass inner = out.getInnerClass(2, "chenssy");
        System.out.println(inner.getNumber());
    }
}

interface InnerClass {
    int getNumber();
}

1、 匿名內部類是沒有訪問修飾符的。
2、 new 匿名內部類,這個類首先是要存在的。如果我們將那個InnerClass介面註釋掉,就會出現編譯出錯。
3、 注意getInnerClass()方法的形參,第一個形參是用final修飾的,而第二個卻沒有。同時我們也發現第二個形參在匿名內部類中沒有使用過,所以當所在方法的形參需要被匿名內部類使用,那麼這個形參就必須為final。

基本型別作為引數傳遞時,傳遞的是值的拷貝,無論你怎麼改變這個拷貝,原值是不會改變的。當你在匿名內部類裡面嘗試改變外部基本型別的變數的值的時候,或者改變外部引用變數的指向的時候,表面上看起來好像都成功了,但實際上並不會影響到外部的變數。所以,Java為了不讓自己看起來那麼奇怪,才加了這個final的限制。但是在Java1.8之後,也可以不用加final修飾,因為java在解析時候會自動幫你加上final。

4.匿名內部類中不能有靜態變數或者方法,因為匿名內部類是成員內部類的一種。

另外,區域性內部類是有構造方法的,只不過是隱式的。

作用:有時候有的內部類只需要建立一個它的物件就可以了,以後再不會用到這個類,這時候使用匿名內部類就比較合適,而且也免去了給它取名字的煩惱。

靜態內部類:

class StaticOuter {  
private int a = 100;  
private static int b = 150;  
public static void test(){  
    System.out.println("Outer static test");  
}  
public  void test2(){  
    System.out.println("Outer instabce test");  
}     
  
    static class StaticInner {  
        public  int a = 200;  
        static int b =300;  
        public static void test(){  
            System.out.println("Inner static test");  
        }  
        public  void test2(){  
            System.out.println("Inner instance test");  
            StaticOuter.test();  
            new StaticOuter().test2();  
            System.out.println("StaticOuter.b  = "+StaticOuter.b);  
        }     
    }  
} 

非靜態內部類在編譯完成之後會隱含地儲存著一個引用,該引用是指向建立它的外圍內,但是靜態內部類卻沒有。沒有這個引用就意味著:
      1、 它的建立是不需要依賴於外圍類的。

      2、 它不能使用任何外圍類的非static成員變數和方法。

如果內部類引用了外部類的非靜態變數,因為靜態內部類的建立不需要先建立外部類物件,外部類都沒有生成,更不會引用到外部類的變數,所以只能引用靜態變數或方法。

作用:如果我內部類與你外部類關係不緊密,耦合程度不高,不需要訪問外部類的所有屬性或方法,那麼我就設計成靜態內部類。而且,由於靜態內部類與外部類並不會儲存相互之間的引用,因此在一定程度上,還會節省那麼一點記憶體資源。