Lambda表达式
函数式编程思想
之前我们学习的Java都是面向对象的思想(重点是做某一件事的执行者,谁去做),而函数式的编程思想由重心点(谁去做)变成了做事的过程。
一个接口只有一个抽象方法的话:
- 我们之前
- 要么是声明一个类去实现接口重写这个抽象方法,再去创建这个类去调用方法
- 要么是使用匿名内部类,重写里面的方法再去调用方法
无论哪一种方式都需要去创建一个对象,去new一个对象,而我们主要是想要去使用这个方法,方法里面只有实参和方法体不同。再去创建对象,写方法体里面的共同不变的部分就显的多余了
- 现在(通过函数式编程思想)
- 只需要使用这个方法
注意:
这里说的是一个接口只有一个抽象方法的情况
什么是Lambda表达式
Oracle所发布的JDK 1.8中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。Java8引入了Lambda表达式之后,Java也开始支持函数式编程。
Lambda表达式不是Java最早使用的,很多语言就支持Lambda表达式,例如:C++,C#,Python,Scala等。lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的(啰嗦的)匿名内部类。语法糖是指使用更加方便,但是原理不变的代码语法
函数接口的概念
-
SAM接口:就是Single Abstract Method接口
-
Lambda表达式就是一个实现了SAM接口的语法格式。
啥叫函数式接口呢?
其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式。
总而言之:只有一个抽象方法的接口(抽象方法只有一个,但可以有其他static、default方法)
备注:可以使用注解@FunctionalInterface来检查是不是函数式接口,检查接口里面是不是只有一个抽象方法。(和@Override类似)
之前学过的接口已经很多了:Cloneable、Comparable、Comparator、Runnable、Iterable、Iterator、Collection、List、Queue、Deque、Set、Map、Serializable、FileFilter、FilenameFilter等,但是满足SAM特征的接口不多。
虽然Iterable和Comparable接口也只有一个抽象方法,但它们没有@FunctionalInterface注解标记,因此不算。
序号 | 接口 | 抽象方法 | SAM接口 |
---|---|---|---|
1 | java.lang.Runnable | public void run() | |
2 | java.util.Comparator | public int compare(T t1, T t2) | |
3 | java.io.FileFilter | public boolean accept(File pathname) | |
4 | java.io.FilenameFilter | public boolean accept(File dir, String name) |
Lambda表达式语法
Lambda可以说就是简化函数式接口用的。将一些公共的部分给省略了,将必要的部分给保留下来。有参数和方法体。
Lambda表达式语法格式
(【形参列表】) -> {方法体}
语法格式说明:
- (【形参列表】)它就是你要传递给原函数式接口的参数列表
- {方法体}就是实现这个抽象方法的方法体
- ->称为Lambda操作符(减号和大于号中间不能有空格)
例如:
public class LambdaTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() { //原来的匿名内部类形势
@Override
public void run() {
System.out.println("Runnable");
}
};
Runnable runnable1 = () -> {System.out.println("Runnable1");}; //无参的Lambda表达式
Integer[] arr = {1,3,5,2,6,7};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
});
Arrays.sort(arr,(Integer o1, Integer o2) -> {return Integer.compare(o1,o2);}); //有参的Lambda表达式
for (Integer integer : arr) {
System.out.println("integer = " + integer);
}
}
}
Lambda表达式的简化
Lambda表达可以有以下几种的语法格式,根据不同的参数列表与方法体选择的简化格式也可以不同。
- 当{方法体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{、return、;}三者可以省略。它们三要么一起省略,要么都不省略。
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
- 当Lambda表达式(形参列表)是空参时,()不能省略
演示代码:
- 当{方法体}中只有一句语句时,可以省略{}和{;}
Runnable runnable2 = () -> System.out.println("Runnable1");
- 当{方法体}中只有一句语句时,并且这个语句还是一个return语句,那么{return;}三者可以省略。它们三要么一起省略,要么都不省略。
Integer[] arr = {1,3,5,2,6,7};
Arrays.sort(arr,(Integer o1, Integer o2) -> Integer.compare(o1,o2));
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
Integer[] arr = {1,3,5,2,6,7};
Arrays.sort(arr,( o1, o2) -> Integer.compare(o1,o2));
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
list.forEach(new Consumer<Integer>() { //使用匿名内部类重写accept方法
@Override
public void accept(Integer integer) {
System.out.println("Integer = " + integer);
}
});
list.forEach( integer -> System.out.println("integer = " + integer));//Lambda表达式
- 当Lambda表达式(形参列表)是空参时,()不能省略
Runnable runnable1 = () -> {System.out.println("Runnable1");}; //无参的Lambda表达式
Java8之后引入的函数式接口
Java8在java.util.function新增了很多函数式接口:主要分为四大类消费型
、供给型
、判断型
、功能型
。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。
1、消费型接口
消费型接口的抽象方法特点:有形参,但是返回值类型是void
序号 | 接口名 | 抽象方法 | 描述 |
---|---|---|---|
1 | Consumer | void accept(T t) | 接收一个对象用于完成功能 |
2 | BiConsumer<T,U> | void accept(T t, U u) | 接收两个对象用于完成功能 |
3 | DoubleConsumer | void accept(double value) | 接收一个double值 |
4 | IntConsumer | void accept(int value) | 接收一个int值 |
5 | LongConsumer | void accept(long value) | 接收一个long值 |
6 | ObjDoubleConsumer | void accept(T t, double value) | 接收一个对象和一个double值 |
7 | ObjIntConsumer | void accept(T t, int value) | 接收一个对象和一个int值 |
8 | ObjLongConsumer | void accept(T t, long value) | 接收一个对象和一个long值 |
已知在JDK1.8中java.lang.Iterable接口中增加了一个默认方法:
public default void forEach(Consumer<? super T> action)
该方法功能是遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。
因为Collection接口继承了Iterable接口,这就意味着所有Collection系列的接口都包含该方法。
public class TestConsumer {
public static void main(String[] args) {
List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
//匿名内部类形式
/*list.forEach(new Consumer<String>() {
@Override
public void accept(String string) {
System.out.println("String= " + string);
}
});*/
//Lambda表达式形式
list.forEach(string -> System.out.println(string ));
}
}
2、供给型接口
这类接口的抽象方法特点:无参,但是有返回值
序号 | 接口名 | 抽象方法 | 描述 |
---|---|---|---|
1 | Supplier | T get() | 返回一个对象 |
2 | BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
3 | DoubleSupplier | double getAsDouble() | 返回一个double值 |
4 | IntSupplier | int getAsInt() | 返回一个int值 |
5 | LongSupplier | long getAsLong() | 返回一个long值 |
public class TestSupplier {
public static void main(String[] args) {
//匿名内部类形式
/*Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "Java";
}
};*/
//Lambda表达式形式
Supplier<String> supplier2 = () -> "Java";
System.out.println("String = " + supplier2.get());
}
}
3、判断型接口
这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。
序号 | 接口名 | 抽象方法 | 描述 |
---|---|---|---|
1 | Predicate | boolean test(T t) | 接收一个对象 |
2 | BiPredicate<T,U> | boolean test(T t, U u) | 接收两个对象 |
3 | DoublePredicate | boolean test(double value) | 接收一个double值 |
4 | IntPredicate | boolean test(int value) | 接收一个int值 |
5 | LongPredicate | boolean test(long value) | 接收一个long值 |
已知:JDK1.8时,Collecton接口增加了一下方法,其中一个如下:
public default boolean removeIf(Predicate<? super E> filter)
用于删除集合中满足filter指定的条件判断的。
public class TestPredicate {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("study");
list.add("ok");
list.add("yes");
//匿名内部类形式
/*list.removeIf(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("o");
}
});*/
//Lambda表达式形式
list.removeIf(s->s.contains("o"));
list.forEach(s -> System.out.println("s = " + s));
}
}
4、功能型接口
这类接口的抽象方法特点:既有参数又有返回值
序号 | 接口名 | 抽象方法 | 描述 |
---|---|---|---|
1 | Function<T,R> | R apply(T t) | 接收一个T类型对象,返回一个R类型对象结果 |
2 | UnaryOperator | T apply(T t) | 接收一个T类型对象,返回一个T类型对象结果 |
3 | DoubleFunction | R apply(double value) | 接收一个double值,返回一个R类型对象 |
4 | IntFunction | R apply(int value) | 接收一个int值,返回一个R类型对象 |
5 | LongFunction | R apply(long value) | 接收一个long值,返回一个R类型对象 |
6 | ToDoubleFunction | double applyAsDouble(T value) | 接收一个T类型对象,返回一个double |
7 | ToIntFunction | int applyAsInt(T value) | 接收一个T类型对象,返回一个int |
8 | ToLongFunction | long applyAsLong(T value) | 接收一个T类型对象,返回一个long |
9 | DoubleToIntFunction | int applyAsInt(double value) | 接收一个double值,返回一个int结果 |
10 | DoubleToLongFunction | long applyAsLong(double value) | 接收一个double值,返回一个long结果 |
11 | IntToDoubleFunction | double applyAsDouble(int value) | 接收一个int值,返回一个double结果 |
12 | IntToLongFunction | long applyAsLong(int value) | 接收一个int值,返回一个long结果 |
13 | LongToDoubleFunction | double applyAsDouble(long value) | 接收一个long值,返回一个double结果 |
14 | LongToIntFunction | int applyAsInt(long value) | 接收一个long值,返回一个int结果 |
15 | DoubleUnaryOperator | double applyAsDouble(double operand) | 接收一个double值,返回一个double |
16 | IntUnaryOperator | int applyAsInt(int operand) | 接收一个int值,返回一个int结果 |
17 | LongUnaryOperator | long applyAsLong(long operand) | 接收一个long值,返回一个long结果 |
18 | BiFunction<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
19 | BinaryOperator | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
20 | ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
21 | ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
22 | ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | 接收一个T类型和一个U类型对象,返回一个long |
23 | DoubleBinaryOperator | double applyAsDouble(double left, double right) | 接收两个double值,返回一个double结果 |
24 | IntBinaryOperator | int applyAsInt(int left, int right) | 接收两个int值,返回一个int结果 |
25 | LongBinaryOperator | long applyAsLong(long left, long right) | 接收两个long值,返回一个long结果 |
已知,java.util.List接口在Java8版本中新增了一个方法:
- default void replaceAll(UnaryOperator operator)将该列表的每个元素替换为将该运算符应用于该元素的结果。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("study");
list.add("ok");
list.add("yes");
//匿名内部类形式
/*list.replaceAll(new UnaryOperator<String>() {
@Override
public String apply(String s) {
return s.substring(0,1).toUpperCase()+s.substring(1);
}
});*/
//Lambda表达式形式
list.replaceAll(s -> s.substring(0,1).toUpperCase()+s.substring(1));
list.forEach(s-> System.out.println("s = " + s));
}
自定义函数式接口
只要确保接口中有且仅有一个抽象方法即可:
@FunctionalInterface
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
接口当中抽象方法的 public abstract 是可以省略的
例如:声明一个计算器Calculator
<T,R>接口,内含抽象方法calculate
可以对两个参数进行计算,并返回结果。其中T是参数类型,R是返回值类型。
@FunctionalInterface
public interface Calculator<T,R> {
R calculate(T a, T b);
}
示例代码:
public class LambdaGrammarSimple {
@Test
public void test01() {
//使用Lambda表达式实现Calculator接口,求两个整数的和的功能
Calculator<Integer,Integer> c1 = (Integer a, Integer b) -> {return a+b;};
System.out.println(c1.calculate(5, 2));
Calculator<Integer,Integer> c2 = (Integer a, Integer b) -> a+b;
System.out.println(c2.calculate(5, 2));
Calculator<Integer,Integer> c3 = (a, b) -> a+b;
System.out.println(c3.calculate(5, 2));
}
}