闭包在很多语言中都存在,例如C++,C#。闭包允许我们创建函数指针,并把它们作为参数传递,Java编程语言提供了接口的概念,接口中可以定义抽象方法,接口定义了API,并希望用户或者供应商来实现这些方法,很多时候并不是为一些接口创建独立的实现类,我们通过写一个匿名的内部类来写一个内联的接口实现,匿名内部类使用相当的广泛,匿名内部类最常见的场景就是事件处理器了,其次匿名内部类还被用于多线程中,写匿名内部类而不是创建Runable\Callable接口的实现类。

一个匿名内部类就是一个内联的给定接口的实现,这个实现类的对象作为参数传递给一个方法,然后这个方法将在内部调用传递过来的实现类的方法,这种接口叫做回调接口,这个方法叫做回调方法。

匿名内部类很多地方都在使用,在使用的同时存在一些问题

  1. 复杂

    这些类代码的层级看起来很乱很复杂,称作Vertical Problem
  2. 不能访问封装类的非final成员

    this关键字变得很迷惑,如果一个匿名类有一个与其封装类相同的成员名称,内部类会覆盖外部的成员变量,在这种情况下,外部成员在匿名类内部是不可见的,甚至不能通过this来访问。

    实例说明

        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中方法的抛出异常声明

    返回值类型必须兼容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很想,它们都需要一个目标类型,但是不同的方法引用不提供方法的实现,它们引用一个已经存在的类或者对象的方法。
    1、System::getProperty
    2、"abc"::length
    3、String::length
    4、super::toString
    5、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 
    
posted on 2016-08-30 16:48  yanweiqi  阅读(13600)  评论(0编辑  收藏  举报