1. 程式人生 > >匿名類物件體內部的方法呼叫 new 類名() {{方法呼叫列表}}

匿名類物件體內部的方法呼叫 new 類名() {{方法呼叫列表}}

問題出場:

mybatis-3.4.5 文件 第八章Statement Builders程式碼段(頁碼:89/97): SQL語句構建器

private String selectPersonSql() {
		return new SQL() {{
		SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
		SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
		FROM("PERSON P");
		FROM("ACCOUNT A");
		INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
		INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
		WHERE("P.ID = A.ID");
		WHERE("P.FIRST_NAME like ?");
		OR();
		WHERE("P.LAST_NAME like ?");
		GROUP_BY("P.ID");
		HAVING("P.LAST_NAME like ?");
		OR();
		HAVING("P.FIRST_NAME like ?");
		ORDER_BY("P.ID");
		ORDER_BY("P.FULL_NAME");
		}}.toString();
}

簡化無關細節:

private String selectPersonSql() {
		return new SQL() {{
		SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
		FROM("PERSON P");
		}}.toString();
}

再次提取,發現一種新的語法形式:

new 類名() {{
	方法呼叫1();
	方法呼叫2();
}};

問題分析測試

測試1

之前沒有見過這種new 類名() {{}}的寫法(兩個雙引號!!),而且兩個引號中有幾個例項方法的呼叫(以分號隔開),但在匿名類物件中Override的寫法與之相似。 看來這是面向物件的又一個語法糖,那麼測試:

public class TestAnonymousClass {
	public static void main(String[] args) {
		// Demo類的引用指向Demo類的例項物件。
		Demo d1 = new Demo();
		d1.foo();	// 利用物件的引用多次呼叫成員。
		d1.bar();

		// Demo類的引用指向匿名類TestAnonymous$1類的例項物件。
		Demo d2 = new Demo() {};
		d2.foo();	// 利用物件的引用多次呼叫其成員。
		d2.bar();

		Demo d3 = new Demo() {{
			foo();	// 在類體中呼叫方法。呼叫語句用{}括起來。
			bar();
		}};
	}
}

class Demo {
	void foo() {
		System.out.println(this.getClass() + "\tfoo");
	}
	void bar() {
		System.out.println(this.getClass() + "\tbar");
	}
}

輸出結果:

class Demo	foo
class Demo	bar
class TestAnonymousClass$1	foo
class TestAnonymousClass$1	bar
class TestAnonymousClass$2	foo
class TestAnonymousClass$2	bar

測試2

再次單獨測試在匿名類物件內部通過{}進行方法呼叫的細節:

public class TestAnonymousClassInnerInvoke {
	public static void main(String[] args) {

		// 匿名類內部重寫方法
		Demo d1 = new Demo() {
			@Override
			public void foo() {
				System.out.println("111 override\t" + this.getClass());
			}
		};
		d1.foo();

		// 匿名類內部直接呼叫方法
		Demo d2 = new Demo() {{
			foo();	// 在類體中呼叫方法。呼叫語句用{}括起來。
		}};

		// Override混合invoke
		new Demo() {
			@Override
			void foo() {
				System.out.println("333 override\t" + this.getClass());
			}
			{	foo();	}
		};
		System.out.println();

		// 用{}包圍呼叫語句的語句塊,可不可以有多個?
		new Demo() {
			{	foo(); bar();	}
			@Override
			void foo() {
				System.out.println("4444 foo override");
			}
			{	foo(); bar();	}
		};
		// 可以

	}
}

class Demo {
	void foo() {
		System.out.println(this.getClass() + "\tfoo");
	}
	void bar() {
		System.out.println(this.getClass() + "\tbar");
	}
}

輸出:

111 override	class TestAnonymousClassInnerInvoke$1
class TestAnonymousClassInnerInvoke$2	foo
333 override	class TestAnonymousClassInnerInvoke$3

4444 foo override
class TestAnonymousClassInnerInvoke$4	bar
4444 foo override
class TestAnonymousClassInnerInvoke$4	bar

再次猜測:這種在類體中直接在{}中呼叫多個方法的寫法是靜態程式碼塊。 驗證:

public class TestIsStaticBlock {
	public static void main(String[] args) {
		new Demo() {
			{	foo();	}
		};
	}
}

class Demo {
	void foo() {}
}

反編譯,javap -p TestIsStaticBlock$1.class,檢視結果

Compiled from "TestIsStaticBlock.java"        
final class TestIsStaticBlock$1 extends Demo {
  TestIsStaticBlock$1();                      
}                                             

那麼這種形式不是靜態程式碼塊。再次設定對比試驗測試:

測試1:

public class TestGuess {
	public static void main(String[] args) {
		new Demo() {};
	}
}

class Demo {
	void foo() {}
}

測試2:

public class TestGuess {
	public static void main(String[] args) {
		new Demo() {
			{	foo();	}
		};
	}
}

class Demo {
	void foo() {}
}

對main方法中匿名類的位元組碼進行反編譯,命令為javap -v xxx.class,對比位元組碼的不同: 測試1的位元組碼:

Classfile /E:/WORKSPACE/前端/frameset/TestGuess$1.class
  Last modified 2018-10-25; size 293 bytes
  MD5 checksum 533152dd698571cd99d3732cef031a5d
  Compiled from "TestGuess.java"
final class TestGuess$1 extends Demo
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#13         // Demo."<init>":()V
   #2 = Class              #14            // TestGuess$1
   #3 = Class              #16            // Demo
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               SourceFile
   #9 = Utf8               TestGuess.java
  #10 = Utf8               EnclosingMethod
  #11 = Class              #17            // TestGuess
  #12 = NameAndType        #18:#19        // main:([Ljava/lang/String;)V
  #13 = NameAndType        #4:#5          // "<init>":()V
  #14 = Utf8               TestGuess$1
  #15 = Utf8               InnerClasses
  #16 = Utf8               Demo
  #17 = Utf8               TestGuess
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
{
  TestGuess$1();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Demo."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
}
SourceFile: "TestGuess.java"
EnclosingMethod: #11.#12                // TestGuess.main
InnerClasses:
     static #2; //class TestGuess$1

測試2:

Classfile /E:/WORKSPACE/前端/frameset/TestGuess$1.class
  Last modified 2018-10-25; size 317 bytes
  MD5 checksum d5a026d0d42b8294e16ea8f4b54749fb
  Compiled from "TestGuess.java"
final class TestGuess$1 extends Demo
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#14         // Demo."<init>":()V
   #2 = Methodref          #3.#15         // TestGuess$1.foo:()V
   #3 = Class              #16            // TestGuess$1
   #4 = Class              #18            // Demo
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               SourceFile
  #10 = Utf8               TestGuess.java
  #11 = Utf8               EnclosingMethod
  #12 = Class              #19            // TestGuess
  #13 = NameAndType        #20:#21        // main:([Ljava/lang/String;)V
  #14 = NameAndType        #5:#6          // "<init>":()V
  #15 = NameAndType        #22:#6         // foo:()V
  #16 = Utf8               TestGuess$1
  #17 = Utf8               InnerClasses
  #18 = Utf8               Demo
  #19 = Utf8               TestGuess
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               foo
{
  TestGuess$1();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method Demo."<init>":()V
         4: aload_0
         5: invokevirtual #2                  // Method foo:()V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 4
}
SourceFile: "TestGuess.java"
EnclosingMethod: #12.#13                // TestGuess.main
InnerClasses:
     static #3; //class TestGuess$1

對比位元組碼的不同: 在這裡插入圖片描述

刪除相同的部分,對比差異之處: 在這裡插入圖片描述

暫時我只能看出位元組碼錶面的不同,而深層呼叫還不夠了解。 所以留圖,日後再細細深究。 也希望有前輩可以給幫忙解釋,不勝感激。 -.-