【学习笔记】Java 8新特性

一、Lambda表达式

1. 基础知识

作用:用于表示匿名函数或闭包。

原因:在Java之前的版本中,函数不能作为参数也不能作为返回值。

重点:在Java中,Lambda表达式是对象(JS中是函数),它必须依赖于一类特殊的对象类型,函数式接口。

1
2
3
4
5
6
7
8
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.forEach(integer -> System.out.println(integer)); // 类型推断
list.forEach((Integer integer) -> System.out.println(integer)); // 申明了类型,便于可读性。
list.forEach(System.out::println); // 方法引用

//倒序排序
List<String> names = Arrays.asList("zhangsan","lisi","wangwu");
Collections.sort(names,(o1, o2)->o2.compareTo(o1));

2. 关于函数式接口FunctionalInterface

  1. 如果一个接口只有一个抽象方法,那么该接口是一个函数式接口。需要注意的是,因为FunctionalInterface是继承自Object类,所以重写Object类中的方法,并不会当成是新的方法。
  2. 如果我们在某个接口上声明了FunctionalInterface注解,那么编译器就会按照函数式接口定义来要求该接口。
  3. 如果某接口只有一个抽象方法,但我们并没有给该接口声明FunctionalInterface注解,那么编译器依旧会将该接口看作函数式接口。
  4. 我们可以通过 lambda 表达式, 函数引用,或者构造方法引用来创建它。

在Java 8 中,接口新增了默认方法与静态默认方法,即默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

1
2
3
4
5
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}

3. 练习:

1
2
3
4
5
6
7
new Thread(()-> System.out.println("hello");).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello1");
}
}).start();
1
2
3
4
5
6
7
8
9
10
11
// 1. 将list 数据全部转换成大写
list.forEach(item -> System.out.println(item.toUpperCase()));

// 2. 将list 数据全部转换成大写 并存入list2中
List<String> list2 = new ArrayList<>(); //diamond语法(类型推断)
list.forEach(item->list2.add(item.toUpperCase()));
System.out.println(list2);

// 3. Stream:返回一个串行流。stream是Collection 的默认方法,通过spliterator进行流的分割
//list.stream().map(item -> item.toUpperCase()).forEach(item -> System.out.println(item));
list.stream().map(String::toUpperCase).forEach(System.out::println);

4. Lambda结构

  • 参数:
    • 一个 Lambda 表达式可以有零个或多个参数。
    • 参数的类型既可以明确声明,也可以根据上下⽂文来推断。例例如: (int a)与(a)效果相同 。
    • 所有参数需包含在圆括号内,参数之间⽤用逗号相隔。例如: (a, b) 或 (int a, int b) 或 (String a, int b, float c)
    • 空圆括号代表参数集为空。例例如: () -> 42
    • 当只有⼀一个参数,且其类型可推导时,圆括号()可省略略。例例如: a -> return a*a
  • 语句:
    • Lambda 表达式的主体可包含零条或多条语句句。
    • 如果 Lambda 表达式的主体只有⼀条语句句,花括号{}可省略略。匿名函数的返回类型与该主体表达式一致 。
    • 如果 Lambda 表达式的主体包含⼀条以上语句句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。

5. 思想

  • 原来:我们在定义一个方法时,方法功能很明确。
    • 即在你写完后,已经明确功能。例如,找到大于4的方法,找到5的方法。
  • 现在:函数式编程,仅仅提供一个通用方法,具体做什么功能,需要调用者实现。
    • 即Predicate接口。传入 判断方法,判断Boolean值。

二、各类函数式接口:

1. Consumer接口

  • 它是一个函数式接口,接收一个单一的输入值,但是不返回 任何数据。
  • 它可能会修改传入的数据。
  • BiConsumer:传入两个参数,不返回值。
1
2
3
4
5
// 将list 数据全部转换成大写 并存入list2中
List<Integer> list = Arrays.asList(1,2,3,4);
List<Integer> list1 = new ArrayList<>();
list.forEach(item->list1.add(item+10));
System.out.println(list1);

2. Function接口

  • Function<T, R>:输入T,输出R。接收一个函数,生成一个结果。
  • 高阶函数:如果一个函数接收一个函数作为参数,或者返回一个函数作为返回值,那么该函数就叫做高阶函数。
  • R apply(T t);:将函数应用给T,返回R。
  • default <V> Function<V, R> compose(Function<? super V, ? extends T> before):先执行before,再执行apply。
  • default <V> Function<T, V> andThen(Function<? super R, ? extends V> after):先执行apply,再执行before。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1. R apply(T t):将函数应用给T,返回R。
public class FunctionTest {
public static void main(String[] args) {
FunctionTest test = new FunctionTest(); // 实例化对象
// 写法一:紧凑
int result = test.compute(1,value->2*value);// 使用Function,传递2*value的行为。
System.out.println(result);

// 写法二:当Lambda表达式很长的时,可以抽取出来。更加清楚。
Function<Integer,Integer> function = value->value*2;
int result1 = test.compute(1,function);
System.out.println(result);
}

public int compute(int a, Function<Integer,Integer> function){
int result = function.apply(a);
return result;
}
}

// 2. compose 与 andThen
public class FunctionTest2 {
public static void main(String[] args) {
FunctionTest2 test = new FunctionTest2();
System.out.println(test.compute(2,value->value*3,value->value*value));
System.out.println(test.compute2(2,value->value*3,value->value*value));
}

public int compute(int a, Function<Integer,Integer> function1,Function<Integer,Integer> function2){
return function1.compose(function2).apply(a);
}

public int compute2(int a, Function<Integer,Integer> function1,Function<Integer,Integer> function2){
return function1.andThen(function2).apply(a);
}
}

3. BiFunction接口

  • BiFunction<T, U, R>:输入T和U,返回R。即将Function改成了两个输入参数。
  • R apply(T t, U u):将函数应用给T和U,返回R。
  • default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after):先执行apply,再执行after(Function)。因为BiFunction返回一个值。
1
2
3
4
5
6
7
8
9
10
public class FunctionTest2 {
public static void main(String[] args) {
FunctionTest2 test = new FunctionTest2();
System.out.println(test.compute(1, 2, (v1, v2) -> v1 + v2));
}

public int compute(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
return biFunction.apply(a, b);
}
}

4. Predicate接口

  • test方法:根据给定的条件,判断真假,返回Boolean。
  • and方法:逻辑与操作。
  • negate方法:逻辑非操作。
  • or方法:逻辑或操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class PredicateTest2 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,0);

PredicateTest2 predicateTest2 = new PredicateTest2();
// 1.找到集合中所有的奇数
predicateTest2.conditionFilter(list,item->item%2==1);
// 2. 找出集合中所有的偶数。
predicateTest2.conditionFilterNegate(list,item->item%2==1);
// 3. 找到集合中所有大于5或是奇数的
predicateTest2.conditionFilterOr(list,item->item>5,item->item%2==1);
// 4. 找到集合中既是偶数又大于5的数。
predicateTest2.conditionFilterAnd(list,item->item%2==0,item->item>5);
}

public void conditionFilter(List<Integer> list, Predicate<Integer> predicate){
list.forEach(item->{
if (predicate.test(item)){
System.out.println(item);
}
});
}

// 与
public void conditionFilterAnd(List<Integer> list,Predicate<Integer> predicate,Predicate<Integer> predicate2){
list.forEach(item->{
if (predicate.and(predicate2).test(item)){
System.out.println(item);
}
});
}
// 或
public void conditionFilterOr(List<Integer> list,Predicate<Integer> predicate,Predicate<Integer> predicate2){
list.forEach(item->{
if (predicate.or(predicate2).test(item)){
System.out.println(item);
}
});
}
// 反
public void conditionFilterNegate(List<Integer> list,Predicate<Integer> predicate){
list.forEach(item->{
if (predicate.negate().test(item)){
System.out.println(item);
}
});
}
}

5. Supplier接口

  • 表示结果的供应者。
  • 不接受参数,但返回一个结果。
1
2
3
4
5
6
7
8
9
10
11
12
public class SupplierTest {
public static void main(String[] args) {
Supplier<Student> supplier = Student::new; // 与()->new Student()相同
System.out.println(supplier.get().toString());
System.out.println("---------------");
}
}
public class Student {
public int age =20;
public String name ="ZhangSan";
// getter、setter和toString方法
}

6. Optional接口

  • 解决空指针异常的问题。if (null.getxxx() != Person)
  • 基于值的类,不能序列化
1
2
3
4
5
6
7
8
Optional<String> op = Optional.of("abc");// of方法必须要有值,否则抛异常。
//Optional<String> op = Optional.ofNullable("abc");// ofNullable方法可以为空(返回empty)
//if (op.isPresent()) System.out.println(op.get());
op.ifPresent(System.out::println);//推荐,函数式编程。

Optional<String> optional = Optional.empty();
System.out.println(optional.orElse("aaa"));//如果optional不存在,执行else
System.out.println(optional.orElseGet(()->"bbb"));//同上,但采用的是supplier接口

重要的写法:

1
2
3
4
5
6
7
8
9
Employee employee = new Employee("aa");
Employee employee1 = new Employee("bb");
//Company company = new Company("company",Arrays.asList(employee,employee1));
Company company = new Company("company",Arrays.asList());

// 如果是getEmployees 中是一个空对象,则返回一个空的list,使得接受端不用判断它。
Optional<Company> optional = Optional.ofNullable(company);
List<Employee> employees = optional.map(Company::getEmployees).orElse(Collections.emptyList());
System.out.println(employees);

三、方法引用

方法引用实际上是Lambda表达式的一种语法糖,我们可以将方法引用看成一个方法引用的【函数指针】。

1
2
List<Integer> list = Arrays.asList(1,2,3);
list.forEach(System.out::println); // 等价于 list.forEach(item-> System.out.println(item));

方法引用共分为4类:

  1. 类名::静态方法名。
  2. 引用名(对象名)::实例方法名。
  3. 类名::实例方法名。
  4. 构造方法引用:类名::new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Student student1 = new Student("zhangsan",10);
Student student2 = new Student("lisi",90);
Student student3 = new Student("wangwu",50);
Student student4 = new Student("zhaoliu",40);
List<Student> students = Arrays.asList(student1,student2,student3,student4);
// 1. 类名::静态方法名。
// List 中的sort方法(升序)接受一个Comparator 函数时接口,
// Comparator 接口,比较两个参数,当第一个值大于第二值时返回正数,相等返回零,否则返回负数。
students.sort(Student::compareStudentByScore); // 等价于 students.sort((param1,param2)->Student.compareByScore(param1,param2));
students.forEach(student -> System.out.println(student.getScore()));

// 2. 引用名(对象名)::实例方法名。
StudentComparator studentComparator = new StudentComparator();
students.sort(studentComparator::compareStudentByScore);
students.forEach(student -> System.out.println(student.getScore()));

// 3. 类名::实例方法名。
students.sort(Student::compareByScore);// 即,param1.compareByScore(param2)
students.forEach(student -> System.out.println(student.getScore()));

// 4. 构造方法引用:类名::new
System.out.println(studentComparator.getString(String::new)); // 通过构造方法,创建一个String对象。

class Student{
private String name;
private int score;
// 1. 静态方法
public static int compareStudentByScore(Student student1,Student student2){
return student1.getScore()-student2.getScore();
}
// 3. 实例方法
public int compareByScore(Student student) {
return this.getScore() - student.getScore();
}
}

class StudentComparator {
// 2. 实例方法
public int compareStudentByScore(Student student1,Student student2){
return student1.getScore()-student2.getScore();
}
// 4. 构造方法引用:类名::new
public String getString(Supplier<String> supplier){
return supplier.get()+"test";
}
}

四、默认方法

保证向后兼容,为接口提供了新的功能。

五、Stream流

1. 基本概念

  • 用于对集合进行链式操作。
  • 流由三个部分组成:1. 输入源;2. 零个或多个中间操作;3. 终止操作。
    • 中间操作:会返回一个新的流,如filter进行过滤;map进行对象转换;sorted进行排序。
    • 终止操作:对流操作的一个结束动作,一般不返回或者非流的结果,如forEach,sum等。
  • 操作的分类:1.惰性求值(执行终止操作时);2. 及早求值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. filter(Predicate predicate) 根据 predicate 滤除数据
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,0);
list.stream().filter(item->item>5).forEach(System.out::print);
System.out.println("");
System.out.println("=========================================");
// 2. map(Function mapper) 根据 mapper 对数据进行指定操作
List<Integer> list1 = Arrays.asList(1,2,3);
list1.stream().map(item->item*item).forEach(System.out::print);
System.out.println("");
System.out.println("=========================================");
// 3. distinct 找到不重复的数据
List<String> list2 = Arrays.asList("A","A","B","B","C");
list2.stream().distinct().forEach(System.out::print);
System.out.println("");
System.out.println("=========================================");
// 4. sorted 排序
List<Integer> list3 = Arrays.asList(1,8,2,3);
list3.stream().sorted().forEach(System.out::print);
System.out.println("");
System.out.println("=========================================");

2. 创建不同类型的流

​ 可以从各种数据源中创建Stream,如List和Set,也可通过of创建。

1
2
3
Stream stream1 = Stream.of("hello","world"); // 创建方法 1
List<String> list = Arrays.asList(1,2,3,4);
Stream stream4 = list.stream();// 创建方法 2

3.流的处理顺序

  • 流具有延迟性,如果不遇到终止操作,不会执行任何代码。
1
2
3
4
5
6
7
8
9
// 不执行
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {
System.out.println("filter: " + s);
return true;});
// 执行
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {
System.out.println("filter: " + s);
return true;})
.forEach(s -> System.out.println("forEach: " + s));

4. 中间操作的顺序

  • 大部分方法是链式操作,少数方法是状态操作(如sorted)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 先map,再filter
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase(); // 转大写
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("A"); // 过滤出以 A 为前缀的元素
})
.forEach(s -> System.out.println("forEach: " + s)); // for 循环输出
// 先filter,再map
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s)
return s.startsWith("a"); // 过滤出以 a 为前缀的元素
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase(); // 转大写
})
.forEach(s -> System.out.println("forEach: " + s)); // for 循环输出
// 3. sorted 状态操作
Stream.of("d2", "a2", "b1", "b3", "c")
.sorted((s1, s2) -> {
System.out.printf("sort: %s; %s\n", s1, s2);
return s1.compareTo(s2); // 排序
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a"); // 过滤出以 a 为前缀的元素
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase(); // 转大写
})
.forEach(s -> System.out.println("forEach: " + s)); // for 循环输出

5. 数据的复用性

  • stream 是不能被复用的,一旦调用任何终端操作,流就会自动关闭。
  • 但是可以采用Supplier包装一下流,通过get()方法构建一个新的流。
1
2
3
Supplier<Stream<String>> streamSupplier = () -> Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok

6.高级使用

6.1 Collect

这是一个很有用的终端操作,可以将流中的元素转换成另一个不同的对象。例如List、Set或Map。

collect 接受入参为Collector(收集器),它由四个不同的操作组成:供应器(supplier)、累加器(accumulator)、组合器(combiner)和终止器(finisher)。

1
2
3
4
5
6
7
8
   
// 3. 流中的字符串转换成list
Stream<String> stream = Stream.of("hello","world","hello world");
List<String> list = stream.collect(Collectors.toList());// toSet()
/*首先创建一个结果集合(supplier:()->new ArrayList()),将附加的元素添加到中间结果中 (accumulator:(theList,item)->theList.add(item)),把中间生成的list添加到结果集合中(combiner:(theList1,theList2)->theList1.addAll(theList2))。
等价于 stream.collect(()->new ArrayList(),(theList,item)->theList.add(item),(theList1,theList2)->theList1.addAll(theList2));
等价于 stream.collect( ArrayList::new, ArrayList::add,ArrayList::addAll);
*/

6.2 FlatMap

将多个对象合并成一个对象。

1
2
3
// flatMap 将多个list进行合并
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2),Arrays.asList(3,4),Arrays.asList(5,6,7));
stream.flatMap(Collection::stream).forEach(System.out::print);

6.3 Reduce

规约操作可以将流的所有元素组合成一个结果。Java 8 支持三种不同的reduce方法。

  1. 将流中的元素规约成流中的一个元素。reduce(BinaryOperator<T> accumulator)

    筛选出年龄最大的那个人:persons.stream().reduce((p1, p2) -> p1.age > p2.age ? p1 : p2).ifPresent(System.out::println);

  2. 将流中元素聚合,构成新的数据。接受标识值和BinaryOperator累加器。`T reduce(T identity, BinaryOperator accumulator);

    persons.stream().reduce(new Person("", 0), (p1, p2) -> {p1.age += p2.age;p1.name += p2.name;return p1;});

  3. 接受三个参数:标识值,BiFunction累加器和类型的组合器函数BinaryOperator

    计算所有人的年龄总和计算所有人的年龄总和:Integer ageSum = persons.stream().reduce(0, (sum, p) -> sum += p.age, Integer::sum);

7. 并行流

使用parallelStream()代替Stream()可使用并行计算,大大提高效率。

六、Collecter

  1. collect:收集器
  2. Collector作为collect方法的参数。
  3. Collector是一个接口,它是一个可变的汇聚操作,将输入元素累积到一个可变的容器中;他会在所有的元素都处理完毕后,将累计的结果转换为一个最终表示(这是一个可选操作);它支持串行与并行两种方式执行。
  4. Collectors本身提供了关于Collector的常见汇聚实现,Collectors本身实际上是一个工厂。