java匿名內部類的使用 (比如new物件後的大括號, List list = new ArrayList() { { }}這用用法等)
今天在看別人的程式碼的時候,發現有
Yyy uu=new Xxx(){
public void aaa(){
//這裡寫程式碼。。。
}
}
這種形式,以前偶爾看見過,也知道是匿名內部類的情況,但一直沒有仔細去研究,今天特意花點時間去寫了點很簡單也易懂的例子,初學時需要的技術不在於複雜程度,能讓人看得懂的程式碼才是好程式碼,希望能幫助大家:
一、例子1::匿名寫一個介面實現類。看下我這個例子(直接copy後在自己的IDE練習即可):
a.新建一個介面:
public interface InterfaceTest { public String getName(); public String getAge(); }
b.新建一個測試類:
package test; public class NewInterfaceDemo { public static void main(String[] args) { InterfaceTest test=new InterfaceTest() {//直接就new InterfaceTest() 敲回車後下面的方法會自動出來的 @Override public String getName() { return "小明"; } @Override public String getAge() { return "5"; } }; System.out.println(test.getName()+test.getAge()+"歲啦"); } }
執行後打印出:
這是最簡單的匿名內部類的例子,實際上看得懂就可以舉一反三了,比如不是介面,而是抽象類的抽象方法,也是可以直接new 抽象類(){抽象方法}的。以上的例子相當於寫了一個實現類,如:
public class InterfaceTestImpl implements InterfaceTest { @Override public String getName() { return "小明"; } @Override public String getAge() { return "5"; } public static void main(String[] args) { InterfaceTest test=new InterfaceTestImpl(); System.out.println(test.getName()+test.getAge()+"歲啦"); } }
二、例子2:new物件後的大括號。
有時候我們看到的new一個Class,雖然這個class不是抽象類也不是介面,就是一個實實在在的類,然後後面也可以跟一個大括號,這又是怎麼回事呢?我們可以直接拿非常常見的HashMap類來做這個例子:
public class Test {
public static void main(String[] args) {
Map<String, String> map2=new HashMap<String,String>(){
@Override
public String put(String var1, String var2) {
var1="key_value";//a行
var2="重寫後的值";//b行
return super.put(var1, var2);
}
};
map2.put("start_key", "不知道start_key的值是不是這個");
System.out.println("找到start_key的值: "+map2.get("start_key"));
System.out.println("雖然沒有明顯把key_value當key賦值,嘗試嘗試key_value的值: "+map2.get("key_value"));
}
}
這例子夠簡單吧,我們這裡看下輸出:
明明後面是寫了map2.put(“start_key”, “不知道start_key的值是不是這個”);,為什麼輸出map2.get(“start_key”)的值是null呢?原因是程式碼中的a行b行重寫了hashmap的put方法,把值給改過來了,實際上他們操作的是map2.put(“key_value”,“重寫後的值”)! 所以從這裡可以看出,在new 物件後,後面還跟著大括號,又直接寫了個方法,這種情況下就是重寫當前類的這個方法了,不過需要注意的是:該重寫的方法只對當前物件有用!
三、List list = new ArrayList() { { }}這種寫法:
不知道這種寫法怎麼說,就是new物件後,後面跟著兩個大括號,比如:
List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
}
};
System.out.println("list長度: "+list.size());
然後這程式碼輸出的是list長度:2 事實上就可以知道,在new ArrayList的時候,建立建構函式時順便給list物件添加了a b兩個值了,所以list的長度為2
再弄個hashmap的:
Map<String, String> map=new HashMap<String,String>(){
{
put("haha", "heiehi");
}
};
System.out.println(map.get("haha"));
輸出heihei
好了 這是怎麼實現的呢?
從表面上看,其實內大括號使用的是this.add()方法,也就是說,這個方法也只是當前物件有效,其他你再new ArrayList()的時候,所得的物件長度就是0 而不是2了。
如果深入瞭解,我們可以把這個檔案進行編譯,原始碼為:
public class BianyiClass {
public static void main(String[] args) {
List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
}
};
}
}
提取出來放入D盤:
然後進行編譯:
得到class檔案:
可以看到產生了兩個class檔案,我們使用java自帶的javap進行反編譯檢視BianyiClass.class檔案:
太長了複製出來看一下:
D:\>javap -verbose BianyiClass.class//這是直接在cmd視窗寫的,下面是反編譯出來的程式碼
Classfile /D:/BianyiClass.class
Last modified 2018-11-16; size 344 bytes
MD5 checksum a180fea3ce00b955e16a1fff9242e64a
Compiled from "BianyiClass.java"
public class test.BianyiClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#15 // java/lang/Object."<init>":()V
#2 = Class #16 // test/BianyiClass$1
#3 = Methodref #2.#15 // test/BianyiClass$1."<init>":()V
#4 = Class #17 // test/BianyiClass
#5 = Class #18 // java/lang/Object
#6 = Utf8 InnerClasses
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 BianyiClass.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Utf8 test/BianyiClass$1
#17 = Utf8 test/BianyiClass
#18 = Utf8 java/lang/Object
{
public test.BianyiClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class test/BianyiClass$1
3: dup
4: invokespecial #3 // Method test/BianyiClass$1."<init>":()V
7: astore_1
8: return
LineNumberTable:
line 9: 0
line 15: 8
}
SourceFile: "BianyiClass.java"
InnerClasses:
static #2; //class test/BianyiClass$1
有點眼花繚亂。。。。。。。。。。。。。。。。。。。緩一下神。。。。。。。。。。。
仔細看一看,發現其實檢視時,自動有備註的,哈哈,繼續往下,發現
class test/BianyiClass$1
這個備註,說明了這個編譯檔案,new了一個物件,在BianyiClass$1裡面,然後我們繼續javapBianyiClass$1檔案吧:
把得到的資訊粘貼出來:
D:\>javap -verbose BianyiClass$1.class
Classfile /D:/BianyiClass$1.class
Last modified 2018-11-16; size 468 bytes
MD5 checksum edefcd30c6aa2ed4935340e149a2c92f
Compiled from "BianyiClass.java"
final class test.BianyiClass$1 extends java.util.ArrayList<java.lang.String>
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Methodref #6.#18 // java/util/ArrayList."<init>":()V
#2 = String #19 // a
#3 = Methodref #5.#20 // test/BianyiClass$1.add:(Ljava/lang/Object;)Z
#4 = String #21 // b
#5 = Class #22 // test/BianyiClass$1
#6 = Class #24 // java/util/ArrayList
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 Signature
#12 = Utf8 Ljava/util/ArrayList<Ljava/lang/String;>;
#13 = Utf8 SourceFile
#14 = Utf8 BianyiClass.java
#15 = Utf8 EnclosingMethod
#16 = Class #25 // test/BianyiClass
#17 = NameAndType #26:#27 // main:([Ljava/lang/String;)V
#18 = NameAndType #7:#8 // "<init>":()V
#19 = Utf8 a
#20 = NameAndType #28:#29 // add:(Ljava/lang/Object;)Z
#21 = Utf8 b
#22 = Utf8 test/BianyiClass$1
#23 = Utf8 InnerClasses
#24 = Utf8 java/util/ArrayList
#25 = Utf8 test/BianyiClass
#26 = Utf8 main
#27 = Utf8 ([Ljava/lang/String;)V
#28 = Utf8 add
#29 = Utf8 (Ljava/lang/Object;)Z
{
test.BianyiClass$1();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/util/ArrayList."<init>":()V
4: aload_0
5: ldc #2 // String a
7: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
10: pop
11: aload_0
12: ldc #4 // String b
14: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
17: pop
18: return
LineNumberTable:
line 9: 0
line 11: 4
line 12: 11
line 13: 18
}
Signature: #12 // Ljava/util/ArrayList<Ljava/lang/String;>;
SourceFile: "BianyiClass.java"
EnclosingMethod: #16.#17 // test.BianyiClass.main
InnerClasses:
static #5; //class test/BianyiClass$1
又是眼花繚亂。。。
注意看這一句:final class test.BianyiClass$1 extends java.util.ArrayList<java.lang.String> 原來是new了一個ArrayList的子類,只不過這個子類是匿名子類。
這個大括號裡面的內容,實際上是建立建構函式時的內容,抽取出來看一下:
{
test.BianyiClass$1();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/util/ArrayList."<init>":()V
4: aload_0
5: ldc #2 // String a
7: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
10: pop
11: aload_0
12: ldc #4 // String b
14: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
17: pop
18: return
LineNumberTable:
line 9: 0
line 11: 4
line 12: 11
line 13: 18
}
然後再看這句:
7: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
還有這句:
14: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
明白了吧,實際上在初始化物件時,這個物件已經新增兩次資訊了,也就是那個a和b!
至此,就已經知道執行原理了!