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 
    
相关文章
相关标签/搜索