1. 程式人生 > >Java 介面和抽象類可以被new麼?——順便總結內部類

Java 介面和抽象類可以被new麼?——順便總結內部類

轉載:https://blog.csdn.net/hackersaillen/article/details/47281549

背景:

    最近有同事跟我說了他面試時遇到的問題,考官問:“介面和抽象類可以被new嘛?”。這可能不是考官的原話,但是據他表達考官大概就是這個意思了。聽到這個問題,我的第一反應是肯定不行啊,直接對介面和抽象類呼叫new,編譯器都過不去。但是他說,考官說可以,用匿名內部類實現。聽見這個回到,我感覺那個考官太…………驚訝,有點無語。我們可以仔細分析下這個問題。

 

直接new介面和抽象類

   首先先明確一點,直接new介面和抽象類,這肯定行不通,編譯器會提示Cannot instantiate the type XX的錯誤。這個實驗就不做了,沒意思。

 

使用匿名內部類

     下面的程式碼是一個匿名內部類的Demo,也就是考官說的可以new。

package com.saillen.test;
 
interface A {
	void f();
}
 
public class T {
 
	public T(A a) {
		a.f();
	}
 
	public static void main(String[] args) {
		T t = new T(new A() {
			public void f() {
				System.out.println("我是匿名內部類");
				System.out.println("Class物件是:" + this.getClass());
				System.out.println("類名字是:" + this.getClass().getSimpleName());
			}
		});
	}
 
}

   上面的程式很簡單,我們使用匿名內部類,然後“new”一個介面A的物件,輸出它的類名了Class物件,輸出如下:


我是匿名內部類
Class物件是:class com.saillen.test.T$1
類名字是:

   通過輸出可以看到,內部類的類名是“”也就是一個空字串,但是它確確實實是有型別的。而且檢視編譯後的class檔案,會發現,會多一個T$1.class,這個class就是匿名內部類的原型,

  用javap反編譯這個檔案,可以看到這個類的原始碼樣式如下:

   通過反編譯後的檔案可以證明,我們的“匿名內部類”的類名是Test$1。所以new針對的還是普通的class(雖然內部類和普通類有很大不一樣),只不過這個class的寫法稍有不同,它是編譯器幫我們從匿名內部類中提取的。

 

結論:

 通過上面的實驗,我仍然堅持,介面和抽象類不可以被new!匿名內部類只是一種寫法上的迷惑而已。這個考官的答案很不靠譜,不能說他的想法就是絕對的錯,但是絕對不應該這麼問這個問題,這不是一個能不能就回答的。或者說不能從字面上就證明可以對interface或者abstract class使用new。

 

內部類總結

    Java程式設計思想第十章專門介紹了內部類,內部類確實神奇而且複雜。但是內部類在程式設計中被應用的場景很少,這主要是看設計者的設計思想,不過它的語法和特性卻有很多。對於內部類來說要記住可以繼承內部類但是不能覆蓋

 

普通內部類:

    普通的內部類,就是在class裡面普通的定義一個類,eg:


public class A{
	public class B{
		
	}
	
}

普通內部類,或者說平凡的內部類,有如下特性:(總結自《Java程式設計思想》)
   (1)這個類在外部類的外面不能被直接訪問,需要通過OuterClassName.InnerClassName方式訪問。比如Map的Map.Entry就是一個內部介面,只能通過Map.Entry方式來使用它。

   (2)內部類物件在建立後會祕密的連結到外部類物件上,隱含的有一個指向外部類物件的引用,所以沒有外部類物件,是無法例項化內部類物件的。也就是我們無法獨立於外部類建立一個內部類物件。(這裡不包括宣告為static的內部類,那不是平凡的內部類

    (3)因為內部類隱含的有一個指向外部類的指標,所以內部類可以訪問外圍類的成員,而且是外圍類的所有成員,包括private的成員。

    (4)在內部類中使用OuterClassName.this可以訪問外圍類物件。

    (4)如果想要在外部類外面例項化內部類物件,那麼可以同.new語法,也就是outerObject.new InnerClass()的方式,eg:


package com.saillen.test;
 
public class A {
 
	public void f() {
		A.B b = this.new B();
		B b2 = new B();
	}
 
	public class B {
             void g(){
                System.out.println(A.this);
             }
	}
 
	public static void main(String[] args) {
		A a = new A();
		A.B b = a.new B();
	}
 
}

  (5)內部類最大的用途是:它可以實現介面或者繼承某個類,這樣使用內部類時,用基類引用內部類物件,可以遮蔽內部類的細節。這樣的好處是,可以實現偽“多重繼承”等。

  (6)普通的內部類不能有static欄位和static資料,也不能包含巢狀類。

 

在方法和作用域內的內部類:

   如果內部類出現在了方法和作用域內,那麼它就不是“平凡”的內部類了,而且這個內部類的作用域就是在這個方法的作用域內,方法外面是無法訪問到的!但是它仍然具有“平凡”的內部類特性。

 

匿名內部類:

   匿名內部類,在內部類的基礎上減少了對class的定義,直接用new 後面跟一個介面或者基類,然後類體裡面實現方法即可,這樣JVM會呼叫編譯器生成的構造器來生成這個內部類物件,並且編譯器幫忙生成這個內部類的類結構。匿名內部類好處是語法簡單,但是不方便閱讀,在Android程式設計中,對Button的監聽經常會用匿名內部類。匿名內部類有一些限制:匿名內部既可以擴充套件類,也可以實現介面,但是不能兩者兼備,而且只能實現一個介面。

 

巢狀類:

   巢狀類就是在內部類的基礎上加上static宣告,也就是靜態的內部類。巢狀類跟普通的內部類特性有很大不同,特點:

   (1)在構造時,不需要外圍類的物件,但是同樣,它只能訪問外圍類中的static欄位;

   (2)巢狀類可以有static資料和欄位;

   (3)巢狀類可以作為介面的一部分,這樣在介面中就可以用公用程式碼出現;

   (4)巢狀類是可以多重巢狀的,巢狀多少層不重要,它都可以訪問它所有外圍類成員。

 

內部類識別符號:

    部類必須生成一個class檔案以包含它的Class物件資訊,這些類檔案有嚴格的命名規則:外圍類的名字,加上“$”,再加上內部類的名字。

 

為什麼需要內部類:

   《Java程式設計思想》中很明確指出:    

   每個內部類都能獨立的繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個實現,對於內部類都沒有影響。

   利用內部類可以實現多重繼承等好處,使用內部類還可以實現java版的閉包和回撥,而且比指標更靈活、更安全。