DistChen

Java8-函数式接口

函数式接口

函数式接口(以下简称FI)的定义其实很简单:任何接口,如果只包含唯一一个方法(默认和静态方法除外),那么它就是一个FI。为了让编译器帮助我们确保一个接口满足FI的要求,Java8提供了@FunctionalInterface注解。虽然这个注解的使用不是强制性的,但是使用它的好处是让此接口的目的更加明确,同时编译器也会对代码进行检查,来确保被该注解标注的接口的使用没有语法错误。Runnable接口就是一个FI,下面是它的源代码:

1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}

下面这些示例也属于函数式接口:

1
2
3
4
@FunctionalInterface
public interface Demo {
void run(String msg);
}
1
2
3
4
@FunctionalInterface
public interface Demo {
abstract void run(String msg);
}
1
2
3
public interface Demo {
void run(String msg);
}
1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface Demo {
void run(String msg);
default void defaultPrint(String msg){
System.out.println("Interface Default Method:"+msg);
}
static void staticPrint(String msg){
System.out.println("Interface Static Method:"+msg);
}
}

Lambda语法糖

为了能够方便、快捷、幽雅的创建出FI的实例,Java8提供了Lambda表达式这颗语法糖。下面我用一个例子来介绍Lambda语法。假设我们想对一个List按字符串长度进行排序,那么在Java8之前,可以借助匿名内部类来实现:

1
2
3
4
5
6
7
8
9
List<String> words = Arrays.asList("apple", "banana", "pear");
words.sort(new Comparator<String>() {
@Override
public int compare(String w1, String w2) {
return Integer.compare(w1.length(), w2.length());
}
});

上面的匿名内部类简直可以用丑陋来形容,唯一的一行逻辑被五行垃圾代码淹没。根据前面的定义(并查看Java源代码)可知,Comparator是个FI,所以,可以用Lambda表达式来实现:

1
2
3
words.sort((String w1, String w2) -> {
return Integer.compare(w1.length(), w2.length());
});

代码变短了好多!仔细观察就会发现,Lambda表达式,很像一个匿名的方法,只是圆括号内的参数列表和花括号内的代码被->分隔开了。垃圾代码写的越少,我们就有越多的时间去写真正的逻辑代码,不是吗?是的!圆括号里的参数类型是可以省略的:

1
2
3
words.sort((w1, w2) -> {
return Integer.compare(w1.length(), w2.length());
});

如果Lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化:

1
2
3
words.sort(
(w1, w2) -> Integer.compare(w1.length(), w2.length())
);

注意,表达式后面是没有分号的!如果只有一个参数,那么包围参数的圆括号可以省略:

1
2
3
words.forEach(word -> {
System.out.println(word);
});

如果表达式不需要参数呢?好吧,那也必须有圆括号,例如:

1
2
3
Executors.newSingleThreadExecutor().execute(
() -> {/* do something. */} // Runnable
);

方法引用

有时候Lambda表达式的代码就只是一个简单的方法调用而已,遇到这种情况,Lambda表达式还可以进一步简化为方法引用(Method References)。

引用静态方法

1
2
List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);

某个特定对象的实例方法

例如前面那个遍历并打印每一个word的例子可以写成这样:

1
words.forEach(System.out::println);

引用某个类的实例方法

1
2
words.stream().map(word -> word.length()); // lambda
words.stream().map(String::length); // method reference

引用类的构造函数

1
2
3
4
5
6
// lambda
words.stream().map(word -> {
return new StringBuilder(word);
});
// constructor reference
words.stream().map(StringBuilder::new);

什么时候用Lambda表达式

既然Lambda表达式这么好用,那么,可以在哪些地方使用呢?如果你真正明白了什么是FI(很容易),应该立刻就能给出答案:任何可以接受一个FI实例的地方,都可以用Lambda表达式。比如,虽然上面给出的例子都是把Lambda表达式当作方法参数传递,但实际上你也可以定义变量:

1
2
3
4
5
6
7
Runnable task = () -> {
// do something
};
Comparator<String> cmp = (s1, s2) -> {
return Integer.compare(s1.length(), s2.length());
};

预定义函数式接口

Java8除了给Runnable,Comparator等接口打上了@FunctionalInterface注解之外,还预定义了一大批新的FI。这些接口都在java.util.function包里,下面简单介绍其中的几个。

1
2
3
4
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

Predicate用来判断一个对象是否满足某种条件,比如,单词是否由六个以上字母组成:

1
2
3
words.stream()
.filter(word -> word.length() > 6)
.count();

Function表示接收一个参数,并产生一个结果的函数:

1
2
3
4
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

下面的例子将集合里的每一个整数都乘以2:

1
ints.stream().map(x -> x * 2);

Consumer表示对单个参数进行的操作,前面例子中的forEach()方法接收的参数就是这种操作:

1
2
3
4
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

对原有API的增强

为了充分发挥Lambda的威力,Java8对很多老的类库进行了增强,给它们配备了Lambda武器。比如前面例子中用到的forEach()方法,实际上是添加到Iterable接口中的。而多次出现的stream()方法,则是添加在了Collection接口里。用过Ruby,Scala,Groovy等语言的Java程序员,可能已经对在这些语言里很好实现的 外部迭代器模式 垂涎很久了。虽然Google的 Guava 可以在一定程度上弥补Java的这种缺陷,但是Java8的Lambda才真正让Java朝着函数式编程迈进了一大步。

接口的默认方法

细心的读者可能会发现一个问题,给Iterable和Collection等接口增加方法,岂不是会破坏接口的向后兼容性?是的,为了保证API的向后兼容性,Java8对接口的语法进行了较大的调整,增加了 默认方法(Default Methods) 。下面是forEach()方法的实现代码:

1
2
3
4
5
6
7
8
9
10
public interface Iterable<T> {
...
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
...
}

接口的静态方法

除了抽象方法和默认方法,从Java8开始,接口也可以有静态(static)方法了。有了这个语法,我们就可以把和接口相关的帮助方法(Helper Methods)直接定义在接口里了。比如Function接口就定义了一个工厂方法indentity():

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Function<T, R> {
...
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
...
}

变量捕获

和 内部类 一样,Lambda也可以访问外部(词法作用域)变量,规则 基本一样 。Java8之前, 内部类只能访问final类型的变量 ,Java8放宽了这种限制,只要变量实际上不可变(effectively final)就可以。换句话说,如果你给变量加上final关键字编译器也不报错,那么去掉final关键字后,它就是effectively final的。看下面的例子:

1
2
3
4
5
6
7
8
9
int a = 100;
Runnable x = new Runnable() {
@Override
public void run() {
System.out.println(a);
}
};

在Java8之前,a必须是final的才能被x看到。下面用Lambda表达式重写上面的例子:

1
2
3
4
int a = 100;
Runnable x = () -> {
System.out.println(a);
};

转自:http://blog.csdn.net/zxhoo/article/details/38349011?utm_source=tuicool&utm_medium=referral在此基础上修改了部分内容。

坚持原创技术分享,您的支持将鼓励我继续创作!