前言
从java8出现以来lambda是最重要的特性之一,它可以让我们用简洁流畅的代码完成一个功能。 很长一段时间java被吐槽是冗余和缺乏函数式编程能力的语言,随着函数式编程的流行java8种也引入了 这种编程风格。
一、lambda简介
什么是lambda?
lambda表达式是一段可以传递的代码,它的核心思想是将面向对象中的传递数据变成传递行为。 我们回顾一下在使用java8之前要做的事,之前我们编写一个线程时是这样的:
Runnable r = new Runnable() { @Override public void run() { System.out.println("do something."); } }
我们也可以使用匿名内部类的方式实现该方法,设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部 类还是不够简便。 为了执行一个简单的任务逻辑,不得不加上 6 行冗繁的样板代码。那如果是lambda该怎么做?
Runnable r = () -> System.out.println("do something.");
嗯,这代码看起来很酷,你可以看到我们用()和->的方式完成了这件事,这是一个没有名字的函数,也没有人和参数,再简单不过了。 使用->将参数和实现逻辑分离,当运行这个线程的时候执行的是->之后的代码片段,且编译器帮助我们做了类型推导; 这个代码片段可以是用{}包含的一段逻辑。下面一起来学习一下lambda的语法。
基础语法
在lambda中我们遵循如下的表达式来编写:
expression = (variable) -> action
- variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
- action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。
可以看到Java中lambda表达式的格式:参数、箭头、以及动作实现,当一个动作实现无法用一行代码完成,可以编写 一段代码用{}包裹起来。
lambda表达式可以包含多个参数,例如:
int sum = (x, y) -> x + y;
这时候我们应该思考这段代码不是之前的x和y数字相加,而是创建了一个函数,用来计算两个操作数的和。 后面用int类型进行接收,在lambda中为我们省略去了return。
1.无参数,无返回值
() -> System.out.println("Hello Lambda!");
Runnable r1 = () -> System.out.println("Hello Lambda!");
2.有一个参数,并且无返回值
//只有一个参数是,参数括号可省略 (x) -> System.out.println(x)
Consumer<String> con = (x) -> System.out.println(x)
3.有两个以上的参数,有返回值
Comparator<Integer> com = (x, y) -> { System.out.println("函数式接口"); return Integer.compare(x, y); };
4.当 Lambda 体只有一条语句时,return 与大括号可以省略
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
函数式接口
函数式接口是只有一个方法的接口,用作lambda表达式的类型。前面写的例子就是一个函数式接口,来看看jdk中的Runnable源码
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
这里只有一个抽象方法run,实际上你不写public abstract也是可以的,在接口中定义的方法都是public abstract的。 同时也使用注解@FunctionalInterface告诉编译器这是一个函数式接口,当然你不这么写也可以,标识后明确了这个函数中 只有一个抽象方法,当你尝试在接口中编写多个方法的时候编译器将不允许这么干。
函数式接口大致分为以下几类:
1.消费型接口示例
public static void donation(Integer money, Consumer<Integer> consumer){ consumer.accept(money); } public static void main(String[] args) { donation(1000, money -> System.out.println("您一共消费"+money+"元")) ; }
2.供给型接口示例
public static List<Integer> supply(Integer num, Supplier<Integer> supplier){ List<Integer> resultList = new ArrayList<Integer>() ; for(int x=0;x<num;x++) resultList.add(supplier.get()); return resultList ; } public static void main(String[] args) { List<Integer> list = supply(10,() -> (int)(Math.random()*100)); list.forEach(System.out::println); }
3.函数型接口示例
转换字符串为Integer
public static Integer convert(String str, Function<String, Integer> function) { return function.apply(str); } public static void main(String[] args) { Integer value = convert("28", x -> Integer.parseInt(x)); }
4.断言型接口示例
筛选出只有2个字的水果
public static List<String> filter(List<String> fruit, Predicate<String> predicate){ List<String> f = new ArrayList<>(); for (String s : fruit) { if(predicate.test(s)){ f.add(s); } } return f; } public static void main(String[] args) { List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃"); List<String> newFruit = filter(fruit, (f) -> f.length() == 2); System.out.println(newFruit); }
默认方法
在Java语言中,一个接口中定义的方法必须由实现类提供实现。但是当接口中加入新的API时, 实现类按照约定也要修改实现,而Java8的API对现有接口也添加了很多方法,比如List接口中添加了sort方法。 如果按照之前的做法,那么所有的实现类都要实现sort方法,JDK的编写者们一定非常抓狂。
幸运的是我们使用了Java8,这一问题将得到很好的解决,在Java8种引入新的机制,支持在接口中声明方法同时提供实现。 这令人激动不已,你有两种方式完成 1.在接口内声明静态方法 2.指定一个默认方法。
我们来看看在JDK8中上述List接口添加方法的问题是如何解决的
default void sort(Comparator<? super E> c) { Object[] a = this.toArray(); Arrays.sort(a, (Comparator) c); ListIterator<E> i = this.listIterator(); for (Object e : a) { i.next(); i.set((E) e); } }
这个List接口的源码,其中加入一个默认方法default void sort(Comparator<? super E> c)。 在返回值之前加入default关键字,有了这个方法我们可以直接调用sort方法进行排序。
List<Integer> list = Arrays.asList(2, 7, 3, 1, 8, 6, 4); list.sort(Comparator.naturalOrder()); System.out.println(list);
Comparator.naturalOrder()是一个自然排序的实现,这里可以自定义排序方案。你经常看到使用Java8操作集合的时候可以直接foreach的原因也是在Iterable接口中也新增了一个默认方法:forEach,该方法功能和 for 循环类似,但是允许 用户使用一个Lambda表达式作为循环体。
方法引用和构造器引用
方法引用:
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致! )
方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。
先定义一个人的实体类:有以下属性:
public class Person { private String firstName, lastName, job, gender; private int salary, age; //省略构造方法/get/set }
如下三种主要使用情况:
(1)对象::实例方法
Consumer<String> consumer = System.out::println; Supplier<String> sup = () -> person.getFirstName(); System.out.println(sup.get()); Supplier<String> sup2 = person::getFirstName; System.out.println(sup2.get());
(2)类::静态方法
BiFunction<Double, Double, Double> fun2 = Math::max; System.out.println(fun2.apply(1.2, 1.5)); Comparator<Integer> com = Integer::compare;
(3)类::实例方法
BiPredicate<String, String> bp2 = String::equals; System.out.println(bp2.test("abc", "abc"));
构造器引用:构造器的参数列表,需要与函数式接口中参数列表保持一致!
Function<Integer, Integer[]> fun1 = Integer[]::new; //Person要有一个只有String参数的构造方法 Function<String, Person> fun2 = Person::new;
二、lambda实战
1.线程启动
new Thread( () -> { System.out.println("In Java8, Lambda expression rocks !!"); }).start();
2.对列表进行迭代
// 使用 lambda 表达式以及函数操作(functional operation) list.forEach((obj) -> System.out.println(obj)); // 在 Java 8 中使用双冒号操作符(double colon operator) list.forEach(System.out::println);
3.排序集合
omparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2)); list.sort(sortByName); list.forEach(System.out::println);
下面通过示范来加深熟悉lambda表达式的理解:测试类开始:
定义两个人的集合:
List<Person> javaProgrammers = new ArrayList<Person>() { { add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500)); add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600)); add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900)); add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300)); add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700)); add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000)); add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300)); } }; List<Person> phpProgrammers = new ArrayList<Person>() { { add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550)); add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200)); add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600)); add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000)); add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100)); add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300)); add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100)); add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000)); add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600)); add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800)); } };
输出php程序员名称:
System.out.println("所有php程序员的姓名:"); phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
给java程序员加薪(两种方式):
System.out.println("给程序员加薪 5% :"); javaProgrammers.forEach(e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary()));
System.out.println("给程序员加薪 5% :"); Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary()); javaProgrammers.forEach(giveRaise);
过滤月薪超过1400元的java程序员(两种方式):
System.out.println("过滤月薪超过 $1,400 的JAVA程序员:"); phpProgrammers.stream() .filter((p) -> (p.getSalary() > 1400)) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getSalary()));
System.out.println("过滤月薪超过 $1,400 的JAVA程序员:"); Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400); phpProgrammers.stream() .filter(salaryFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getSalary()));
过滤年龄大于 24岁且月薪在$1,400以上的女PHP程序员:
System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:"); Predicate<Person> ageFilter = (p) -> (p.getAge() > 25); Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400); Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender())); phpProgrammers.stream() .filter(ageFilter) .filter(salaryFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s %s; ", p.getGender(), p.getAge(), p.getFirstName()));
将java程序员根据工资排序:
System.out.println("Java programmers根据 salary 排序:"); javaProgrammers.stream() .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) ) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getSalary()));
找出工资最低和最高的java程序员:
System.out.println("工资最低的 Java programmer:"); Person personMin = javaProgrammers .stream() .min((p1, p2) -> (p1.getSalary() - p2.getSalary())) .get(); System.out.printf("Name: %s; Salary: $%,d.", personMin .getFirstName(), pers.getSalary());
System.out.println("工资最高的 Java programmer:"); Person person = javaProgrammers .stream() .max((p1, p2) -> (p1.getSalary() - p2.getSalary())) .get(); System.out.printf("Name: %s; Salary: $%,d.", person.getFirstName(), person.getSalary());
将PHP程序员的姓拼接成字符串:
System.out.println("将 PHP programmers 的 first name 拼接成字符串:"); String phpDevelopers = phpProgrammers .stream() .map(Person::getFirstName) .collect(Collectors.joining(" ; ")); System.out.println(phpDevelopers);
将java程序员的姓放到set中:
System.out.println("将 Java programmers 的 first name 存放到 Set:"); Set<String> javaDevFirstName = javaProgrammers .stream() .map(Person::getFirstName) .collect(Collectors.toSet());
将java程序员的姓放到Treeset中:
System.out.println("将 Java programmers 的 first name 存放到 TreeSet:"); TreeSet<String> javaDevLastName = javaProgrammers .stream() .map(Person::getLastName) .collect(Collectors.toCollection(TreeSet::new));
计算所有java程序员的工资 :
System.out.println("计算付给 Java programmers 的所有money:"); int totalSalary = javaProgrammers .parallelStream() .mapToInt(p -> p.getSalary()) .sum();
获得各种汇总数据。
// 接下来,我们可以访问这些方法,比如getMax, getMin, getSum或getAverage: //计算 count, min, max, sum, and average for numbers List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IntSummaryStatistics stats = numbers .stream() .mapToInt((x) -> x) .summaryStatistics(); System.out.println("List中最大的数字 : " + stats.getMax()); System.out.println("List中最小的数字 : " + stats.getMin()); System.out.println("所有数字的总和 : " + stats.getSum()); System.out.println("所有数字的平均值 : " + stats.getAverage())