Lambda表达式(Java8)

Lambda表达式

Lambda表达式简介

Lambda表达式是Java 8引入的一项重要的新特性,主要受到函数式编程思想的影响。函数式编程思想强调的是对数据进行操作的方式,而不关注具体的对象是什么。Lambda表达式可以被理解为一种匿名函数,它基于数学中的λ演算而得名,也可以称为闭包(Closure)。

  • 优点:简化代码,开发迅速,使得函数式编程更加方便。
  • 缺点:代码可读性变差,不容易进行调试。

Lambda表达式语法

Lambda表达式由Lambda参数、箭头符号(->)和方法体组成,语法如下

1
( paramaters ) -> { 主体部分 }
组成简介
paramatersLambda参数列表,可以有零个或多个参数,参数类型可以显式指定,也可以由编译器根据上下文自动推断。
如果没有参数,则可以使用空括号()表示;
如果只有一个参数,可以省略参数的括号;
如果有多个参数,需要使用括号()包裹,并用逗号将参数分隔。
->箭头操作符,可理解为“被用于”的意思,用于分隔参数列表与Lambda表达式的主体部分
主体部分可以是一个表达式,也可以是一个代码块。
如果是一个表达式,则可以省略return关键字,返回一个值或者什么都不返回,等同于方法的方法体;
如果是一个代码块,则需要使用花括号将多个语句括起来,并且需要显式使用return语句来返回值。

Lambda表达式省略规则

省略参数类型:如果可以通过上下文推断出参数的类型,可以省略参数的类型,如果需要省略,每个参数的类型都要省略。

1
2
3
4
5
6
7
8
9
// 完整写法
Function<Integer, String> converter = (Integer num) -> {
return Integer.toString(num);
};

// 参数类型可以被推断,省略参数类型
Function<Integer, String> converter = (num) -> {
return Integer.toString(num);
};

省略参数括号:当只有一个参数时,可以省略参数的括号。

1
2
3
4
5
6
7
8
9
// 完整写法
Consumer<String> printer = (String message) -> {
System.out.println(message);
};

// 只有一个参数时,省略参数括号
Consumer<String> printer = message -> {
System.out.println(message);
};

省略花括号:如果Lambda表达式只有一条语句,并且没有返回值,可以省略花括号。

1
2
3
4
5
6
7
// 完整写法
Runnable runnable = () -> {
System.out.println("Hello World");
};

// 只有一条语句,省略花括号
Runnable runnable = () -> System.out.println("Hello World");

省略return关键字:如果Lambda表达式只有一条语句,并且有返回值,可以省略return关键字。

1
2
3
4
5
6
7
// 完整写法
Function<Integer, Integer> square = (Integer num) -> {
return num * num;
};

// 只有一条返回语句,省略return关键字
Function<Integer, Integer> square = num -> num * num;

函数式接口

有且只有一个抽象方法的接口,称为函数式接口(Functional Interface)。在Java中,Lambda表达式是对函数式接口的一种简写方式,只有一个接口是函数式接口时,才能用Lambda表达式

内置四大核心函数式接口

在Java 8中,提供了四个内置的核心函数式接口,它们分别是:FunctionPredicateConsumerSupplier

函数式接口用途
Function<T,R>函数型接口,接受一个输入参数 T,并返回一个输出结果 R
Predicate<T>断定型接口。接受一个输入参数 T,并返回一个布尔值。
Consumer<T>消费型接口,接受一个输入参数 T,但没有返回值。
Supplier<T>供给型接口,返回类型为T的对象,包含方法:T get()

其他函数式接口

函数式接口用途
ToIntFunction<T>计算int值的函数
ToLongFunction<T>计算long值的函数
ToDoubleFunction<T>计算double值的函数
IntFunction<R>参数为int类型的函数
LongFunction<R>参数为long类型的函数
DoubleFunction<R>参数为double 类型的函数

自定义函数式接口

如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口。其中@FunctionalInterface注解用于检测接口是否是函数式接口,即检查接口是否只有一个抽象方法,如果有两个抽象方法,程序编译就会报错。

1
2
3
4
5
6
7
8
/**
* 自定义函数式接口
*/
@FunctionalInterface// 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface MyFunctionalInterface<T> {
// 注意:只能有一个抽象方法
T doSomething();
}

Lambda表达式六种使用情况

无参无返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
@FunctionalInterface // 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface NoParameterNoReturn {
// 无参无返回值(注意只能有一个抽象方法)
void methodExample();
}

public class LambdaExample {
public static void main(String[] args) {
// 使用匿名实现类实现接口,重写 methodExample() 方法
NoParameterNoReturn noParameterNoReturn1 = new NoParameterNoReturn() {
@Override
public void methodExample() {
System.out.println("无参数无返回值!");
}
};
noParameterNoReturn1.methodExample();

// 使用 Lambda 表达式实现接口,重写 methodExample() 方法
NoParameterNoReturn noParameterNoReturn2 = () -> System.out.println("无参数无返回值!");
noParameterNoReturn2.methodExample();
}
}

无参有返回值

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
// 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
@FunctionalInterface // 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface NoParameterReturn {
// 无参数有返回值(注意只能有一个抽象方法)
int methodExample();
}

public class LambdaExample {
public static void main(String[] args) {
// 使用匿名实现类实现接口,重写 methodExample() 方法
NoParameterReturn noParameterReturn1 = new NoParameterReturn() {
@Override
public int methodExample() {
System.out.println("无参数有返回值!");
return 100;
}
};
System.out.println("返回值:" + noParameterReturn1.methodExample());// 接收函数的返回值并打印

// 使用 Lambda 表达式实现接口,重写 methodExample() 方法
NoParameterReturn noParameterReturn = () -> {
System.out.println("无参数有返回值!");
return 100;
};
System.out.println("返回值:" + noParameterReturn1.methodExample());// 接收函数的返回值并打印
}
}

一参数无返回值

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
// 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
@FunctionalInterface // 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface OneParameterNoReturn {
// 一个参数无返回值(注意只能有一个抽象方法)
void methodExample(int num);
}

public class LambdaExample {
public static void main(String[] args) {
// 使用匿名实现类实现接口,重写 methodExample(int num) 方法
OneParameterNoReturn oneParameterNoReturn1 = new OneParameterNoReturn() {
@Override
public void methodExample(int num) {
System.out.println("一参无返回值,参数:" + num);
}
};
oneParameterNoReturn1.methodExample(100);


// 使用 Lambda 表达式实现接口,重写 methodExample(int num) 方法
OneParameterNoReturn oneParameterNoReturn = (int num) -> {
System.out.println("一参无返回值,参数:" + num);
};
oneParameterNoReturn.methodExample(100);
}
}

一参数有返回值

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
// 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
@FunctionalInterface // 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface OneParameterReturn {
// 一个参数有返回值(注意只能有一个抽象方法)
int methodExample(int num);
}

public class LambdaExample {
public static void main(String[] args) {
// 使用匿名实现类实现接口,重写 methodExample(int num) 方法
OneParameterReturn oneParameterReturn1 = new OneParameterReturn() {
@Override
public int methodExample(int num) {
System.out.println("一参有返回值,参数:" + num);
return num * 2;
}
};
System.out.println("返回值:" + oneParameterReturn1.methodExample(100));// 接收函数的返回值并打印

// 使用 Lambda 表达式实现接口,重写 methodExample(int num) 方法
OneParameterReturn oneParameterReturn2 = (int num) -> {
System.out.println("一参有返回值,参数:" + num);
return num * 2;
};
System.out.println("返回值:" + oneParameterReturn2.methodExample(100));// 接收函数的返回值并打印
}
}

多参数无返回值

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
// 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
@FunctionalInterface // 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface MoreParameterNoReturn {
// 多个参数无返回值(注意只能有一个抽象方法)
void methodExample(int num1, int num2);
}

public class LambdaExample {
public static void main(String[] args) {
// 使用匿名实现类实现接口,重写 methodExample(int num1, int num2) 方法
MoreParameterNoReturn moreParameterNoReturn1 = new MoreParameterNoReturn() {
@Override
public void methodExample(int num1, int num2) {
System.out.println("多参无返回值,参数一:" + num1 + ",参数二:" + num2);
}
};
moreParameterNoReturn1.methodExample(50, 50);

// 使用 Lambda 表达式实现接口,重写 methodExample(int num1, int num2) 方法
MoreParameterNoReturn moreParameterNoReturn2 = (int num1, int num2) -> {
System.out.println("多参无返回值,参数一:" + num1 + ",参数二:" + num2);
};
moreParameterNoReturn2.methodExample(50, 50);
}
}

多参有返回值

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
// 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
@FunctionalInterface // 检查是否为函数式接口,即检查接口是否只有一个抽象方法
interface MoreParameterReturn {
// 多参数有返回值(注意只能有一个抽象方法)
int methodExample(int num1, int num2);
}

public class LambdaExample {
public static void main(String[] args) {
// 使用匿名实现类实现接口,重写 methodExample(int num1, int num2) 方法
MoreParameterReturn moreParameterReturn1 = new MoreParameterReturn() {
@Override
public int methodExample(int num1, int num2) {
System.out.println("多参无返回值,参数一:" + num1 + ",参数二:" + num2);
return num1 + num2;
}
};
System.out.println("返回值:" + moreParameterReturn1.methodExample(50, 50));// 接收函数的返回值并打印

// 使用 Lambda 表达式实现接口,重写 methodExample(int num1, int num2) 方法
MoreParameterReturn moreParameterReturn2 = (int num1, int num2) -> {
System.out.println("多参无返回值,参数一:" + num1 + ",参数二:" + num2);
return num1 + num2;
};
System.out.println("返回值:" + moreParameterReturn2.methodExample(50, 50));// 接收函数的返回值并打印
}
}

Lambda表达式方法引用

方法引用是一种简化 Lambda 表达式书写的语法,它提供了一种直接访问类或对象中已有方法的能力

方法引用形式一:静态方法引用

使用类名来引用静态方法,语法如下

1
类名::静态方法名

代码示例

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
public class LambdaExample {
/**
* 静态方法引用语法:类名::静态方法名
*/
@Test
public void methodExample() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Lambda表达式写法
numbers.forEach((number) -> MyMathUtils.printSquare(number)); // 输出每个数字的平方

// 等价的静态方法引用写法
numbers.forEach(MyMathUtils::printSquare); // 输出每个数字的平方
}

static class MyMathUtils {
/**
* 静态方法,打印一个整数的平方
*
* @param number 整数
*/
static void printSquare(int number) {
System.out.println(number * number);
}
}
}

方法引用形式二:实例方法引用

使用实例来引用实例方法,语法如下

1
实例对象::实例方法名(非静态方法名)

代码示例

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
public class LambdaExample {
/**
* 实例方法引用语法:对象::实例方法名(非静态方法名)
*/
@Test
public void methodExample() {
// Lambda表达式写法

// 等价的实例方法引用写法
Person person = new Person("Alice");
Consumer<Person> greeting = Person::sayHello;
greeting.accept(person); // 输出:Hello, Alice
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Person {
private String name;

public void sayHello() {
System.out.println("Hello, " + name);
}
}
}

方法引用形式三:对象方法引用

使用类名来引用实例方法,语法如下

1
类名::实例方法名(非静态方法名)

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LambdaExample {
/**
* 对象方法引用语法:类名::实例方法名(非静态方法名)
*/
@Test
public void methodExample() {
// Lambda表达式写法
Consumer<String> printMessage = message -> System.out.println(message);
printMessage.accept("Hello World!");// 输出 "Hello World!"

// 等价的对象方法引用写法
Consumer<String> printUpperCase = System.out::println;
printUpperCase.accept("Hello World!");// 输出 "Hello World!"
}
}

方法引用形式四:构造方法引用

使用类名和 new 关键字来引用构造函数,语法如下

1
类名::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
public class LambdaExample {
/**
* 构造方法引用语法:类名::new
*/
@Test
public void methodExample() {
// Lambda表达式写法
Supplier<Person> personSupplier1 = () -> new Person();// 使用无参构造方法创建Person对象
Person person1 = personSupplier1.get();// 获取Person对象
person1.setName("小明");// 设置Person对象的姓名为"小明"
System.out.println(person1.getName()); // 输出:Alice

// 等价的构造方法引用写法
Supplier<Person> personSupplier2 = Person::new;// 使用无参构造方法创建Person对象
Person person2 = personSupplier2.get();// 获取Person对象
person2.setName("小明");// 设置Person对象的姓名为"小明"
System.out.println(person2.getName()); // 输出:Alice
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Person {
private String name;
}
}

Stream流(Java8)

Stream流简介

(1)Stream流结合了Lambda表达式,用于简化集合、数组操作的API

(2)Stream 和 Collection 集合的区别

对比区别
Collection是一种静态的内存数据 结构,面向内存,存储在内存中
Stream用于操作数据源(集合、数组等)所生成的元素序列,面向 CPU,通过 CPU 实现计算

(3)Stream流使用流程

流程简介
创建流将数据源转换为Stream流
操作流中间操作链,对数据源的数据进行处理(过滤、聚合等)
结束流终止操作,执行中间操作链,并产生结果

Stream流创建的五种方式

通过集合创建Stream流(最常用)

通过集合创建Stream流(最常用),Java8 中的 Collection 接口被扩展,提供了两个获取流的方法

方法简介
stream()返回一个顺序流
parallelStream()返回一个并行流
1
2
3
4
5
6
7
8
9
10
11
@Test
public void test1() {
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
// 根据集合创建顺序流
Stream stream1 = list.stream();
// 根据集合创建并行流
Stream stream2 = list.parallelStream();
}

通过数组创建Stream流

通过数组创建Stream流,Java8 中的 Arrays 的静态方法 stream() 可以获取数组流

方法简介
stream(T[] array)返回一个流
1
2
3
4
5
6
@Test
public void test2() {
int[] ints = {};
// 根据int数组创建流
IntStream stream = Arrays.stream(ints);
}

通过Stream本身创建Stream流

通过Stream本身创建Stream流,调用Stream类静态方法 of(),通过显示值创建一个流,可以接收任意数量的参数

方法简介
of(T… values)返回一个流
1
2
3
4
5
@Test
public void test3() {
// 通过Stream本身创建Stream流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
}

通过函数创建Stream无限流

通过函数创建Stream无限流,使用静态方法 Stream.iterate() 和 Stream.generate()创建无限流

方法简介
iterate(final T seed, final UnaryOperatorf)迭代,指定一个常量seed,生成从seed到常量f的流
generate(Suppliers)生成,返回无限顺序无序流,其中每个元素由提供的供应商生成,适用于生成恒定流,随机元素流等
1
2
3
4
5
6
7
@Test
public void test4() {
// 迭代
Stream<Integer> iterate = Stream.iterate(0, n -> n + 2);
// 生成
Stream<Double> generate = Stream.generate(Math::random);
}

通过文件创建Stream流

通过文件创建Stream流,通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行

1
2
3
4
5
6
7
8
@Test
public void test5() {
try {
Stream<String> fileStream = Files.lines(Paths.get("test.txt"), Charset.defaultCharset());
} catch (IOException e) {
e.printStackTrace();
}
}

Stream流中间操作链

中间操作链简介

(1)通常对于Stream的中间操作,可以视为是源的查询,与数据库中视图的原理相似;

(2)Stream流的强大之处便是在于提供了丰富的中间操作,相比集合或数组这类容器,极大的简化源数据的计算复杂度

多个中间操作可以连接起来形成一个流水线,在终止操作时一次性全部处理,否则中间操作不会执行任何的处理,称为“惰性求值”

(3)一个流可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用,这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终止操作时

筛选与切片

流方法含义
filter(Predicate p)过滤,接收 Lambda,通过设置的条件,从流中排除某些元素
distinct()去重,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断,返回一个不超过给定长度的流
skip(long n)跳过,返回一个扔掉了前 n 个元素的流,若流中元素不足 n 个,则返回一 个空流,与 limit(n) 互补
peek(Predicate p)处理,对元素进行遍历处理
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
49
50
51
52
53
public class Demo {

/**
* filter(Predicate p):过滤,接收 Lambda,通过设置的条件,从流中排除某些元素
* 案例:输出ID大于5的user对象
*/
@Test
public void test1() {
List<User> userList = User.getUserList();
userList.stream().filter(user -> user.getId() > 5).forEach(System.out::println);
}

/**
* limit(long maxSize):截断,返回一个不超过给定长度的流
* 案例:输出前5个user对象
*/
@Test
public void test2() {
List<User> userList = User.getUserList();
userList.stream().limit(5).forEach(System.out::print);
}

/**
* skip(long n):跳过,返回一个扔掉了前 n 个元素的流,若流中元素不足 n 个,则返回一个空流,与 limit(n) 互补
* 案例:跳过前5个user对象
*/
@Test
public void test3() {
List<User> userList = User.getUserList();
userList.stream().skip(5).forEach(System.out::println);
}

/**
* distinct()去重,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
* 案例:对list集合进行去重
*/
@Test
public void test4() {
List<User> userList = User.getUserList();
userList.stream().distinct().forEach(System.out::println);
}

/**
* peek(Predicate p):处理,对元素进行遍历处理
* 案例:对元素进行遍历处理,每个用户年龄加1输出
*/
@Test
public void test6() {
List<User> userList = User.getUserList();
userList.stream().peek(user -> user.setId(user.getAge() + 1)).forEach(System.out::println);
}

}

映射

流方法含义
map(Function f)对流中每一个元素进行处理,返回一个值 接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素
flatMap(Function f)流扁平化,将一个整体拆成一个一个的个体,称为扁平化(拆分层级,放到同一层) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream。

map(Function f)与flatMap(Function f)的区别与应用场景

流方法本质区别
map(Function f)对一级元素进行操作,返回一个值
flatMap(Function f)对二级元素操作,返回一个流,多个值
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class Demo {

// 静态方法,用来返回 列表中列表 [[1,2,3],[1,2,3]]
private static List<List<Integer>> getList() {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<List<Integer>> lists = new ArrayList<>();
lists.add(integers);
lists.add(integers);
return lists;
}

/**
* map(Function f):对流中每一个元素进行处理
* 接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素
* 案例:将集合中的小写字符串转为大小
*/
@Test
public void test1() {
List<String> arrayList = Arrays.asList("a", "b", "c");
arrayList.stream().map(str -> str.toUpperCase()).forEach(System.out::print);
}

/**
* flatMap(Function f):流扁平化,将一个整体拆成一个一个的个体,称为扁平化(拆分层级,放到同一层)
* 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
* 案例1:拆分 ["Hello", "World"] 为 HelloWorld
*/
@Test
public void test2() {
String[] words = new String[]{"Hello", "World"};// ["Hello", "World"]
Arrays.stream(words)
.map(str -> str.split("")) // 按空格分隔字符,形成多个字符(H e l l o W o r l d)
.flatMap(
Arrays::stream // 获取内层数组的流对象
)// 生成的多个流被合并起来,即扁平化为一个流
.forEach(System.out::print);// 输出HelloWorld
}

/**
* flatMap(Function f):流扁平化,将一个整体拆成一个一个的个体,称为扁平化(拆分层级,放到同一层)
* 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
* 案例2:拆分 [[1,2,3],[1,2,3]] 为 123123
*/
@Test
public void test3() {
List<List<Integer>> lists = getList();// [[1,2,3],[1,2,3]]
lists.stream()
.flatMap(
item -> item.stream()// 获取内层数组的流对象
)// 将多个流扁平化为一个流
.forEach(System.out::print);
}

/**
* flatMap(Function f):流扁平化,将一个整体拆成一个一个的个体,称为扁平化(拆分层级,放到同一层)
* 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
* 案例3:拆分 用户集合列表 获取 集合中数组中的数据
*/
@Test
public void test4() {
List<User> userList = User.getUserList();
userList.stream()
.flatMap(
user -> user.getHobby().stream() // 获取数组的流对象
)// 将多个流扁平化为一个流
.forEach(System.out::print);
}

}

排序

流方法含义
sorted()自然排序,返回自然排序后的流
sorted(Comparator com)定制排序,返回按比较器排序后的流
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
public class Demo {

/**
* sorted():自然排序,返回自然排序后的流
* 案例:从小到大自然排序
*/
@Test
public void test1() {
List<Integer> list = Arrays.asList(3, 1, 4, 2, 5);
list.stream().sorted().forEach(System.out::print);// 12345
}

/**
* sorted(Comparator com):定制排序,按比较器排序后的流
* 案例:按用户年龄排序
*/
@Test
public void test2() {
List<User> userList = User.getUserList();
userList.stream()
.sorted((a, b) -> Integer.compare(a.getAge(), b.getAge()))
.forEach(System.out::println);
}

}

Stream流终止操作

终止操作简介

(1)终止操作的执行,才会真正开始流的中间操作,会从流的中间操作链生成结果,其结果可以是任何不是流的值

(2)Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流

(3)一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。

匹配与查找

流方法含义
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
sum(Comparator c)求和,与maptoint中间操作结合使用
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)遍历流,Stream API内部进行迭代,若使用 Collection 接口需要用户去做迭代, 称为外部迭代。
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
public class Demo {

/**
* allMatch(Predicate p):检查是否匹配所有元素
* 案例:判断用户年龄是否都大于10岁
*/
@Test
public void test1() {
List<User> userList = User.getUserList();
boolean b = userList.stream()
.distinct()// 去重
.allMatch(user -> user.getAge() > 10);// 其余的用户年龄是否都大于10
System.out.println(b);
}

/**
* anyMatch(Predicate p):检查是否至少匹配一个元素
* 案例:是否有一个用户名称为小明
*/
@Test
public void test2() {
List<User> userList = User.getUserList();
boolean b = userList.stream()
.distinct()// 去重
.anyMatch(user -> user.getName().equals("小明"));
System.out.println(b);
}

/**
* anyMatch(Predicate p):检查是否至少匹配一个元素
* 案例:是否没有用户名称为小明
*/
@Test
public void test3() {
List<User> userList = User.getUserList();
boolean b = userList.stream()
.distinct()// 去重
.noneMatch(user -> user.getName().equals("小明"));
System.out.println(b);
}

/**
* findFirst():返回第一个元素
* 案例:返回第一个元素
*/
@Test
public void test4() {
List<User> userList = User.getUserList();
Optional<User> first = userList.stream()
.distinct()// 去重
.findFirst();// 返回第一个元素
System.out.println(first);
}

/**
* findAny():返回当前流中的任意元素
* 案例:返回当前流中的任意元素
* */
@Test
public void test5() {
List<User> userList = User.getUserList();
Optional<User> any = userList.parallelStream()
.distinct()// 去重
.findAny();// 返回当前流中的任意元素
System.out.println(any);
}

/**
* count():返回流中元素总数
* 案例:求总记录数
* */
@Test
public void test6() {
List<User> userList = User.getUserList();
long count = userList.parallelStream()
.distinct()// 去重
.count();// 求总记录数
System.out.println(count);
}

/**
*sum(Comparator c):求和,与maptoint中间操作结合使用
*案例:求和
* */
@Test
public void test7() {
List<User> userList = User.getUserList();
int sum = userList.parallelStream()
.mapToInt(User::getId)// 转为IntStream
.sum();// 求和
System.out.println(sum);
}

/**
*max(Comparator c):返回流中最大值
*案例:求最大值
* */
@Test
public void test8() {
List<User> userList = User.getUserList();
OptionalInt max = userList.parallelStream()
.mapToInt(User::getId)// 转为IntStream
.max();// 最大值
System.out.println(max);
}

/**
* min(Comparator c):返回流中最小值
* 案例:求最小值
*
* */
@Test
public void test9() {
List<User> userList = User.getUserList();
OptionalInt max = userList.parallelStream()
.mapToInt(User::getId)// 转为IntStream
.min();// 最小值
System.out.println(max);
}

/**
* forEach(Consumer c):遍历流
* 案例:遍历打印
* */
@Test
public void test10() {
List<User> userList = User.getUserList();
userList.parallelStream()
.mapToInt(User::getId)// 转为IntStream
.forEach(System.out::println);// 遍历
}

}

规约

流方法含义
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional
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
public class Demo {

/**
* reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 T
* 案例:求和
*/
@Test
public void test1() {
List<Integer> list = Arrays.asList(1, 2, 3, 3);
Integer reduce = list.stream()
.distinct()// 去重 1,2,3
.reduce(5, Integer::sum);// 初始值1,求和,5+1+2+3

System.out.println(reduce);
}

/**
* reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
* 案例:求年龄之和
*/
@Test
public void test2() {
List<User> userList = User.getUserList();
OptionalInt reduce = userList.stream()
.mapToInt(User::getAge)// 转为IntStream
.reduce((a1, a2) -> a1 + a2);// 规约求年龄之和
System.out.println(reduce);
}

}

汇总

流方法含义
collect(Collector c)收集器,把Stream流操作后的结果数据转换为其他形式(集合、数组) 接收一个 Collector 接口的实现,用于给Stream中元素做汇总的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo {

/**
* collect(Collector c):收集器,把Stream流操作后的结果数据转换为其他形式(集合、数组)
* 接收一个 Collector 接口的实现,用于给Stream中元素做汇总的方法
* 案例:收集用户名到列表
*/
@Test
public void test() {
List<User> userList = User.getUserList();
List<String> collect = userList.stream()
.distinct()// 去重
.map(user -> user.getName())// 获取用户名
.collect(Collectors.toList());// 转换为数值
System.out.println(collect);
}

}

Collector收集策略接口

(1)Collector接口是结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力

(2)在Collectors工具中提供了Collector接口的实现类,可以方便地创建常见收集器实例

方法作用
toList把流中元素收集到List
toSet把流中元素收集到Set
toMap把流中元素收集到Map
toCollection把流中元素收集到创建的集合
counting计算流中元素的个数
summingInt对流中元素的整数属性求和
averagingInt计算流中元素Integer属性的平均值
summarizingInt收集流中Integer属性的统计值,平均值
joining连接流中每个字符串
maxBy根据比较器选择最大值
minBy根据比较器选择最小值
reducing从一个作为累加器的初始值开始, 利用BinaryOperator与流中元素逐个结合从而归约成单个值
collectingAndThen包裹另一个收集器,对其结果转 换函数
partitioningBy根据某属性值对流分组 属性为K, 结果为V
partitioningBy根据true或false进行分区

Optional类(Java8)

Optional类简介

(1)为了解决空指针异常,Google公司著名的Guava项目引入了Optional类, Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。

(2)受到Google Guava的启发,Optional类已经成为Java 8类库的一部分

(3)Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在,或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念,并且可以避免空指针异常

(4)Optional提供很多有用的方法,这样我们就不用显式进行空值检测

创建Optional类对象的方法

方法简介
Optional.of(T t)创建一个 Optional 实例,t必须非空;
Optional.empty()创建一个空的 Optional 实例
Optional.ofNullable(T t)t可以为null

判断Optional容器中是否包含对象

方法简介
boolean isPresent()判断是否包含对象
void ifPresent(Consumer consumer)如果有值,就执行Consumer 接口的实现代码,并且该值会作为参数传给它。

获取Optional容器的对象

方法简介
T get()如果调用对象包含值,返回该值,否则抛异常
T orElse(T other)如果有值则将其返回,否则返回指定的other对象。
T orElseGet(Supplier other)如果有值则将其返回,否则返回由 Supplier接口实现提供的对象。
T orElseThrow(Supplier exceptionSupplier)如果有值则将其返 回,否则抛出由Supplier接口实现提供的异常。