1. 程式人生 > >java匿名內部類的使用 (比如new物件後的大括號, List list = new ArrayList() { { }}這用用法等)

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檔案吧:
命令為:javap  -verbose BianyiClass$1.class

把得到的資訊粘貼出來:


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!
至此,就已經知道執行原理了!