写在前面: 最近在看<<Java8实战>>,感觉这本书的排版,纸质,内容真的都超级棒,个人觉得这本书还是很值得一读.本文简单或详细介绍一下Java8的FunctionalInterface和Lambda表达式.
What is Functional Interface?
函数式接口(Functional Interface)是在Java8才开始引入的.首先看一下Java普通接口和函数式接口的区别:
- Java普通接口: 指的是一些具有相同属性和行为的类的抽象.
- 函数式接口: 也是同样的理解方式,它是对一些相似的方法的抽象.
How can we say those methods are similar methods?
1.如果定义的是一个泛型的函数式接口的话,比如:
@FunctionalInterfacepublic interface MyFunction1{ R calculate(T t);}
那么所有只有一个参数并有返回值的函数都是MyFunction1的一个实例.也就是说这些方法都是相似的方法.
2.如果定义的是一个具体的函数式接口,比如:
@FunctionalInterfacepublic interface MyFunction2 { void print(Integer i);}
那么所有参数是Integer类型的并且没有返回值的函数都是MyFunction2的一个实例.也就是说这些方法都是相似的方法.
Java的接口可以当做函数的参数,那么函数式接口自然也可以,这就叫做行为参数化.
How to define and use a funtional interface?
在Java8中已经定义了好多的函数式接口,比如:
- java.lang.Runnable
- java.util.Comparator
- java.util.function.Predicate
- java.util.function.Consumer
那么定义一个函数式接口,有哪些需要注意的地方呢?
- 1.添加
@FunctionalInterface
注解. - 2.有且只有一个抽象方法.可以有多个default方法和static方法,但是这些方法都必须实现.
对于@FunctionalInterface
注解,其实不加也可以,完全是可以通过编译的,但前提是这个接口需要满足上面的第二个注意点.
但又是为什么需要@FunctionalInterface
?首先我们先看一个例子:
假设我们定义了一个函数式接口,但是忘记了添加了
@FunctionInterface
,同时这个函数式接口还没被其他地方引用,如果这个时候我们又添加了一个抽象方法,是不会报错的.因为这个接口有了多个抽象方法,所以这个接口就是一个普通的接口,而不是我们所期望的函数式接口.
这时候,如果添加了@FunctionalInterface
注解的话,就会报错,提示说这个接口具有多个抽象方法.实际上,个人理解@FunctionalInterface
就是手动添加约束,说明这个接口就是函数式接口,只能有一个抽象方法.
接下来编写并使用一个自定义函数式接口
// define a functional interface@FunctionalInterfacepublic interface MyFunction{ R convert(T t);}// use a function interfacepublic class Test { public static void main(String[] args) { MyFunction f = (Long l) -> String.valueOf(l); System.out.println(f.convert(10L)); // f: convert a Long to String MyFunction g = (String s) -> Long.parseLong(s); System.out.println(g.convert("10")); // g: convert a String to Long }}
What is Lambda expression?
咱们可以看一看<<Java8实战>>这本书中的定义:
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式: 它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表.
- 匿名: 是因为它不像普通的方法有一个明确的名称
- 函数: 是因为Lambda函数不像方法那样属于某个特定的类,但又和方法一样,有参数列表,函数主体,返回值类型,还可能有可以抛出的异常列表
- 传递: Lambda表达式可以作为参数传递给方法或存储在变量中
- 简洁: 无须像匿名类那样写很多模板代码
个人理解,Lambda表达式完全是服务于函数式接口的,就是为了在创建一个函数式接口实例时更加的直观,简洁.比如咱们要创建一个匿名类对象时,可以看一下普通接口和函数式接口的区别:
// a common interfacepublic interface Common { void f(); void g();}@FunctionalInterfacepublic interface MyFunction{ R convert(T);}public class Test { public static void main(String[] args){ // create a instance of common interface Common c = new Common() { @Override public void f() {} @Override public void g() {} }; // create a instance of functional interface MyFunction f = (Long l) -> String.valueOf(l); }}
Method Reference
说起方法引用之前,咱们先看一个例子
// 1 MyFunctionf = (Long l) -> String.valueOf(l); // 2: 通过Lambda的上下文推断出l的数据类型 MyFunction f = l -> String.valueOf(l); // // 3 MyFunction f = String::valueOf;
第一种,第二种和第三种写法的作用完全是一样的,但显然第三种的写法更加简洁.形如xxx::xxx这样的表达式就是方法引用.方法引用可以被看做仅仅调用特定方法的Lambda的一种快捷写法.
How many ways to replace lambda expression with method reference?
总共有四种方法引用,下面咱们根据一个实例来说明并解释这几种方法引用.
// common classpublic class FunctionInstance { public Integer getLength(String str) { return Optional.ofNullable(str).orElse("").length(); // Optional也是Java8引入的 }}// test classpublic class MethodReference { public static void main(String[] args) { // 1.指向静态方法的方法引用 //Functionf1 = s -> Integer.parseInt(s); Function f1 = Integer::parseInt; // 2.指向任意类型实例方法的方法引用,s为内部对象 //Predicate f2 = s -> s.isEmpty(); Predicate f2 = String::isEmpty; // 3.指向现有对象(外部对象)的实例方法的方法引用, instance为外部对象 //Function f3 = s -> instance.getLength(s); FunctionInstance instance = new FunctionInstance(); Function f3 = instance::getLength; // 4.构造函数引用 //Supplier f4 = () -> new MethodReference(); Supplier f4 = MethodReference::new; }}
2和3有人可能会混淆, 反正一开始看的时候我是没区分出来.其实就看参数是为内部对象还是外部对象.内部对象就是用2,外部对象就是用3
- 内部对象: 指
Lambda表达式
中的对象,比如上面的s, 传入的参数,属于内部对象 - 外部对象: 指的是
Lambda表达式
外的对象,比如上面的instance
Summary
函数式接口: 就是一个特殊的接口,只能有一个抽象方法.所以可以将函数式接口想象成是一个函数类型(比如Predicate,Consumer或自定义的函数式接口).
Lambda表达式: 则是简化了匿名类对象的创建.正如我在上文所说的,Lambda表达式完全是服务于函数式接口的,正是因为了函数式接口的特殊性,所以才使用了Lambda表达式来创建一个函数类型实例,而不是像匿名类那样写一堆无用的模板代码.