Java 自 jdk8 之后,引入了一种新的思想:函数式编程思想。什么是函数式编程思想?说白了就是注重结果,但是忽略过程。所以这里就有两个比较重要的知识点,一个是 Lambda 表达式,另一个是函数式接口。
在这里,我会先讲下函数式接口,然后再说下这个 Lambda 表达式,这个很重要。因为Lambda 表达式的使用前提是,这个使用对象它必须有接口且必须是函数式接口,我还记得上一次在学校上课的时候,我们老师在这一点上就讲的非常含糊,所以我会在本文详解讲解下。
1, 什么是函数式接口?
函数式接口,其实就是一个接口,它有且仅有一个抽象方法,这样我们就可以称它为函数式接口。函数式接口虽然只能有一个抽象方法,但它也可以包含多个默认方法或静态方法。
// 只有一个抽象方法,那么它就是一个函数式接口
@FunctionalInterface
public interface Runnable {
// 抽象方法
public abstract void run();
/*/}
在 java 中,还提供了注解 @Functionallnterface 用于检测一个接口它是否为函数式接口。当然,我们在定义函数式接口的时候,这个注解不是必需的。
2,内置的函数式接口
Java8 默认给我们提供了大量的函数式接口,但我们通常重点学习的是以下这四大函数式接口:
- Supplier 接口 – 供应型
- Consumer 接口 – 消费型
- Predicate 接口 – 比较型
- Function 接口 – 转化型
a. Supplier 接口
我们讲通俗点,Supplier 接口,它就相当于一个供应商,它不接受任何参数,只给你提供结果,提供你想要的数据。
@FunctionalInterface
public interface Supplier<T> {
// 返回数据
T get();
/**/}
使用例子:
public class SupplierDemo {/**/
public static void main(String[] args) {/**/
getMax(new Supplier<Integer>() {/**/
@Override
public Integer get() {/**/
Integer[] integers = new Integer[]{1,8,6,2,3};
return integers[integers.length-1];
}
});
}
public static void getMax(Supplier<Integer> supplier) {/**/
Integer max = supplier.get();
System.out.println("Max 值是" + max);
}
} // 运行结果:3
b. Consumer 接口
既然有供应,那对应的是不是也得有消费?Consumer 接口就相当于一个消费者,去 ” 消费我们的数据 ”,即对数据进行一些操作或使用它。而具体要如何消费,就写在我们 accept 方法里。
@FunctionalInterface
public interface Consumer<T> { //
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) { //
Objects.requireNonNull(after);
return (T t) -> {accept(t); after.accept(t); };
}
}
使用例子:
public class ConsumerDemo {//
public static void main(String[] args) {//
echoString(new Consumer<String>() {//
@Override // 消费一个字符串数据
public void accept(String s) {//
StringBuilder stringBuilder = new StringBuilder(s);
System.out.println(stringBuilder.reverse()); // 字符串倒序
}
});
}
public static void echoString(Consumer<String> consumer) {//
consumer.accept("LUJIA IS ME");
}
} // 运行结果: EM SI AIJUL
c. Predicate 接口
Predicate 接口,它就相当于是一个比较器,对给定数据进行比较或判断,返回一个布尔值。
并且 Predicate 接口默认还提供了很多的默认方法,我认为非常实用,我们主要重点学习下面这个几个:
default Predicate<T> and(Predicate<? super T> other); // 组合条件判断,等价于 "和"
default Predicate<T> negate(); // 返回非逻辑布尔值 等价于 "!" 或 "非"
default Predicate<T> or(Predicate<? super T> other);// 组合条件判断,等价于 "或"
static <T> Predicate<T> isEqual(Object targetRef);// 判断是否为空
使用例子:
假如我们有一张集合表,这张集合表里面储存了学生们的名字和它们对应的成绩,然后我们要从这张表里面筛选出 成绩合格(大于 60 分) 且名字是两个字,名字开头不是 S 的学生。
public class PredicateDemo { //
public static void main(String[] args) {
// 学生表信息
HashMap<String, Integer> students = new HashMap<>();
students.put("John", 73);
students.put("Jia", 57);
students.put("Li", 67);
students.put("Juggler", 94);
students.put("Wd", 77);
students.put("Sb", 61);
HashMap<String, Integer> s = filterStudents(students,
new Predicate<String>() {
// 判断名字长度是不是为 2
public boolean test(String s) { //
if (s.length() == 2)
return true;
return false;
}
},
new Predicate<String>() {
// 判断名字开头是否为大小写 S
public boolean test(String s) { //
if (!s.startsWith("s") & !s.startsWith("S"))
return true;
return false;
}
}
,
new Predicate<Integer>() {
// 判断成绩是否大于等于 60
public boolean test(Integer s) { //
if (s>= 60)
return true;
return false;
}
});
System.out.println(s);
}
public static HashMap<String,Integer> filterStudents( //
HashMap<String, Integer> students,
Predicate<String> p1,
Predicate<String> p2,
Predicate<Integer> p3
) { //
HashMap<String, Integer> result = new HashMap<>();
Iterator<Map.Entry<String, Integer>> iterator = students.entrySet().iterator();
while (iterator.hasNext()) { //
Map.Entry<String, Integer> next = iterator.next();
Boolean nameIf = p1.and(p2).test(next.getKey()); // 判断名字是不是两位且开头不是 S
Boolean socre = p3.test(next.getValue()); // 判断成绩是否大于等于 60
if (nameIf && socre) { //
result.put(next.getKey(), next.getValue());
}
}
return result;
}
}
最终的运行结果就是:
{Li=67, Wd=77}
d. Function 接口
我们通俗点讲,Function 接口就相当于是一个 ” 转换器 ”,主要负责将给定数据的数据类型转化为另一种数据类型。
@FunctionalInterface
public interface Function<T, R> { //
R apply(T t);
}
Function 接口接受两个泛型,一个是原始数据的数据类型,另一个是要转化的数据类型。
使用例子:
public class FunctionDemo { //
public static void main(String[] args) { //
Integer result = getInt(new Function<String, Integer>() { //
@Override
public Integer apply(String s) { //
return Integer.parseInt(s);
}
});
System.out.println(result + 1);
}
// 将字符串转化为整数型
public static Integer getInt(Function<String, Integer> F) { //
return F.apply("123");
}
}// 运行结果:124
3, Lambda 表达式
讲完了函数式接口,我们可以来讲讲 Lambda 表达式了。前面我提到,函数式编程思想的核心就是注重结果,忽略过程。而 Lambda 表达式就是能够让我们对一些过程进行简化,忽略,使我们的代码更加简洁和更高的可读性。
a. Lambda 表达式的使用前提
1, 使用对象必须是一个函数式接口
2,若函数式接口方法作为形参,则可以考虑使用 Lambda 表达式
b. Lambda 表达式使用规则
1,表达式形式: (参数列表) -> {重写的逻辑}
类和函数声明的过程可以被忽略,包括参数的数据类型也是可以忽略的。
使用例子:
先给大家看一下,我们之前写的函数式接口的代码:
public class SupplierDemo {/**/
public static void main(String[] args) {/**/
getMax(new Supplier<Integer>() {/**/
@Override
public Integer get() {/**/
Integer[] integers = new Integer[]{1,8,6,2,3};
return integers[integers.length-1];
}
});
}
public static void getMax(Supplier<Integer> supplier) {/**/
Integer max = supplier.get();
System.out.println("Max 值是" + max);
}
}
可以看到,Supplier 接口方法是作为形参,我们传入的时候,我们声明一个匿名类然后去实现 Supplier 接口中的 run。在使用 Lambda 表达式之前,我们要先分析一下,哪些是过程,哪些是结果;然后忽略过程,保留结果。
很明显,我们声明匿名类和声明 get 方法是不是都属于过程?而 get 方法中代码是不是就是我们想要得到的结果?然后前面我们也说到了,Lambda 表达式的形式为: (参数列表) -> {重写的逻辑}
因此,使用 Lambda 表达式后,代码就是如下:
public class SupplierDemo { /**/
public static void main(String[] args) { /**/
getMax(() -> { /**/
Integer[] integers = new Integer[]{1,8,6,2,3};
return integers[integers.length-1];
});
}
public static void getMax(Supplier<Integer> supplier) { /**/
Integer max = supplier.get();
System.out.println("Max 值是" + max);
}
}
代码是不是一下子就简洁了许多?这就是 Lambda 表达式的强大之处。
此外,如果重写的方法,代码只有一行,我们也可以忽略掉花括号。
假如有如下代码:
public class SupplierDemo {/**/
public static void main(String[] args) {/**/
getMax(new Supplier<Integer[]>() {/**/
@Override
public Integer[] get() {/**/
return new Integer[]{1,8,6,2,3};
}
});
}
public static void getMax(Supplier<Integer[]> supplier) {/**/
Integer[] arrays = supplier.get();
System.out.println("数组:" + Arrays.toString(arrays));
}
}
使用 Lambda 表达式后,则是
public class SupplierDemo {/**/
public static void main(String[] args) {/**/
getMax(() -> new Integer[]{1,8,6,2,3});
}
public static void getMax(Supplier<Integer[]> supplier) {/**/
Integer[] arrays = supplier.get();
System.out.println("数组:" + Arrays.toString(arrays));
}
}
后面的博文中,我们还会讲到 Stream 流和方法引用,它们与 Lambda 配合使用,将会非常强大。
