Java學習筆記-嵌套類,java學習筆記嵌套
嵌套類
嵌套類有兩種類別: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=52 2*3=63 Hello World4 2005 Lambda訪問外部變數:6 Hello Lambda7 Hello Lambda8 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運算式會根據上下文環境確定目標類型
參考:
- Nested Classes
- Lambda Expressions