一、Lambda表达式
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)
1、Lambda表达式的使用
1)举例: (o1,o2) -> Integer.compare(o1,o2);
2)格式:
-> :lambda操作符 或 箭头操作符
->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
-> 右边:lambda体 (其实就是重写的抽象方法的方法体)
3)Lambda表达式的本质:作为函数式接口的实例
4)如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
5)所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
2、Lambda表达式的6种语法
语法格式一:无参,无返回值
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门"); //我爱北京天安门
}
};
r1.run();
Runnable r2 = () -> System.out.println("我爱北京故宫"); //我爱北京故宫
r2.run();
语法格式二:一个参数,无返回值
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("谎言和誓言的区别是什么?"); //谎言和誓言的区别是什么?
Consumer<String> con1 = (String s) -> System.out.println(s);
con1.accept("都当真了"); //都当真了
语法格式三:类型推断
数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
Consumer<String> con1 = (String s) -> {
System.out.println(s);
};
con1.accept("当真了");
Consumer<String> con2 = (s) -> System.out.println(s);
con2.accept("当真了");
语法格式四:只有一个参数
Lambda 若只需要一个参数时,参数的小括号可以省略
Consumer<String> con1 = (s) -> System.out.println(s);
con1.accept("当真了");
Consumer<String> con2 = s -> System.out.println(s);
con2.accept("当真了");
语法格式五:Lambda有多个参数
Lambda需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(com1.compare(12,21));
Comparator<Integer> com2 = (o1,o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(12,6));
语法格式六:Lambda体只有一条语句
当Lambda体只有一条语句时,return与大括号若有,都可以省略
Comparator<Integer> com1 = (o1,o2) -> {
return o1.compareTo(o2);
};
System.out.println(com1.compare(12,6));
Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
System.out.println(com2.compare(12,21));
语法总结:
->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
二、函数式(Functional)接口
1、函数式接口概述
1)函数式接口:只包含一个抽象方法的接口。可以通过Lambda表达式来创建该接口的对象。只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
2)可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。同时javado也会包含一条声明,说明这个接口是一个函数式接口。
3)在java.util.function包下定义Java 8的丰富的函数式接口
2、作为参数传递Lambda表达式
为了将Lambda表达式作为参数传递,接Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型。
3、Java 内置四大核心函数式接口
1)消费型接口 Consumer
public void test1(){
// 方式一:
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("价格为:" + aDouble);
}
});
// 方式二:
happyTime(400,money -> System.out.println("价格为:" + money));
}
public void happyTime(double money, Consumer<Double> con){
con.accept(money);
}
2)断定型接口 Predicate
public void test2(){
List<String> list = Arrays.asList("北京","南京","天津","东京","西京","普京");
// 方式一:
List<String> filterStrs = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
// 方式二:
List<String> filterStrs1 = filterString(list,s -> s.contains("京"));
}
//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){
ArrayList<String> filterList = new ArrayList<>();
for(String s : list){
if(pre.test(s)){
filterList.add(s);
}
}
return filterList;
}
三、方法引用与构造器引用
1、方法引用概述
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
如下三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
2、方法引用的三种使用情况
方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!(针对于情况1和情况2)
情况一:对象:: 实例方法(非静态方法)
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");
Consumer<String> con2 = System.out::println;
con2.accept("beijing");
}
//Supplier中的T get()
//Employee中的String getName()
public void test2() {
Employee emp = new Employee(1001,"Tom",23,5600);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
情况二:类:: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
public void test3() {
Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12,21));
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(12,3));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
public void test4() {
Function<Double,Long> func = new Function<Double, Long>() {
@Override
public Long apply(Double d) {
return Math.round(d);
}
};
Function<Double,Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(12.3));
Function<Double,Long> func2 = Math::round;
System.out.println(func2.apply(12.6));
}
情况三:类:: 实例方法(非静态方法)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
public void test5() {
Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc","abd"));
Comparator<String> com2 = String :: compareTo;
System.out.println(com2.compare("abd","abm"));
}
//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
public void test6() {
BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc","abc"));
BiPredicate<String,String> pre2 = String :: equals;
System.out.println(pre2.test("abc","abd"));
}
//Function中的R apply(T t)
//Employee中的String getName();
public void test7() {
Employee employee = new Employee(1001, "Jerry", 23, 6000);
Function<Employee,String> func1 = e -> e.getName();
System.out.println(func1.apply(employee));
Function<Employee,String> func2 = Employee::getName;
System.out.println(func2.apply(employee));
}
3、构造器引用
4、数组引用
四、强大的Stream API
1、Stream API概述
1)Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,Stream讲的是计算!”
2)Stream和Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算
注意:
① Stream自己不会存储元素,数据仍在集合中
② Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③ Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
3)Stream 的操作三个步骤
1- 创建Stream:一个数据源(如:集合、数组),获取一个流
2- 中间操作:一个中间操作链,对数据源的数据进行处理
3- 终止操作(终端操作) 一旦执行终止操作,就执行中间操作链,并产生结果。之后不会再被使用
2、创建Stream的四种方式
@Data
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public Employee() {
System.out.println("Employee().....");
}
public Employee(int id) {
this.id = id;
System.out.println("Employee(int id).....");
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Employee employee = (Employee) o;
if (id != employee.id)
return false;
if (age != employee.age)
return false;
if (Double.compare(employee.salary, salary) != 0)
return false;
return name != null ? name.equals(employee.name) : employee.name == null;
}
@Override
public int hashCode() {
int result;
long temp;
result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
temp = Double.doubleToLongBits(salary);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
/**
* 提供用于测试的数据
public class EmployeeData {
public static List<Employee> getEmployees(){
List<Employee> list = new ArrayList<>();
list.add(new Employee(1001, "马化腾", 34, 6000.38));
list.add(new Employee(1002, "马云", 12, 9876.12));
list.add(new Employee(1003, "刘强东", 33, 3000.82));
list.add(new Employee(1004, "雷军", 26, 7657.37));
list.add(new Employee(1005, "李彦宏", 65, 5555.32));
list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
list.add(new Employee(1007, "任正非", 26, 4333.32));
list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
return list;
}
}
方式一:通过集合
List<Employee> employees = EmployeeData.getEmployees();
//返回一个顺序流
Stream<Employee> stream = employees.stream();
//返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
方式二:通过数组
int[] arr = new int[]{1,2,3,4,5,6};
// 调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001,"Tom");
Employee e2 = new Employee(1002,"Jerry");
Employee[] arr1 = new Employee[]{e1,e2};
Stream<Employee> stream1 = Arrays.stream(arr1);
方式三:通过Stream的of()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
方式四:创建无限流(了解)
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
// 遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
3、Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
3.1 筛选与切片
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> stream = list.stream();
// filter(Predicate p):接收Lambda,从流中排除某些元素。
// 1、查询员工表中薪资大于7000的员工信息
stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
// limit(n):截断流,使其元素不超过给定数量
// 2、查询前三条员工信息
list.stream().limit(3).forEach(System.out::println);
// skip(n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
// 3、跳过前三条数据,查询后面的员工信息
list.stream().skip(3).forEach(System.out::println);
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",41,8000));
// distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
// 4、数据按照hashCode() 和 equals()去重
list.stream().distinct().forEach(System.out::println);
3.2 映射
public void test2(){
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
// map(Function f):接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素
// 1、将list的元素全部大写后返回
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
// 2、获取员工姓名长度大于3的员工的姓名。
List<Employee> employees = EmployeeData.getEmployees();
Stream<String> namesStream = employees.stream().map(Employee::getName);
namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
System.out.println();
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println);
});
System.out.println();
// flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
characterStream.forEach(System.out::println);
}
// 将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> fromStringToStream(String str){ //aa
ArrayList<Character> list = new ArrayList<>();
for(Character c : str.toCharArray()){
list.add(c);
}
return list.stream();
}
3.3 排序
// sorted()——自然排序
List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
list.stream().sorted().forEach(System.out::println);
// 抛异常,原因:Employee没有实现Comparable接口
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out::println);
// 2、sorted(Comparator com)——定制排序
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted( (e1,e2) -> {
int ageValue = Integer.compare(e1.getAge(),e2.getAge());
if(ageValue != 0){
return ageValue;
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println);
4、Stream的终止操作
- 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,如:List、Integer,甚至是void
- 流进行了终止操作后,不能再次使用
4.1 匹配与查找
```java Listemployees = EmployeeData.getEmployees();
// allMatch(Predicate p):检查是否匹配所有元素 // 1、是否所有的员工的年龄都大于18 boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
// anyMatch(Predicate p):检查是否至少匹配一个元素 // 2、是否存在员工的工资大于10000 boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
// noneMatch(Predicate p)——检查是否没有匹配的元素 // 3、是否存在员工姓“雷” boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith(“雷”));
// findFirst:返回第一个元素
// 4、返回第一个元素
Optional
// findAny:返回当前流中的任意元素
// 5、返回当前流中的任意元素
Optional
List
// count:返回流中元素的总个数 // 1、count——返回流中元素的总个数 long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
// max(Comparator c):返回流中最大值
// 2、返回最高工资
Stream
// min(Comparator c)——返回流中最小值
// 3、返回最低工资的员工
Optional
// forEach(Consumer c):内部迭代 // 4、forEach——内部迭代 employees.stream().forEach(System.out::println);
//5、使用集合的遍历操作 employees.forEach(System.out::println);
<a name="fVnSu"></a>
#### 4.2 **归约**
```java
// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
// 练习1:计算1-10的自然数的和
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer::sum);
// reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
// 练习2:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
// Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
System.out.println(sumMoney.get());
4.3 收集
// collect(Collector c):将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
// 练习1:查找工资大于6000的员工,结果返回为一个List或Set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out::println);
Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
employeeSet.forEach(System.out::println);