嵌套類 嵌套類有兩種類別:static and non-static,分別對應為靜態嵌套類和內部類。 其中靜態嵌套類只能訪問外部類的靜態成員,內部類可以訪問外部類的任意成員;它們可以被聲明為private, public, protected, 或 package private。 靜態嵌套類實例化 ...
嵌套類
嵌套類有兩種類別:static and non-static,分別對應為靜態嵌套類和內部類。
1 class OuterClass { 2 ... 3 static class StaticNestedClass { 4 ... 5 } 6 class InnerClass { 7 ... 8 } 9 }
其中靜態嵌套類只能訪問外部類的靜態成員,內部類可以訪問外部類的任意成員;它們可以被聲明為private
, public
, protected
, 或 package private。
- 靜態嵌套類實例化方式為: OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
- 內部類實例化方式:OuterClass.InnerClass innerObject = outerObject.new InnerClass(); 即通過外部類實例才能訪問內部類。
有兩個比較特殊的內部類,分別為局部內部類和匿名類。
局部內部類
- 局部內部類(Local CLasses)可聲明在類中任意塊(block)中,如方法、for或if塊中
- 局部內部類可以訪問外部類的成員,若局部內部類聲明在靜態塊中,則可訪問外部類的靜態成員;若聲明在非靜態塊中,則可訪問外部類所有成員;
- 局部內部類可以訪問所在塊的局部變數,但該局部變數必須聲明為final;在JDK8中進行了改進,局部變數可以聲明為final或effectively final;
- 其他特性類似於普通內部類
其中effectively final與final局部變數的區別在於,前者可以不顯式聲明變數為final,只要在整個過程中,該變數不會被修改(編譯器預設該情況為final)。具體為什麼局部內部類為什麼必須引用final變數,可參考
java為什麼匿名內部類的參數引用時final? 。大致意思是局部內部類引用局部變數,其實是進行的值引用(或者說是值拷貝)。可以認為避免外部代碼塊在內部類運行結束前結束,導致局部變數回收而出錯。
匿名類
匿名類與局部內部類相似,只是沒有命名,並且同時進行聲明和實例化。如下:
1 HelloWorld frenchGreeting = new HelloWorld() { 2 String name = "tout le monde"; 3 public void greet() { 4 greetSomeone("tout le monde"); 5 } 6 public void greetSomeone(String someone) { 7 name = someone; 8 System.out.println("Salut " + name); 9 } 10 };
匿名內部類適用於只用一次的情況。其他的特性與局部內部類相同。
Lambda表達式
在使用匿名內部類的時候,無需提供類名。對於只有一個方法的介面,使用Lambda顯然比匿名類的實現簡單明瞭。如下所示,定義一個LambdaTest介面,該介面只包含一個opt方法:1 interface LambdaTest { 2 int opt(int a , int b); 3 } 4 5 LambdaTest sumTest = (a,b) -> a+b;
第5行即為Lambda表達式聲明,其中(a,b)為方法的參數,a+b為方法體,->表示將參數傳遞給方法體。
- Lambda表達式的方法體中,可以是一個表達式,也可以是代碼塊。若為表達式,Java運行期會計算表達式,並返回結果;若為代碼塊,可以添加return語句,將結果返回。
- Lambda表達式其實是一個方法的聲明,可以認為Lambda表達式是匿名方法。
- Lambda表達式與局部內部類和匿名類相似,可以訪問外部類和外部代碼塊的變數;但與後兩者不同,其不存在變數覆蓋的問題,可以認為沒有引入新的代碼塊,其與外部代碼塊中的局部變數同級。
- 由於第三條,所以在表達式的參數中,不能聲明與同級作用域相同的變數名,否則會出現重覆定義的異常。
- Lambda表達式是匿名內部類實現形式的一種,其訪問的外部變數必須是final或effectively final。
舉例如下:
1 public class Lambda { 2 3 private int var = 100; 4 private String x = "hello"; 5 6 interface Cal{ 7 int op(int a, int b); 8 } 9 10 interface Print{ 11 void print(String msg); 12 } 13 14 public int operator(int a, int b, Cal cal) { 15 return cal.op(a, b); 16 } 17 18 public void operator1(String msg, Print print) { 19 print.print(msg); 20 } 21 22 public void operator2(String x) { 23 24 // x = ""; 25 26 Print print = (msg) -> { 27 System.out.println("Lambda訪問外部變數:"); 28 System.out.println(x); 29 System.out.println(msg); 30 System.out.println(Lambda.this.x); 31 }; 32 33 print.print(x); 34 } 35 36 public static void main(String[] args) { 37 Cal add = (a,b) -> {return a+b;}; 38 Cal mul = (a,b) -> a*b; 39 40 Lambda lambda = new Lambda(); 41 System.out.println("2+3="+lambda.operator(2, 3, add)); 42 System.out.println("2*3="+lambda.operator(2, 3, mul)); 43 44 lambda.var = 200; 45 Print print = (msg) -> { 46 System.out.println(msg); 47 System.out.println(lambda.var); 48 }; 49 lambda.operator1("Hello World", print); 50 51 lambda.operator2("Hello Lambda"); 52 } 53 54 }
運行結果:
1 2+3=5 2 2*3=6 3 Hello World 4 200 5 Lambda訪問外部變數: 6 Hello Lambda 7 Hello Lambda 8 hello
其中operator2方法可以驗證後三條,如果將24行的註釋取消,28行就會報“local variables referenced from a lambda expression must be final or effectively final”的異常。
目標類型(Target Type)
目標類型為外部類方法期望調用的類型,如上例中operator期望調用的目標方法為Cal。Java會根據Lambda表達式所處的語境和上下文信息判斷目標類型,並實現調用。
舉例如下:1 public class TargetType { 2 3 interface Cal{ 4 String op(); 5 } 6 7 interface Cal1{ 8 int op1(); 9 } 10 11 interface Cal2{ 12 void op1(); 13 } 14 15 public static String invoke(Cal cal) { 16 return cal.op(); 17 } 18 19 public static void invoke(Cal1 cal1) { 20 cal1.op1(); 21 } 22 23 public static void invoke(Cal2 cal2) { 24 cal2.op1(); 25 } 26 27 public static void main(String[] args) { 28 invoke(() -> "done"); 29 invoke(() -> 100); 30 invoke(() -> {return;}); 31 } 32 }
聲明三個介面(Cal Cal1 Cal2),具有相同名稱的方法,但他們的返回值不同。另聲明瞭3個invoke方法,分別接收3個類,即期望的目標類型不同。然後進行測試:
main方法中的三個語句都通過編譯,並且eclipse提示28行調用目標類型為Cal的invoke,29行調用目標類型為Cal1的invoke,30行調用目標類型為Cal2的invoke,目標類型如下圖所示:
(1)如果再添加一句如:invoke(() -> 100.0); 則編譯器會報錯,Type mismatch: cannot convert from double to String;
(2)如果將Cal介面方法的返回值改為int,則除了28行報錯,29行也報錯:The method invoke(TargetType.Cal) is ambiguous for the type TargetType,即編譯器無法確定調用哪個目標類型。
官網文檔中舉的例子為Runnable和Callable,原理一樣,如下:1 public interface Runnable { 2 void run(); 3 } 4 5 public interface Callable<V> { 6 V call(); 7 }
方法聲明:
1 void invoke(Runnable r) { 2 r.run(); 3 } 4 5 <T> T invoke(Callable<T> c) { 6 return c.call(); 7 }
根據上下文確定目標類型,由於有返回值,所以會調用參數為Callable的invoke方法:
1 String s = invoke(() -> "done");
總結:
- 靜態嵌套類與內部類區別
- 兩類特殊的內部類,局部內部類和匿名內部類;
- 匿名內部類的特殊實現:Lambda表達式,可認為匿名方法的實現;
- Lambda表達式會根據上下文環境確定目標類型
參考: