匿名類物件體內部的方法呼叫 new 類名() {{方法呼叫列表}}
阿新 • • 發佈:2018-12-17
問題出場:
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
對比位元組碼的不同:
刪除相同的部分,對比差異之處:
暫時我只能看出位元組碼錶面的不同,而深層呼叫還不夠了解。 所以留圖,日後再細細深究。 也希望有前輩可以給幫忙解釋,不勝感激。 -.-