闭包在很多语言中都存在,例如C++,C#。闭包允许我们创建函数指针,并把它们作为参数传递,Java编程语言提供了接口的概念,接口中可以定义抽象方法,接口定义了API,并希望用户或者供应商来实现这些方法,很多时候并不是为一些接口创建独立的实现类,我们通过写一个匿名的内部类来写一个内联的接口实现,匿名内部类使用相当的广泛,匿名内部类最常见的场景就是事件处理器了,其次匿名内部类还被用于多线程中,写匿名内部类而不是创建Runable\Callable接口的实现类。
一个匿名内部类就是一个内联的给定接口的实现,这个实现类的对象作为参数传递给一个方法,然后这个方法将在内部调用传递过来的实现类的方法,这种接口叫做回调接口,这个方法叫做回调方法。
匿名内部类很多地方都在使用,在使用的同时存在一些问题
-
复杂
这些类代码的层级看起来很乱很复杂,称作Vertical Problem -
不能访问封装类的非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
-
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) -
Lambda
- 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的这个接口
-
方法引用
方法引用被用作引用一个方法而不调用它,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); } }
输出:- Before Sort: Nick, Robin, Josh, Andy, Mark,
- After Sort: Andy, Josh, Mark, Nick, Robin,
-
构造方法引用
先看看实例
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
-
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