Java8閉包

閉包在不少語言中都存在,例如C++,C#。閉包容許咱們建立函數指針,並把它們做爲參數傳遞,Java編程語言提供了接口的概念,接口中能夠定義抽象方法,接口定義了API,並但願用戶或者供應商來實現這些方法,不少時候並非爲一些接口建立獨立的實現類,咱們經過寫一個匿名的內部類來寫一個內聯的接口實現,匿名內部類使用至關的普遍,匿名內部類最多見的場景就是事件處理器了,其次匿名內部類還被用於多線程中,寫匿名內部類而不是建立Runable\Callable接口的實現類。html

一個匿名內部類就是一個內聯的給定接口的實現,這個實現類的對象做爲參數傳遞給一個方法,而後這個方法將在內部調用傳遞過來的實現類的方法,這種接口叫作回調接口,這個方法叫作回調方法。java

匿名內部類不少地方都在使用,在使用的同時存在一些問題sql

  1. 複雜

    這些類代碼的層級看起來很亂很複雜,稱做Vertical Problem
  2. 不能訪問封裝類的非final成員

    this關鍵字變得很迷惑,若是一個匿名類有一個與其封裝類相同的成員名稱,內部類會覆蓋外部的成員變量,在這種狀況下,外部成員在匿名類內部是不可見的,甚至不能經過this來訪問。express

    實例說明編程

        public void test() {  
            String variable = "Outer Method Variable";  
            new Thread(new Runnable() {  
                String variable = "Runnable Class Member";  
                public void run() {  
                    String variable = "Run Method Variable";  
                    System.out.println("->" + variable);  
                    System.out.println("->" + this.variable);  
               }  
            }).start();  
        } 
    輸出
        ->Run Method Variable   
        ->Runnable Class Member 

    這個例子很好的說明了上面的兩個問題,而Lambda表達式幾乎解決上面的全部問題,咱們討論Lambda表達式以前,讓咱們來看看Functional Interfaces數組

  3. Funcational Interfaces

    一個只有單個方法的接口,這表明了這個方法的契約。
    The Single method cal exist in the form of multiple abstract methods that are inherited from superinterfaces.But in that case the inherited methods should logically represent a single method or it might redundantly declare a method that is provided by classes like Object,e.g.toString
    多線程

    > interface Runnable{void run();}
    > interface Foo {boolean equals(Object obj);}
    > interface extends Foo{ int compare(String s1,String s2)}
    > interface Comparetor{
          boolean equals(Object obj);
          int compare(T t1,T t2);
       }
    
    > interface Foo( int m(); Object clone();
    大多數回調接口都是Functional Interfaces,例如Runable,Callable,Comparetor等等,之前被稱做SAM(Single Abstract Method) 
  4. Lambda

    1. Lambda表達式實際上就是匿名類,只不過他們的結構更輕量,Lambda表達式看起來像方法。他們有一個正式的參數列表和這參數的塊體表達式。針對上面的例子進行Lambda改造。
        public class TestLambdaExpression{
          public String variable ="Class level variable";
          public static void main(){
              new TestLambdaExpression().test();
          }
          public void test() {  
            String variable = "Method local Variable";  
            new Thread(() {  
                public void run() -> {
                    System.out.println("->" + variable);  
                    System.out.println("->" + this.variable);  
               }  
            }).start();  
          } 
        }
        輸出
         ->Method local Variable
        ->Class level variable
    能夠比較一些使用Lambda表達式和使用匿名內部類的區別,咱們能夠清楚的看出,使用Lambda表達式的方式寫匿名內部類解決了變量可見性的問題,Lambda表達式不容許建立覆蓋變量。 Lambda表達式的語法包括一個參數列表和「->」,爲何選擇這樣的語法形式,由於目前C#和Scala中一般都是這樣的,也算是遵循了Lambda的通用寫法,這樣的語法基本上解決了匿名類的複雜性,同時也顯得很是的靈活,目標接口類型不是一個表達式的一部分。編譯器會幫助推斷Lambda expression的類型與周圍的環境,Lambda表達式必須有一個目標類型,而它們能夠適配任意可能的目標類型,固然類型是一個接口的時候,下面的條件必須知足,才能編譯正確。

    接口應該是一個Funcational interface閉包

    表達式的參數數量和類型必須與Functional interface中聲明的一致編程語言

    拋出的異常表達式必須兼容function interface中方法的拋出異常聲明ide

    返回值類型必須兼容function interface 中方法的返回值類型

    因爲編譯器能夠經過目標類型的聲明中得知參數類型和個數,因此在Lambda表達式中能夠省略類型的聲明。

    例如

    Comparetor c = (s1,s2) –> s1.comparteToIgnoreCase(s2);

    並且,若是目標類型中聲明的方法只有一個參數,那麼小括號也能夠不寫,例如

    ActionListenr listenr = event –> event.getWhen();

    一個很明顯的問題來了,爲何Lambda不須要指定方法名呢?由於Lambda expression只能用戶Functional interface,而functional interface 只有一個方法。當咱們肯定一個functional interface來建立Lambda表達式的時候,編譯器能夠感知functional interface中的方法的簽名,而且檢查給定的表達式是否匹配。Lambda表達式的語法是上下文相關的,但並不是在Java8中才出現,Java 7中添加了diamond operators也有這個概念,經過上下文推斷類型。

    void invoke (Runable r) {r.run()}

    Futrue invoke (Callable c) {return c.compute()}

    Future s = invoke (() –> "done");//這個Lambda Expression顯然是調用了帶有futrue的這個接口

  5. 方法引用

    方法引用被用做引用一個方法而不調用它,Lambda表達式容許咱們定義一個匿名的方法,並將它做爲Functional Inteface的一個實例。方法引用跟Lambda Expression很想,它們都須要一個目標類型,可是不一樣的方法引用不提供方法的實現,它們引用一個已經存在的類或者對象的方法。
    一、System::getProperty
    二、"abc"::length
    三、String::length
    四、super::toString
    五、Arraylist::new
    這裏引用了一個新的操做符"::"(雙冒號)。目標引用或者說接收者被放在提供者和分隔符的後面,這造成了一個表達式,它可以引用一個方法。下面經過一個例子來了解這個操做符。
    這是一個Employee數組的排序程序
        
        import java.util.Arrays;  
        import java.util.List;  
        import java.util.concurrent.ExecutionException;  
        import java.util.concurrent.Future;  
        public class MethodReference {  
            public static void main (String[] ar){  
                Employee[] employees = {
    		new Employee("Nick"), 
    		new Employee("Robin"), 
    		new Employee("Josh"), 
    		new Employee("Andy"), 
    		new Employee("Mark")
    	    };  
                System.out.println("Before Sort:");  
                dumpEmployee(employees);  
                Arrays.sort(employees, Employee::myCompare);  
                System.out.println("After Sort:");  
                dumpEmployee(employees);  
            }  
            public static void dumpEmployee(Employee[] employees){  
                for(Employee emp : Arrays.asList(employees)){  
                    System.out.print(emp.name+", ");  
                }  
                System.out.println();  
            }  
        }  
        class Employee {  
            String name;  
            Employee(String name) {  
              this.name = name;  
            }  
            public static int myCompare(Employee emp1, Employee emp2) {  
                return emp1.name.compareTo(emp2.name);  
            }  
        }
    輸出:
    1. Before Sort: Nick, Robin, Josh, Andy, Mark,
    2. After Sort: Andy, Josh, Mark, Nick, Robin,
  6. 構造方法引用

    先看看實例

        public class ConstructorReference {  
            public static void main(String[] ar){  
                MyInterface in = MyClass::new;  
                System.out.println("->"+in.getMeMyObject());  
            }  
        }  
        interface MyInterface{  
            MyClass getMeMyObject();  
        }  
        class MyClass{  
            MyClass(){}  
        } 
    輸出
    ->com.MyClass@34e5307e
    這看起來有點神奇吧,這個接口和這個類除了接口中聲明的方法的返回值是MyClass類型的,沒有任何關係。這個例子又激起了我心中的另外一個問題:怎樣實例化一個帶參數的構造方法引用?看看下面的程序:
    public class ConstructorReference {  
        public static void main(String[] ar){  
            EmlpoyeeProvider provider = Employee::new;  
            Employee emp = provider.getMeEmployee("John", 30);  
            System.out.println("->Employee Name: "+emp.name);  
            System.out.println("->Employee Age: "+emp.age);  
       }  
    }  
    interface EmlpoyeeProvider{  
        Employee getMeEmployee(String s, Integer i);  
    }  
    class Employee{  
        String name;  
        Integer age;  
        Employee (String name, Integer age){  
            this.name = name;  
            this.age = age;  
        }  
    }
    輸出是:
        ->Employee Name: John  
        ->Employee Age: 30 
  7. Default Method

    Java8中將會引入一個叫作默認方法的概念,早期的Java版本的接口擁有很是的嚴格的接口,接口包含了一些抽象方法的聲明,全部非抽象的實現類必需要提供全部這些抽象方法的實現,甚至是這些方法沒有用或者不合適出如今一些特殊的實現類中。在即將到來的Java 版本中,容許咱們在接口中定義方法的默認實現。

        public class DefaultMethods {  
         public static void main(String[] ar){  
          NormalInterface instance = new NormalInterfaceImpl();  
          instance.myNormalMethod();  
          instance.myDefaultMethod();  
         }  
        }  
        interface NormalInterface{  
         void myNormalMethod();  
         void myDefaultMethod () default{  
          System.out.println("-> myDefaultMethod");  
         }  
        }  
        class NormalInterfaceImpl implements NormalInterface{  
         @Override 
         public void myNormalMethod() {  
          System.out.println("-> myNormalMethod");  
         }  
        }  
    輸出
    -> myDefaultMethod
    在這個例子中,ParentInterface 定義了兩個方法,一個是正常的,一個是有默認實現的,子接口只是簡單的反了過來,給第一個方法添加了默認實現,給第二個方法移除了默認實現。
    設想一個類繼承了類 C ,實現了接口 I ,並且 C 有一個方法,並且跟I中的一個提供默認方法的方法是重載兼容的。在這種狀況下,C中的方法會優先於I中的默認方法,甚至C中的方法是抽象的時候,仍然是優先的。
    public class DefaultMethods {  
     public static void main(String[] ar){  
      Interfaxe impl = new NormalInterfaceImpl();  
      impl.defaultMethod();  
     }  
    }  
    class ParentClass{  
     public void defaultMethod() {  
      System.out.println("->ParentClass");  
     }  
    }  
    interface Interfaxe{  
     public void defaultMethod() default{  
      System.out.println("->Interfaxe");  
     }  
    }  
    class NormalInterfaceImpl extends ParentClass implements Interfaxe{
    }
    輸出:
       -> ParentClass 
    
    第二個例子是,實現了兩個不一樣的接口,可是兩個接口中都提供了相同的具備默認實現的方法的聲明。在這種狀況下,編譯器將會搞不清楚怎麼回事,實現類必須選擇兩個的其中一個實現。這能夠經過以下的方式來使用super來搞定。
        public class DefaultMethods {  
         public static void main(String[] ar){  
          FirstInterface impl = new NormalInterfaceImpl();  
          impl.defaultMethod();  
         }  
        }  
        interface FirstInterface{  
         public void defaultMethod() default{  
          System.out.println("->FirstInterface");  
         }  
        }  
        interface SecondInterface{  
         public void defaultMethod() default{  
          System.out.println("->SecondInterface");  
         }  
        }  
        class NormalInterfaceImpl implements FirstInterface, SecondInterface{  
         public void defaultMethod(){  
          SecondInterface.super.defaultMethod();  
         }  
        } 
    
    輸出
        ->SecondInterface 
    
相關文章
相關標籤/搜索