参考
官网文档:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
博客:https://www.cnblogs.com/franson-2016/p/5593080.html
菜鸟教程: https://www.runoob.com/java/java8-functional-interfaces.html
为什么要用Lambda表达式?
Lambda表达式可以让我们用较少的代码来实现更多的功能,尤其在Java这种相对来说比较“啰嗦”的语言中,是一把提高效率的利器。
演变过程
把官网上提供的demo拿过来
实体类
@Data @AllArgsConstructor public class Person { public enum Sex { MALE,FEMALE; } private String name; private LocalDate birthday; private Sex gender; private String emailAddress; private Integer age; public void printPerson() { System.out.println(this.toString()); } }
需求
现在我们需要实现一个功能,按照某种标准,从Person的集合中,挑选出符合条件的对象。
写法一
/** * 第一种写法,所有的判断方法耦合在一起 */ public class PrintTest { //大于指定年龄即可 public static void printPersonOlderTan(List<Person> roster, int age) { for (Person person : roster) { if(person.getAge() > age) { person.printPerson(); } } } //在某个年龄区间内 public static void printPersonWithinAgeRange (List<Person> roster, int low, int high) { for (Person person : roster) { if(low <= person.getAge() && person.getAge() < high) { person.printPerson(); } } } }
根据不同的要求写多个方法,不够灵活,重复代码较多。
写法二
上述代码存在的问题是,如果筛选的条件改变了,我们就需要写一个新的方法。
所以,思考一下我们可不可以把变化的部分单独拿出来。
public interface CheckPerson { boolean test(Person person); }
public class CheckPersonEligibleForSelectiveService implements CheckPerson{ @Override public boolean test(Person person) { return person.getGender() == Person.Sex.MALE && person.getAge() >= 17 && person.getAge() <= 25; } }
/** * 将判断条件抽象成方法 */ public class PrintPerson { public static void printPerson(List<Person>roster, CheckPerson tester) { for (Person person : roster) { if(tester.test(person)) { person.printPerson(); } } }
将筛选条件封装成表示算法的类,当有新的需求下来的时候,可以根据不同的需要编写对应的类,实现 CheckPerson接口,重写test方法即可。
将不同的算法封装起来,实现同一个接口,利用java的多态,根据不同的需求指定对应的算法,这就是策略设计模式。
调用该方法
public class PrintPersonTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 12); Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 13); Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 14); List<Person> rosters = Arrays.asList(person1, person2, person3); //实例化算法类 CheckPersonEligibleForSelectiveService checkService = new CheckPersonEligibleForSelectiveService(); PrintPerson.printPerson(rosters, checkService) } }
写法三
有一种算法就需要定义一个类,如果说这个算法经常使用,倒还是值得的。
但如果这个算法只用一次,还要特地定义一个类,未免有些太不划算了。
所以这个时候就想起了jdk1.8之前就存在的匿名内部类。
public class PrintPersonTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 12); Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 13); Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 14); List<Person> rosters = Arrays.asList(person1, person2, person3); //匿名内部类 PrintPerson.printPerson(rosters, new CheckPerson() { @Override public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } }); } }
写法四
Lambda表达式可以在匿名类的基础上,进一步简化写法。
public class PrintPersonTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 12); Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 13); Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 14); List<Person> rosters = Arrays.asList(person1, person2, person3); //lambda表达式 PrintPerson.printPerson(rosters, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25); } }
Lambda写法汇总
没有参数
public interface SayHello0 { void say(); }
/** * 函数没有参数 */ public class SayTest { public static void main(String[] args) { //写法一 SayHello0 sayHello0 = ()->{ System.out.println("hello"); }; //写法二,可以省略大括号 SayHello0 sayHello01 = () -> System.out.println("hello2") ; //方法里面的内容多余一行的时候,必须要加大括号 SayHello0 sayHello02 = () -> { System.out.println("hello3"); System.out.println("hello3"); }; } }
一个参数
public interface SayHello1 { void say(String msg); }
/** * 函数有1个参数 */ public class SayTest1 { public static void main(String[] args) { //写法一 SayHello1 sayHello1 = (msg)->{ System.out.println(msg); }; //写法二,简写 SayHello1 sayHello11 = msg -> System.out.println(msg); //写法三:再简写(这种写法的解释在后面) SayHello1 sayHello12 = System.out::println; } }
两个及以上
public interface SayHello2 { void say(String name, String msg); }
/** * 函数有2个参数 */ public class SayTest2 { public static void main(String[] args) { //多个参数时,小括号不能省略 SayHello2 sayHello2 = (name, msg) -> { System.out.println("name" + "msg"); }; } }
System.out::println
双冒号(::)也是1.8才有的运算符,用于lambda表达式中。
看一个demo
public class LambdaTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 10); Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 40); Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 35); Person person4 = new Person("test4", null, Person.Sex.MALE, "qwert@gmail.com", 28); Person person5 = new Person("test5", null, Person.Sex.MALE, "qwert@gmail.com", 91); List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5); // personList.forEach(person -> System.out.println(person)); personList.forEach(System.out::println); } }
顺序输出集合中的所有元素
personList.forEach(person -> System.out.println(person));
personList.forEach(System.out::println);
这两种写法的效果是等价的,
先看forEach()方法需要的参数是什么
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
Consumer是java8的内置函数式接口
@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); }; } }
标注了@FunctionalInterface接口,符合函数式接口的规范。函数式接口要求接口只能有一个方法,这里的接口为啥会有两个呢?
第二个方法有个default关键字,所以不需要给他初始化,当你不实现他的时候,jvm会采用默认的实现方式。
。。。。。。
所以这个forEach方法是干嘛的
forEach遍历集合,把每一个元素都传给一个方法处理,而这个方法是用Consumer来接收的,所以必须符合Lambda表达式的规范。
所以
personList.forEach(System.out::println);
含义是,将personList里面的每一个元素都传给System.out这个对象的println方法,供这个方法消费(consumer)。
自定义一个类,来说明这么写的含义,可能会更好一点。
public class ShowService { public void show(Person person) { System.out.println(person); } }
测试类
public class LambdaTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 10); Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 40); Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 35); Person person4 = new Person("test4", null, Person.Sex.MALE, "qwert@gmail.com", 28); Person person5 = new Person("test5", null, Person.Sex.MALE, "qwert@gmail.com", 91); List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5);
ShowService showService = new ShowService(); personList.forEach(showService::show); //或者把show改为静态方法 public static void show(Person person) // personList.forEach(ShowService::show); } }
至于forEach以及Stream等java8新特性,后面会陆续总结。
内置函数式接口
1.8新增的内置函数接口有很多,在java.util.function这个包下。
可以参考:.https://www.runoob.com/java/java8-functional-interfaces.html
我们可以根据具体的需求,去使用不同的接口
例如
消费型接口(Consumer),有参数,返回值
public void test01(){ //Consumer Consumer<Integer> consumer = (x) -> System.out.println("消费型接口" + x); //test consumer.accept(100); }
提供型接口(supplier),无参数,有返回值
public void test02(){ List<Integer> list = new ArrayList<>(); List<Integer> integers = Arrays.asList(1,2,3); list.addAll(integers); //Supplier<T> Supplier<Integer> supplier = () -> (int)(Math.random() * 10); list.add(supplier.get()); System.out.println(supplier); for (Integer integer : list) { System.out.println(integer); } }
函数型接口 Function<K,V>,有参数,有返回值
public void test03(){ //Function<T, R> String oldStr = "abc123456xyz"; Function<String, String> function = (s) -> s.substring(1, s.length()-1); //test System.out.println(function.apply(oldStr)); }
断言型接口 (Predicate),有参数,返回值为布尔型
public void test04(){ //Predicate<T> Integer age = 35; Predicate<Integer> predicate = (i) -> i >= 35; if (predicate.test(age)){ System.out.println("你该退休了"); } else { System.out.println("我觉得还OK啦"); } }
本节demo来自:https://blog.csdn.net/weixin_45225595/article/details/106203264
实战
public class CompareTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "qwert@gmail.com", 10); Person person2 = new Person("test2", null, Person.Sex.MALE, "qwert@gmail.com", 40); Person person3 = new Person("test3", null, Person.Sex.MALE, "qwert@gmail.com", 35); Person person4 = new Person("test4", null, Person.Sex.MALE, "qwert@gmail.com", 28); Person person5 = new Person("test5", null, Person.Sex.MALE, "qwert@gmail.com", 91); //匿名内部类写法 List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5); Collections.sort(personList, new Comparator<Person>() { //从小到大排序 @Override public int compare(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } }); System.out.println(personList); //正则表达式写法 Collections.sort(personList, (p1, p2)->{ return p1.getAge() - p2.getAge(); }); //1.8以后的Comparator提供了专门的方法 Collections.sort(personList, Comparator.comparingInt(Person::getAge)); //还可以这么用,BiFunction也是内置函数 BiFunction<Person,Person, Integer> function= (p1, p2)-> p1.getAge() - p2.getAge(); function.apply(person1, person2); } }
如有错误,恳请批评指正!