Lambda表达式的前世今生(来历与概述)
Lambda表达式的前世------匿名类
以往,使用单一抽象方法的接口被用作函数类型。 它们的实例表示函数(functions)或行动(actions)。 自从 JDK 1.1 于 1997 年发布以来,创建函数对象的主要手段就是匿名类。匿名类,通俗地讲,就是没有类名,直接通过new关键字创建这个类的实例。下面是匿名类的一个例子:
java.util包中的Comparator接口
public interface Comparator<T> {int compare(T o1, T o2);
}
使用匿名类创建排序的比较方法(强制排序顺序):
Collections.sort(words, new Comparator<String>() {public int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}
});
匿名类适用于需要函数对象的经典面向对象设计模式,特别是策略模式,上面的匿名类是排序字符串的具体策略。 然而,匿名类确实过于冗长。
Lambda表达式的今生
在 Java 8 中,语言形式化了这样的概念,即使用单个抽象方法的接口是特别的,应该得到特别的对待。 这些接口现在称为函数式接口,并且该语言允许你使用lambda 表达式或简称 lambda 来创建这些接口的实例。 Lambdas 在功能上与匿名类相似,但更为简洁。 下面的代码使用 lambdas 替换上面的匿名类。清晰明了:
Collections.sort(words,(s1, s2) -> Integer.compare(s1.length(), s2.length()));
Lambda表达式语法
JDK1.8之后引入的一种语法,他的写法是使用一个->符号,箭头将Lambda表达式分为左右两部分,左边写的是实现的这个接口中的抽象方法中的形参列表,右边就是对抽象方法的处理;
(实现的这个接口中的抽象方法中的形参列表) -> 抽象方法的处理
具体写法
因为Lambda表达式的核心就是(实现的这个接口中的抽象方法中的形参列表) -> 抽象方法的处理
,因此根据形参列表与返回值的不同,Lambda表达式的具体写法也不相同
无返回值有形参的抽象方法
public class MyTest1 {public static void main(String[] args) {MyInterface myInterface = new MyInterface() {@Overridepublic void show(int a, int b) {System.out.println(a + b);}};myInterface.show(20, 30);//50//简写1:方法名可以自己推断出来MyInterface myInterface1 = (int a, int b) -> {System.out.println(a + b);};myInterface1.show(20, 40);//60//简写2:可以省略形参列表中的形参类型MyInterface myInterface2 = (a, b) -> {System.out.println(a + b);//70};myInterface2.show(20, 50);//简写3:如果抽象方法中只有一行代码,可以省略方法体的大括号,当然,如果不止一行,就不能省略MyInterface myInterface3 = (a, b) -> System.out.println(a + b);myInterface3.show(20, 60);//80}
}
public interface MyInterface {public abstract void show(int a,int b);
}
主要特点:
- 可以省略方法名,IDEA会帮你自动检测方法名;
- 可以省略方法中的形参类型;
- 如果对抽象方法的实现逻辑只有一行,可以省略方法体的大括号,当然如果不止一行,就不能省略了;
有返回值的抽象方法
public class MyTest2 {public static void main(String[] args) {MyInterface1 test1 = new MyInterface1() {@Overridepublic int test(int a, int b) {return a - b;}};System.out.println(test1.test(90, 8));//82//简写1:MyInterface1 test2 = (int a, int b) -> {return a - b;};System.out.println(test2.test(20, 10));//10//简写2:MyInterface1 test3 = (a, b) -> {return a - b;};System.out.println(test3.test(30, 10));//20//简写3:这个有返回值的方法,不能直接去掉大括号,还需要去掉return关键字MyInterface1 test4 = (a, b) -> a - b;System.out.println(test4.test(40, 10));//30}
}
public interface MyInterface1 {public abstract int test(int a,int b);
}
- 有返回值的方法,如果要去掉大括号,还需要去掉return关键字;
有一个形参的抽象方法
public class MyTest3 {public static void main(String[] args) {MyInterface2 myInterface = a -> a-20;myInterface.show(20);}
}
public interface MyInterface2 {public abstract int show(int a);
}
- 形参列表中只有一个参数,可以去掉形参的括号
Lambda表达式作为参数传递
参数的类型为函数式接口
import java.util.Arrays;
public class MyTest4 {public static void main(String[] args) {Integer[] ints = {89, 67, 23};Arrays.sort(ints, (o1, o2) -> o1-o2);System.out.println(Arrays.toString(ints));//[23, 67, 89]}
}
什么情况下使用
Lambda表达式不是万能的,他需要函数式接口的支持,只有当某个方法需要传入的参数类型为函数式接口或者作为方法返回值时,才能使用。
什么是函数式接口:
函数式接口的定义是: 只包含一个抽象方法的接口,称为函数式接口;
其实我们的Lambda表达式就是对函数式接口的一种简写方式,所以只有是函数式接口,我们才能用Lambda表达式;再换句话说,Lambda表达式需要函数式接口的支持,那函数式接口我们可以自己定义,当然JDK1.8也给我们提供了一些现成的函数式接口;
//自定义一个函数式接口
@FunctionalInterface//此注解用来表明这是一个函数式接口
public interface MyInterFace<T> {
//函数式接口只有一个抽象方法
void getValue(T t);
}
//自定义的函数式接口的Lambda表达式
MyInterFace<String> face=(x)->System.out.println(x);
- 可以通过 Lambda 表达式来创建该接口的对象,我们可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口;
- 为什么只能有一个抽象方法,如果有多个抽象方法,这个接口不是函数式接口,简写的时候省略了方法名,IDEA不能知道到底重写的是哪一个方法,不能推断出来;
- 注解写在接口声明上面,如果不报错,就不是函数式接口;
- JDK1.8之后,提供了很多函数式接口,作为参数传递;
常用的函数式接口
Java 8在java.util.function包下预定义了大量的函数式接口供我们使用
我们重点学习下面4个接口
- Supplier接口
- Consumer接口
- Predicate接口
- Function接口
Supplier接口
Supplier< T >:包含一个无参的方法
- T get():获得结果
- 该方法不需要参数,他会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
- Supplier< T >接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会产生什么类型的数据供我们使用
import java.util.function.Supplier;public class SupplierDemo {public static void main(String[] args) {String s = getString(()->"张三");int i = getInteger(()->18);System.out.println(s+","+i);}//定义一个方法,返回一个字符串数据private static String getString(Supplier<String> sup){return sup.get();}//定义一个方法,返回一个整数数据private static Integer getInteger(Supplier<Integer> sup){return sup.get();}
}
Consumer接口
Consumer< T >:包含两个方法
- void accept(T t):对给定的参数执行此操作
- default Consumer < T > andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
- Consumer< T >接口也被称为消费型接口,它消费的数据类型由泛型指定
import java.util.function.Consumer;public class ConsumerDemo {public static void main(String[] args) {operatorString("张三", (s) -> System.out.println(s));operatorString("张三", (s) -> System.out.println(s), (s)-> System.out.println(new StringBuilder(s).reverse().toString()));}//定义一个方法,消费一个字符串数据private static void operatorString(String name, Consumer<String> con) {con.accept(name);}//定义一个方法,用不同的方式消费同一个字符串两次private static void operatorString(String name, Consumer<String> con1,Consumer<String> con2) {
// con1.accept(name);
// con2.accept(name);//返回一个组合的Consumercon1.andThen(con2).accept(name);}
}
Predicate接口
Predicate< T >:常用的四个方法
- boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
- default Predicate< T > negate():返回一个逻辑的否定,对应逻辑非
- default Predicate< T > and():返回一个组合判断,对应短路与
- default Predicate< T > or():返回一个组合判断,对应短路或
- isEqual():测试两个参数是否相等
- Predicate< T >:接口通常用于判断参数是否满足指定的条件
import java.util.function.Predicate;public class ConsumerTest {public static void main(String[] args) {boolean string = chenkString("张三", s -> s.equals("张三"));System.out.println(string);boolean hello = chenkString("hello", s -> s.length() > 8, s -> s.length() < 18);System.out.println(hello);}//判定给定的字符串是否满足要求
// private static boolean chenkString(String s, Predicate<String> pre){
// return pre.test(s);
// }private static boolean chenkString(String s, Predicate<String> pre){return pre.negate().test(s);}// private static boolean chenkString(String s, Predicate<String> pre, Predicate<String> pre1){
// return pre.and(pre1).test(s);
// }private static boolean chenkString(String s, Predicate<String> pre, Predicate<String> pre1){return pre.or(pre1).test(s);}
}
Function接口
Runction<T,R>:常用的两个方法
- R apply(T t):将此函数应用于给定的参数
- default< V >:Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
- Function<T,R>:接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新值
import java.util.function.Function;public class ConsumerTest {public static void main(String[] args) {convert("18", s -> Integer.parseInt(s));convert(20, integer -> String.valueOf(integer + 18));convert("245", s -> Integer.parseInt(s), integer -> String.valueOf(integer + 18));} //定义一个方法,把一个字符串转换成int类型,在控制台输出private static void convert(String s, Function<String, Integer> fun) {int i = fun.apply(s);System.out.println(i);} //定义一个方法,把int类型数据加上一个整数之后,转换为字符串在控制台输出private static void convert(int i, Function<Integer, String> fun) {String s = fun.apply(i);System.out.println(s);} //定义一个方法,把一个字符串转换int类型,把int类型的数据加上一个整数后,转换成字符串在控制台输出private static void convert(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {String s1 = fun2.apply(fun1.apply(s));System.out.println(s1);}
}
方法引用
先来看一下什么是方法引用:
方法引用其实是Lambda表达式的另一种写法,当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用;
注意: 实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!
方法引用:使用操作符::将方法名和对象或类的名字分隔开来,三种主要使用情况为:
对象::实例方法
类::静态方法
类::实例方法
对象::实例方法
import java.util.function.Consumer;public class MyTest {public static void main(String[] args) {Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}};consumer.accept("aaaaaaaaaaaaaa");//aaaaaaaaaaaaaa//简写1:Consumer<String> consumer1 = (String s) -> {System.out.println(s);};consumer1.accept("abc");//abc//简写2:Consumer<String> consumer2 = (s) -> System.out.println(s);consumer2.accept("bcd");//bcd//简写3:Consumer<String> consumer3 = System.out::println;consumer3.accept("abc");//abc}
}
为什么可以写成上述方式?
因为:System.out.println(s);与void accept(String s)一样,都是使用s作为参数,返回值是void,因此就可以简写为简写3;
类::静态方法
import java.util.function.BinaryOperator;public class MyTest1 {public static void main(String[] args) {BinaryOperator<Double> operator = new BinaryOperator<Double>(){@Overridepublic Double apply(Double o, Double o2) {return Math.max(o,o2);}};System.out.println(operator.apply(2.13, 3.12));//3.12BinaryOperator<Double> operator2 = (o, o2) -> Math.max(o,o2);System.out.println(operator2.apply(2.13, 3.12));//3.12BinaryOperator<Double> operator3 = Math::max;Double max = operator3.apply(5.0, 20.0);System.out.println(max);//20.0}
}
因为Math.max()所需要的参数以及返回值与重写的accpet()一样,因此可以简写为类::静态方法
;
下面再举一个例子
import java.util.Comparator;
public class MyTest2 {public static void main(String[] args) {Comparator<Integer> comparator = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1,o2);}};System.out.println(comparator.compare(20, 12));//1Comparator<Integer> comparator1 = Integer::compareTo;System.out.println(comparator1.compare(20, 12));//1}
}
类::实例方法
import java.util.Comparator;
public class MyTest2 {public static void main(String[] args) {Comparator<String> comparator = new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.compareTo(o2);}};System.out.println(comparator.compare("20", "12"));//1Comparator<String> comparator1 = String::compareTo;System.out.println(comparator1.compare("20", "12"));//1}
}
为什么可以这样写?、
传递过来的两个参数,一个作为调用者,一个作为参数,这时候,使用类::实例方法
简写;
构造引用
格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
public class MyTest4 {public static void main(String[] args) {Student student = new Student("张三", 23);BiFunction<String, Integer, Student> function = new BiFunction<String, Integer, Student>() {@Overridepublic Student apply(String s, Integer integer) {return student;}};BiFunction<String, Integer, Student> function1 = (s, integer) -> student;BiFunction<String, Integer, Student> function2 = Student::new;}
}