理解Java的匿名類


在實際的項目中看到一個很奇怪的現象,Java可以直接new一個接口,然后在new里面粗暴的加入實現代碼。就像下面這樣。那么問題來了,new出來的對象沒有實際的類作為載體,這不是很奇怪嗎?

思考以下代碼的輸出是什么?

1 Runnable x = new Runnable() {  
2     @Override  
3     public void run() {  
4         System.out.println(this.getClass());  
5     }  
6 };  
7 x.run();  

實際答案是出現xxxx$1這樣一個類名,它是編譯器給定的名稱。

匿名類

匿名類相當於在定義類的同時再新建這個類的實例。我們來看看匿名類的編譯結果。

這個類的代碼如下:

 1 public class Test {  
 2   public void test() {  
 3     Runnable r = new Runnable(){  
 4       @Override  
 5       public void run(){  
 6         System.out.println("hello");  
 7       }  
 8     };  
 9   }  
10 }  

來看看它的編譯結果,通過javap反向編譯Test.class,得到的結果如下:

1 SourceFile: "Test.java"  
2 EnclosingMethod: #20.#21                // Test.test  
3 InnerClasses:  
4      #6; //class Test$1  

發現了一個字段叫EnclosingMethod,說明這個類是定義在Test.test方法下的。那現在有個問題,如果有兩個test方法,會出現什么呢?

原來是旁邊這個注釋不太准確,實際上會包含函數簽名的。請看Constant Pool部分,這里的#21指向了這個函數簽名。

1 #21 = NameAndType        #34:#16        // test:()V  

匿名類的語法

這里舉一個簡單的例子:

1 Runnable hello = new Runnable() {  
2     public void run() {  
3         System.out.println("hello");  
4     }  
5 };  

一個匿名類由以下幾個部分組成:

  1. new操作符
  2. Runnable:接口名稱。這里還可以填寫抽象類、普通類的名稱。
  3. ():這個括號表示構造函數的參數列表。由於Runnable是一個接口,沒有構造函數,所以這里填一個空的括號表示沒有參數。
  4. {...}:大括號中間的代碼表示這個類內部的一些結構。在這里可以定義變量名稱、方法。跟普通的類一樣。

訪問權限

那么匿名內部類能訪問哪些東西呢?按照規則,可以訪問如下內容:

  1. 訪問外層Class里面的字段。
  2. 不能訪問外層方法中的本地變量。除非變量是final。
  3. 如果內部類的名稱和外面能訪問的名稱相同,則會把名稱覆蓋掉。
1 public class A {  
2     private int foo;  
3     public void test() {  
4         Runnable r = new Runnable() {  
5             System.out.println(foo);  
6         };  
7     }  
8 }  

匿名類里面不可以有的東西:
1.不能定義靜態初始化代碼塊(Static Initializer)。比如下面的代碼是不符合語法的:

1 public class A {  
2     public void test() {  
3         Runnable r = new Runnable() {  
4             static { System.out.println("hello"); }  
5         };  
6     }  
7 }  

2.不能在匿名類里面定義接口。

比如:

1 public class A {  
2     public void test() {  
3         Runnable r = new Runnable() {  
4             public interface Hello { };  
5         };  
6     }  
7 }  

和上面一樣,也是為了語義的清晰。interface只能定義靜態的。

3.不能在匿名類中定義構造函數。

1 public class A {  
2     public void test() {  
3         Runnable r = new Runnable() {  
4             public Runnable() { }  
5         };  
6     }  
7 } 

因為匿名類沒有名字,而構造函數需要把類名作為方法名才能看成構造函數。
匿名類中可以包含的東西有:

    1. 字段
    2. 方法
    3. 實例初始化代碼
    4. 本地類

為什么不能定義靜態初始化代碼

事實上,內部類中不能定義任何靜態的東西。

關鍵字:Inner class cannot have static declarations

參考資料:http://stackoverflow.com/questions/975134/why-cant-we-have-static-method-in-a-non-static-inner-class

StackOverFlow上看起來有一種解釋如下。

首先來看一個內部類。

1 public class A {  
2   public class B {  
3   }  
4 }  

它編譯之后,會變成下面這種含義:

1 public class A {  
2   public static class B {  
3     private final A parent;  
4     public B(A parent) {  
5       this.parent = parent;  
6     }  
7   }  
8 } 

所以,按照這么說,內部類就是一種語法糖。當我們定義靜態變量時,就會產生下面這種歧義。下面的代碼看起來沒什么問題。

1 public class A {  
2   private int a;  
3   public class B {  
4     public static void test() {  
5       a = 1;  
6     }  
7   }  
8 }  

但是編譯之后,問題就來了。

 1 public class A {  
 2   private int a;  
 3   public static class B {  
 4     private final A parent;  
 5     public B (A parent) { this.parent = parent; }  
 6     public static void test() {  
 7       parent.a = 1; // 這里有語法錯誤  
 8     }  
 9   }  
10 }  

所以,歸根結底,Java為了保持清晰的語法,不允許這種有歧義的語法存在。

 

Java 函數式編程(lambda) 之匿名函數

函數是一個數學上的概念,表示一個集合和另一個集合的映射關系,這種關系我們在編程的時候通過箭頭函數((input)->{expression})來表示,就是函數式編程。箭頭的左邊表示輸入集合,右邊表示這種映射關系,最終整個函數的結果代表函數表達式的輸出。

關於函數表達式的一些概念(純函數、函數副作用、合成柯里化)這里就不做詳細介紹了,因為我覺得在java中使用lambda表達式,理解上一段落就可以了,以個人在目前實踐中的經驗來看不可避免會有函數副作用,主要是指在對集合的迭代操作的時候。

在java中函數式編程在兩個地方使用起來很方便,一個是匿名函數,一個是集合的stream操作。

本文主要介紹匿名函數,匿名函數。

函數式接口:只有一個方法的接口。只要是函數式接口,都可以通過匿名函數來實現。例如java多線程的接口:

1 package java.lang;
2 @FunctionalInterface
3 public interface Runnable {
4     public abstract void run();
5 }

Runable接口只有一個方法,那就是run,像這樣的接口都可以通過匿名函數實現,下面分別展示了通過匿名類和匿名函數來實現多線程的例子:
Thread

 1          //匿名類
 2         Thread anonymousClassThread=new Thread(new Runnable() {
 3             @Override
 4             public void run()  {
 5                 int count=1;
 6                 while (count<5){
 7                     System.out.println("anonymousClassThread:"+count);
 8                     count++;
 9                     try {
10                         Thread.sleep(1000);
11                     }catch (InterruptedException e){
12                     }
13                 }
14             }
15         });
16         //匿名函數
17         Thread anonymousFunctionThread=new Thread(()->{
18             int count=1;
19             while (count<5){
20                 System.out.println("anonymousFunction:"+count);
21                 count++;
22                 try {
23                     Thread.sleep(100);
24                 }catch (InterruptedException e){
25                 }
26             }
27         });
28         anonymousClassThread.start();
29         anonymousFunctionThread.start();

可以看出相對於匿名類的使用,匿名函數在使用上更加直觀、簡潔、也很易用。從這個例子上也可以看出最好在實現匿名函數的時候養成把函數寫成純函數的好處,那就是棧信息全部在函數內部,如果在不是純函數,就會在多線程中引發同步的相關的問題。

也可以在平時的實踐中,定義一些函數式接口,通過匿名函數輕便實現該接口:

 1 interface IFunctionCase{
 2     void func(String arg);
 3 }
 4 
 5 class FunctionCaseClass{
 6 
 7     public void funcTest(IFunctionCase f){
 8           System.out.println("before");
 9           f.func("test");
10         System.out.println("after");
11     }
12 }

通過匿名函數輕松實現接口:

1         FunctionCaseClass functionCaseClass=new FunctionCaseClass();
2         functionCaseClass.funcTest((arg)->System.out.println("a   "+arg));
3         functionCaseClass.funcTest((arg)->System.out.println("b  "+arg));

java.util.function中,也有一些內置的函數式接口,這些函數式接口在jdk內置的lambda表示中被廣泛的使用,主要包含下面四類:

1.Function 返回一個新值
2.Consumer 不返回值
3.Supplier 沒有輸入,返回一個
4.Predicate 返回一個布爾值
帶Bi前綴的表示有多個輸入的場景

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM