Lambda表达式
为什么使用Lambda表达式:
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。它是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,极大地优化代码结构。
JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
Lambda表达式对接口的要求:
Lambda 表达式需要“函数式接口”的支持
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。一般地,函数式接口都使用注解 @FunctionalInterface 修饰,来检查是否是函数式接口。
Lambda表达式的基础语法:
JAVA8 中引入了一个新的操作符 “->” ,该操作符称为 “箭头操作符” 或 “Lambda操作符”。
箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需要执行的功能,即 Lambda 体
示例:
import org.junit.Test; import java.util.Comparator; import java.util.function.Consumer; /** * Lambda 表达式测试 */ public class TestLambda1 { /** * 语法格式一: 无参数,无返回值 */ @Test public void test1(){ int num = 0;//jdk 1.7 前,必须是 final,jdk 1.8 以后,无需手动加 final ,默认就是final 的 Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello World!" + num); } }; r.run(); System.out.println("-------------------------------"); Runnable r1 = () -> System.out.println("Hello Lambda !! " + num); r1.run(); } /** * 语法格式二:有一个参数,并且无返回值 */ @Test public void test2(){ // /* //用一个Java的工具类作为案例。 @FunctionalInterface //函数式接口的注解 public interface Consumer<T> { void accept(T t); } */ Consumer<String> con = new Consumer<String>() { @Override public void accept(String s) { System.out.println("匿名内部类方式输入的参数:"+s); } }; con.accept("Java 是最好的编程语言!"); System.out.println("-------------------------------"); Consumer<String> con1 = (s) -> System.out.println("Lambda 方式的输入参数:"+s); con1.accept("Java 是最好的编程语言!"); } /** * 语法格式三:若只有一个参数,小括号可以省略不写 */ @Test public void test3(){ Consumer<String> con1 = s -> System.out.println("Lambda 方式的输入参数:"+s); con1.accept("Java 是最好的编程语言!"); } /** * 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句,则可以加上 {} */ @Test public void test4(){ Comparator<Integer> com = (x,y) -> { System.out.println("Java 是最好的编程语言!"); return Integer.compare(x, y); }; System.out.println(com.compare(3,4)); } /** * 语法格式五:若 Lambda 体中只有一条语句, return 和 {} 都可以省略不写 */ @Test public void test5(){ Comparator<Integer> com = (x,y) -> Integer.compare(x, y); System.out.println(com.compare(3,4)); } /** * 语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM 编译器通过上下文推断出,数据类型,即“类型推断” */ @Test public void test6(){ Comparator<Integer> com = (Integer x,Integer y) -> Integer.compare(x, y); System.out.println(com.compare(3,4)); } }
类型推断:上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
Lambda表达式的应用示例:
示例1:对传入的参数,进行运算,在实际调用方法的时候才知晓具体做什么运算。
@FunctionalInterface public interface MyFun { /** * 根据传入的值,进行运算,返回运算后的值。 * @param i * @return */ public Integer getValue(Integer i); } public class TestLambda2 { public static void main(String[] args) { //先用匿名内部类的方式 MyFun mf = new MyFun() { @Override public Integer getValue(Integer i) { System.out.println("我是最原始的匿名内部类实现的运算(返回:参数 * 100)..."); return i * 100; } }; //对 20 进行 * 100 的运算 System.out.println(mf.getValue(20)); System.out.println("-------------------------------"); //转换成Lambda表达式方式 MyFun mf1 = (i) -> { System.out.println("Lambda 表达式实现运算(返回:参数 * 100)..."); return i * 100; }; System.out.println(mf1.getValue(20)); //Lambda表达式可以用最简便的代码,实现各种运算 //例: MyFun mf2 = i -> i + 2; MyFun mf3 = i -> i * 2; MyFun mf4 = i -> i / 2; MyFun mf5 = i -> i - 2; //等等.................... } }
示例2:用 Collections.sort() 方法对集合排序。
import lombok.Data; @Data public class Employee{ private String name; private Integer age; private Integer gender; } import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class TestLambda3 { public static void main(String[] args) { List<Employee> emps = new ArrayList<>(); emps.add(new Employee("张三",10,0)); emps.add(new Employee("李四",13,1)); emps.add(new Employee("王麻子",8,0)); emps.add(new Employee("王五",18,0)); emps.add(new Employee("赵六",13,0)); //原始方法排序 Collections.sort(emps, new Comparator<Employee>() { @Override public int compare(Employee o1, Employee o2) { //按照年龄排序 return o1.getAge() - o2.getAge(); } }); System.out.println(emps); System.out.println("-------------------------------"); //Lambda表达式方式 // 排序1:按照年龄排 Collections.sort(emps,(o1,o2)->o2.getAge() - o1.getAge()); System.out.println(emps); System.out.println("-------------------------------"); //排序2:如果年龄相同,则按照性别排序 Collections.sort(emps,(o1,o2)->{ if((o1.getAge() - o2.getAge()) == 0){ return o2.getGender() - o1.getGender(); } return o1.getAge() - o2.getAge(); }); System.out.println(emps); } }
当我们需要用Lambda表达式来实现一个自己的需求,就得自己去创建一个接口,这样还是比较麻烦的。实际上,我们完全可以不用自己创建接口,使用Java8自带的函数式接口来满足我们的需求。
JAVA8内置的四大函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer<T> 消费型接口 |
T | void | 对类型为 T 的对象应用操作,包含方法:void accept(T t); |
Supplier<T> 供给型接口 |
无 | T | 返回类型为T 的对象,包含方法:T get(); |
Function<T,R> 函数型接口 |
T | R | 对类型为 T 的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t); |
Predicate<T> 断定型接口 |
T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t); |
内置函数式接口示例:
import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; public class TestLambda4 { /** * Consumer<T> 消费型接口 */ @Test public void test1(){ happy(20000.0,m -> System.out.println("你要去做大保健,每次"+m+"元")); } public void happy(Double money, Consumer<Double> consumer){ consumer.accept(money); } /** * Supplier<T> 供给型接口 */ @Test public void test2(){ List<Integer> list = getNumList(25, () -> (int) (Math.random() * 100)); System.out.println(list); } /** * 获取指定数量的整数集合 * @param num * @param supplier * @return */ public List<Integer> getNumList(int num,Supplier<Integer> supplier){ List<Integer> list = new ArrayList<>(); for(int i=0;i<num ;i++){ Integer result = supplier.get(); list.add(result); } return list; } /** * Function<T, R> 函数型接口 */ @Test public void test3(){ String newStr = strHandler("\t\t\n Java 好牛逼呀 ", (str) -> str.trim()); System.out.println(newStr); String subStr = strHandler("Java is so so cool...", (str) -> str.toUpperCase()); System.out.println(subStr); } /** * 需求:用于处理字符串 * @param str * @param fun * @return */ public String strHandler(String str, Function<String, String> fun){ return fun.apply(str); } /** * Predicate<T> 断定型接口 */ @Test public void test4(){ List<String> list = Arrays.asList("Java", "Python", "Lambda", "I love u", "ok"); List<String> strList = filterStr(list, (s) -> s.length() > 4); System.out.println(strList); } /** * 返回满足条件的字符串集合 * @param list * @param pre * @return */ public List<String> filterStr(List<String> list, Predicate<String> pre){ List<String> strList = new ArrayList<>(); for (String str : list) { if(pre.test(str)){ strList.add(str); } } return strList; } }
其他函数式接口:
方法引用、构造器引用、数组引用:
方法引用:
若 Lambda 体中的内容有方法已经实现了,我们可以使用“方法引用”。可以理解为方法引用就是 Lambda 表达式的另外一种表现形式。
方法引用主要的三种语法格式:
对象 :: 实例方法名
类 :: 静态方法名
类 :: 实例方法名
注意:
1. Lambda 体中调用方法的 【参数列表】与【返回值类型】,要与 函数式接口中 抽象方法 的 【参数列表】和【返回值类型】保持一致。
2. 若 Lambda 参数列表中的 第一参数 是 实例方法的调用者,而 第二个参数 是 实例方法的 参数 时,则可以使用 类 :: 实例方法名(ClassName :: method)
示例:
import org.junit.Test; import java.util.Comparator; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Supplier; public class TestLambda5 { /** * 方法引用测试 对象 :: 实例方法名 */ @Test public void test1(){ Consumer<String> con = (str) -> System.out.println("我是参数:"+str); con.accept("Hello Lambda"); Consumer<String> con1 = System.out::println; con1.accept("Hello 方法引用..."); // 需要实现的抽象方法的参数列表和返回值:void accept(String str); accept() 方法的参数为 String ,返回值为 void //Lambda体中引用的方法的 参数列表 与 返回值类型 : System.out.println("");System.out 对象的 println() 方法的参数为 String , 返回值为 void //在上面这种情况下,(str) -> System.out.println("我是参数:"+str); 就可以直接写成 System.out::println } /** * 方法引用测试 对象 :: 实例方法名 */ @Test public void test2(){ Employee emp = new Employee("周三",22,1); Supplier<String> sup1 = ()-> emp.getName(); //Supplier<String> sup1 = emp :: getName; //与上面等价 System.out.println(sup1.get()); System.out.println("-------------------------------"); //Supplier<Integer> sup2 = () -> emp.getAge(); //与下面等价 Supplier<Integer> sup2 = emp::getAge; //sup2.get() 和 emp.getAge() 参数列表都为 Null ,返回值都是Integer ,所以可以用两种表现形式。 System.out.println(sup2.get()); } /** * 方法引用测试 类 :: 静态方法名 */ @Test public void test3(){ Comparator<Integer> com1 = (x,y) -> Integer.compare(x,y); System.out.println(com1.compare(10,20)); System.out.println("-------------------------------"); Comparator<Integer> com2 = Integer::compare; System.out.println(com2.compare(122,22)); } /** * 方法引用测试 类 :: 实例方法名 */ @Test public void test4(){ BiPredicate<String,String> bp1 = (x,y) -> x.equals(y); System.out.println(bp1.test("abc","abc")); System.out.println("-------------------------------"); BiPredicate<String,String> bp2 = String::equals; System.out.println(bp1.test("abc","abc")); } }
构造器引用:
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
格式:ClassName :: new
注意:
需要调用的 构造器 的 参数列表 要与 函数式接口 中 抽象方法 的 参数列表 保持一致!
示例:
import org.junit.Test; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; /** * 构造器 引用 测试 * 格式:ClassName :: new */ public class TestLambda6 { /** * 调用无参构造器 */ @Test public void test1(){ Supplier<Employee> supplier = () -> new Employee(); System.out.println(supplier.get()); System.out.println("-------------------------------"); Supplier<Employee> sup1 = Employee::new;//sup1.get() 没有参数,调用的new Employee() 也是无参构造器。 System.out.println(sup1.get()); } /** * 调用一个参数的构造器 */ @Test public void test3(){ Function<String,Employee> fun = (name) -> new Employee(name); System.out.println(fun.apply("张麻子")); System.out.println("-------------------------------"); Function<String,Employee> fun1 = Employee::new;//fun1.apply("") 有一个参数,调用的new Employee("")一个参数的构造器。 System.out.println(fun1.apply("王二麻子")); } /** * 调用两个参数的构造器 */ @Test public void test4(){ BiFunction<String,Integer,Employee> fun = (name,age) -> new Employee(name,age); System.out.println(fun.apply("张麻子",12)); System.out.println("-------------------------------"); BiFunction<String,Integer,Employee> fun1 = Employee::new;//fun1.apply("",int) 有两个参数,调用的new Employee("",int)两个参数的构造器。 System.out.println(fun1.apply("王二麻子",20)); } }
数组引用:
格式:type[] :: new
示例:
/** * 数组引用 */ @Test public void test5() { Function<Integer, String[]> fun1 = (x) -> new String[x]; System.out.println(fun1.apply(10).length); System.out.println("-------------------------------"); Function<Integer, String[]> fun2 = String[]::new; System.out.println(fun1.apply(20).length); }