背景:
最近有同事跟我說了他面試時遇到的問題,考官問:“接口和抽象類可以被new嘛?”。這可能不是考官的原話,但是據他表達考官大概就是這個意思了。聽到這個問題,我的第一反應是肯定不行啊,直接對接口和抽象類調用new,編譯器都過不去。但是他說,考官說可以,用匿名內部類實現。聽見這個回到,我感覺那個考官太………,有點無語。我們可以仔細分析下這個問題。
直接new接口和抽象類
首先先明確一點,直接new接口和抽象類,這肯定行不通,編譯器會提示Cannot instantiate the type XX的錯誤。這個實驗就不做了,沒意思。
使用匿名內部類
下面的代碼是一個匿名內部類的Demo,也就是考官說的可以new。
1 package com.saillen.test; 2 3 interface A { 4 void f(); 5 } 6 7 public class T { 8 9 public T(A a) { 10 a.f(); 11 } 12 13 public static void main(String[] args) { 14 T t = new T(new A() { 15 public void f() { 16 System.out.println("我是匿名內部類"); 17 System.out.println("Class對象是:" + this.getClass()); 18 System.out.println("類名字是:" + this.getClass().getSimpleName()); 19 } 20 }); 21 } 22 23 }
上面的程序很簡單,我們使用匿名內部類,然后“new”一個接口A的對象,輸出它的類名了Class對象,輸出如下:
1 我是匿名內部類 2 Class對象是:class com.saillen.test.T$1 3 類名字是:
通過輸出可以看到,內部類的類名是“”也就是一個空字符串,但是它確確實實是有類型的。而且查看編譯后的class文件,會發現,會多一個T$1.class,這個class就是匿名內部類的原型,
用javap反編譯這個文件,可以看到這個類的源碼樣式如下:
通過反編譯后的文件可以證明,我們的“匿名內部類”的類名是Test$1。所以new針對的還是普通的class(雖然內部類和普通類有很大不一樣),只不過這個class的寫法稍有不同,它是編譯器幫我們從匿名內部類中提取的。
結論:
通過上面的實驗,我仍然堅持,接口和抽象類不可以被new!匿名內部類只是一種寫法上的迷惑而已。這個考官的答案很不靠譜,不能說他的想法就是絕對的錯,但是絕對不應該這么問這個問題,這不是一個能不能就回答的。或者說不能從字面上就證明可以對interface或者abstract class使用new。
內部類總結
Java編程思想第十章專門介紹了內部類,內部類確實神奇而且復雜。但是內部類在編程中被應用的場景很少,這主要是看設計者的設計思想,不過它的語法和特性卻有很多。對於內部類來說要記住可以繼承內部類但是不能覆蓋。
普通內部類:
普通的內部類,就是在class里面普通的定義一個類,eg:
1 public class A{ 2 public class B{ 3 4 } 5 6 }
普通內部類,或者說平凡的內部類,有如下特性:(總結自《Java編程思想》)
(1)這個類在外部類的外面不能被直接訪問,需要通過OuterClassName.InnerClassName方式訪問。比如Map的Map.Entry就是一個內部接口,只能通過Map.Entry方式來使用它。
(2)內部類對象在創建后會秘密的鏈接到外部類對象上,隱含的有一個指向外部類對象的引用,所以沒有外部類對象,是無法實例化內部類對象的。也就是我們無法獨立於外部類創建一個內部類對象。(這里不包括聲明為static的內部類,那不是平凡的內部類)
(3)因為內部類隱含的有一個指向外部類的指針,所以內部類可以訪問外圍類的成員,而且是外圍類的所有成員,包括private的成員。
(4)在內部類中使用OuterClassName.this可以訪問外圍類對象。
(4)如果想要在外部類外面實例化內部類對象,那么可以同.new語法,也就是outerObject.new InnerClass()的方式,eg:
1 package com.saillen.test; 2 3 public class A { 4 5 public void f() { 6 A.B b = this.new B(); 7 B b2 = new B(); 8 } 9 10 public class B { 11 void g(){ 12 System.out.println(A.this); 13 } 14 } 15 16 public static void main(String[] args) { 17 A a = new A(); 18 A.B b = a.new B(); 19 } 20 21 }
(5)內部類最大的用途是:它可以實現接口或者繼承某個類,這樣使用內部類時,用基類引用內部類對象,可以屏蔽內部類的細節。這樣的好處是,可以實現偽“多重繼承”等。
(6)普通的內部類不能有static字段和static數據,也不能包含嵌套類。
在方法和作用域內的內部類:
如果內部類出現在了方法和作用域內,那么它就不是“平凡”的內部類了,而且這個內部類的作用域就是在這個方法的作用域內,方法外面是無法訪問到的!但是它仍然具有“平凡”的內部類特性。
匿名內部類:
匿名內部類,在內部類的基礎上減少了對class的定義,直接用new 后面跟一個接口或者基類,然后類體里面實現方法即可,這樣JVM會調用編譯器生成的構造器來生成這個內部類對象,並且編譯器幫忙生成這個內部類的類結構。匿名內部類好處是語法簡單,但是不方便閱讀,在Android編程中,對Button的監聽經常會用匿名內部類。匿名內部類有一些限制:匿名內部既可以擴展類,也可以實現接口,但是不能兩者兼備,而且只能實現一個接口。
嵌套類:
嵌套類就是在內部類的基礎上加上static聲明,也就是靜態的內部類。嵌套類跟普通的內部類特性有很大不同,特點:
(1)在構造時,不需要外圍類的對象,但是同樣,它只能訪問外圍類中的static字段;
(2)嵌套類可以有static數據和字段;
(3)嵌套類可以作為接口的一部分,這樣在接口中就可以用公用代碼出現;
(4)嵌套類是可以多重嵌套的,嵌套多少層不重要,它都可以訪問它所有外圍類成員。
內部類標識符:
部類必須生成一個class文件以包含它的Class對象信息,這些類文件有嚴格的命名規則:外圍類的名字,加上“$”,再加上內部類的名字。
為什么需要內部類:
《Java編程思想》中很明確指出:
每個內部類都能獨立的繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個實現,對於內部類都沒有影響。
利用內部類可以實現多重繼承等好處,使用內部類還可以實現java版的閉包和回調,而且比指針更靈活、更安全。