Java8新特性
补下票好吧🫥,原谅我在学JavaSE的时候没有耐心听这些
Java8新特性的好处
- 速度更快
- 代码更少(Lambda简化代码书写)
- 强大的Stream API
- 便于并行
- 最大化减少空指针异常:
Optional - Nashorn引擎,允许在JVM上运行JS应用
1. 并行流与串行流
并行流就是把一个内容分为多个数据块,并用不同的线程分别处理每个数据块的流。
相比较于串行流,可以很大程度上提高程序的执行效率。Java8中将其进行了优化,我们可以很容易的对数据进行并行操作
Stream API可以声明式的通过
parallel()和sequential()在并行流和串行流之间切换
2. Lambda表达式
- Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样传递)。
- 使用它可以写出更简洁,更灵活的代码。作为一种更紧凑的代码风格,使Java语言表达能力得到了提升。
Lambda表达式使用举例
- 举例1:
如下,我们之前写一个运行功能需要写匿名内部类,现在我们使用Lambda表达式会更加简洁1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void test01(){
//1.之前写法写匿名内部类
Runnable runnable01 = new Runnable(){
public void run(){
System.out.println("World.search(you);");
}
};
//2. Lambda表达式
Runnable runnable02 = () -> {
System.out.println("World.execute(me);");
};
new Thread(runnable01).start();
new Thread(runnable02).start();
}
- 举例2:
- 第一种写法是用匿名内部类实现比大小,第二章lambda表达式更加优雅点
- 但是因为Integer包装类里极影存在了compareTo方法,所以可以直接用方法引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void test02(){
//1. 之前的写法
Comparator<Integer> comparator01 = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator01.compare(233, 514));
System.out.println("------------------------------------------------");
//2. Lambda表达式
Comparator<Integer> comparator02 = (o1, o2) -> o1.compareTo(o2);
System.out.println(comparator02.compare(233, 514));
System.out.println("------------------------------------------------");
//3. 引用Integer包装类里的compareTo方法
Comparator<Integer> comparator03 = Integer::compareTo;
System.out.println(comparator03.compare(233, 514));
}
Lambda表达式语法的使用
举例:
(01,02) -> Interger.compare(o1,o2)格式:
->: Lambda操作符(箭头操作符)->左边: Lambda形参列表(其实就是接口中的抽象方法的形参列表)->右边: Lambda体(重写的抽象方法的方法体)
Lambda表达式的使用(六种情况):
无参无返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public void test01(){
//1.之前写法写匿名内部类
Runnable runnable01 = new Runnable(){
public void run(){
System.out.println("World.search(you);");
}
};
//2. Lambda表达式
Runnable runnable02 = () -> {
System.out.println("World.execute(me);");
};
new Thread(runnable01).start();
new Thread(runnable02).start();
}Lambda需要一个参数,但没返回值
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
public void test03(){
//1. 以前的写法
Consumer<String> consumer01 = new Consumer<String>() {
public void accept(String s) {
System.out.println(s);
}
};
consumer01.accept("World.search(you);");
System.out.println("------------------------------------------------");
//2. Lambda表达式
Consumer<String > consumer02 = (String s) -> {
System.out.println(s);
};
consumer02.accept("World.execute(me);");
System.out.println("------------------------------------------------");
//3. 方法引用
//为什么不用Consumer<String > consumer03 = Consumer::accept;?
//因为Consumer中的accept是个抽象方法,不能直接引用,他的作用是Consumer后面是什么方法
//调用accept就回去实现
Consumer<String > consumer03 = System.out::println;
consumer03.accept("I love you");
}数据类型可以省略,因为可由
类型判断得出
这里因为Lambda之前的数据类型是String 所以可以推断出s为String类型,连括号都不需要加1
2
3
4Consumer<String> consumer02 = s -> {
System.out.println(s);
};
consumer02.accept("World.execute(me);");Lambda有两个或以上的参数,多条执行语句,且有返回值该如何写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void test05() {
//1. 以前的写法
Comparator<Integer> comparator01 = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(comparator01.compare(95, 27));
System.out.println("-------------------------");
//2. Lambda表达式
Comparator<Integer> comparator02 = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(comparator02.compare(12, 21));
}
3. 函数式接口
Lambda表达式的本质:作为函数式接口的示例
如果在一个接口中,只声明了一个抽象方法,则此接口就被称为函数式接口。
我们可以在一个接口上使用
@FunctionalInterface注解来验证该接口是否是函数式接口(如果该接口有两个方法就会报错)。正式因为抽象方法只有一个方法吗,所以我们才可以省略
@Override函数声明内容。在
Java.util.function包下定义了Java8丰富的函数式接口
Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着Python、Scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
Java内置的函数式接口
| 函数式接口 | 参数类型 | 返回类型 | 用途 |
|---|---|---|---|
| Consumer 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
| Supplier 供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get() |
| Function函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
| Predicate断定型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t) |
| BiFunction | T, U | R | 对类型为T,U参数应用操作,返回R类型的结果。包含方法为:Rapply(T t,U u); |
| UnaryOperator(Function子接口) | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为:Tapply(T t); |
| BinaryOperator(BiFunction子接口) | T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为:Tapply(T t1,T t2); |
| BiConsumer | T,U | void | 对类型为T,U参数应用操作。包含方法为:voidaccept(Tt,Uu) |
| BiPredicate | T,U | boolean | 包含方法为:booleantest(Tt,Uu) |
| ToIntFunction | T | int | 计算int值的函数 |
| ToLongFunction | T | long | 计算long值的函数 |
| ToDoubleFunction | T | double | 计算double值的函数 |
| IntFunction | int | R | 参数为int类型的函数 |
| LongFunction | long | R | 参数为long类型的函数 |
| DoubleFunction | double | R | 参数为double类型的函数 |
其中Consumer 消费型接口 Supplier 供给型接口 Function函数型接口 Predicate断定型接口四个最为重要
| 接口名 | 抽象方法 | 中文含义 | 经典用法示例 |
|---|---|---|---|
| Consumer |
void accept(T t) | 消费者 | forEach、打印、日志 |
| Supplier |
T get() | 生产者 | 工厂、延迟加载 |
| Function<T,R> | R apply(T t) | 函数/转换器 | map、类型转换 |
| Predicate |
boolean test(T t) | 判断器 | filter、if 判断 |
- 判断使用什么样的接口很简单 例如:
像输出语句这种只有输入没有输出的方法,适合consumer去实现,
但是如果是String的toUpperCaser这个方法需要输入字符串 就不适合consumer去实现了 而是Function去实现
消费型接口
Consumer使用举例:
只要方法参数里有Consumer<T>就代表该方法为消费型函数,传入Consumer对象,就可以让调用的人决定将该数据类型的对象那去干啥
拿这个方法去使用要用到accept,因为其是抽象方法,所以需要自己去定义,使用lambda就不需要去定义而是直接在lambda内部写使用方法了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public void sayIt(double a, Consumer<Double> consumer) {
consumer.accept(a);
}
public void test06() {
//1. 以前的写法
sayIt(233, new Consumer<Double>() {
public void accept(Double a) {
System.out.println("我要说233咯:" + a);
}
});
System.out.println("-------------------------");
//2. Lambda表达式,压缩代码
sayIt(888,a -> System.out.println("我要说888咯:" + a));
}断定型接口
Predicate使用举例:
filterString方法加了断定型接口Predicate 断定型接口有个test抽象方法
比如这样我写了一个字符串过滤器 我输入一堆字符串 遍历所有的字符串,如果通过了test方法过滤 才会添加该字符到新的字符串当中。
其中test方法需要调用者去实现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
public List<String> filterString(List<String> strings, Predicate<String> predicate) {
ArrayList<String> res = new ArrayList<>();
for (String s : strings) {
if (predicate.test(s)) {
res.add(s);
}
}
return res;
}
public void test07() {
List<String> strings = Arrays.asList("晴天","阴天","今天","明天","永远");
//1. 使用匿名内部类的写法
List<String> result01 = filterString(strings,new Predicate<String>() {
public boolean test(String s) {
return s.contains("天");
}
});
System.out.println(result01);
//2. 使用lambda表达式去写
List<String> result02 = filterString(strings,s -> s.contains("天"));
System.out.println(result02);
}
4. 方法引用
当要传递给Lambda体的操作已经有实现的方法了,就可以使用方法引用
方法构造可以看作会Lambda表达式的深层次表达
换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个示例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
格式:要求使用操作符
::将类和方法名分割开来有如下三种使用情况
对象::实例方法名类::静态方法名类::实例方法名
| 符号 | 名称 | 什么时候用 | 典型例子 |
|---|---|---|---|
| -> | Lambda 表达式 | 需要自己写逻辑(哪怕只有一行) | (a, b) -> a + b |
| :: | 方法引用 | 直接“借用”已经存在的方法,不想再写一遍 | String::toUpperCase |
方法引用的使用
- 可以和函数式接口配合使用,例如
1
2
3
4
5
6
public void test08() {
Consumer<String> consumer = System.out::println;
Function<String,String> function = String::toUpperCase;
consumer.accept(function.apply("World.search(you);"));
}
5. Stream API
Stream API概述
Stream API(java.util.stream)把真正的函数式编程风格引入到java,这时目前为止对java类库最好的补充。
Stream是Java8中处理集合的抽象概念,它可以指定你希望对集合的操作,可以执行非常复杂的查找,过滤,和映射数据等操作
使用Stream API对集合数据进行操作,就类似于SQL执行的数据库查询,也可以使用Stream API来并行执行。
为什么要使用Stream API
- 实际开发中,项目多数数据源来自于MySQL,Oracle等,但现在数据源可以更多了,有MOGODB,Redis等,而这些NoSQL的数据就要去Java层面处理。
- Stream和Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者主要面向内存,后者主要面向CPU,通过CPU实现计算 (这就是为什么一旦执行终止操作之后,Stream就不能再被使用,必须创建一个新的流)
Stream 自己不会存储数据
Stream 不会改变源对象,相反,他们会返回一个持有结果的新Stream
Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行Stream 执行流程
- Stream实例化
- 一系列中间操作(过滤、映射、..)
- 终止操作
说明
一系列中间操作链,对数据源的数据进行处理
一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用
Stream实例化
- 先新建两个类
1 | package pojo; |
1 | package Data; |
通过List集合创建Stream
1
2
3
4
5
6
7
8
public void streamTest01(){
List<Employee> employees = EmployeeData.getEmployees();
//default Stream<E> stream() 返回一个顺序流
Stream<Employee> stream = employees.stream();
//default Stream<E> parallelStream() 返回一个并行流(多线程,自动利用多核CPU)
Stream<Employee> parallelledStream = employees.parallelStream();
}通过数组创建Stream
1
2
3
4
5
6
7
8
9
10
11
12
public void streamTest02(){
int[] arr = new int[]{1,2,3,4,5,6,7,8,9,10};
//调用Arrays的static <T>Stream<T> stream(T[] array)返回一个流
IntStream stream = Arrays.stream(arr);
Employee yin_bo_ = new Employee(101,"音波");
Employee canon_0724 = new Employee(102,"耿鬼");
Employee[] employees = new Employee[]{yin_bo_,canon_0724};
Stream<Employee> stream1 = Arrays.stream(employees);
}通过Stream的of()创建stream(最灵活)
可以装任意类型,自动推断1
2
3
4
public void streamTest03(){
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}创建无限流
如果不用limit限制输出,则会一直输出下去,forEach就相当于终止操作1
2
3
4
5
6
7
8
9
10
public void streamTest04(){
//迭代(有规律的无限流)
//遍历前10个数 iterate第一个参数为seed,即是种子,为初始元素,第二个参数是执行的函数,limit是限制元素的大小
Stream.iterate(0,t->t+1).limit(10).forEach(System.out::println);
//生成(随机或自定义规则)
//10个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
中间操作:过滤,筛选,截断,跳过
- 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为
惰性求值
| 方法 | 描述 |
|---|---|
| filter(Predicate p) | 接收Lambda ,从流中排除某些元素 |
| distinct() | 筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素 |
| limit(long maxSize) | 截断流,使其元素不超过给定数量 |
| skip(long n) | 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n)互补 |
- 现在我们来练习表中四个方法
1 |
|
中间操作:映射
| 方法 | 描述 |
|---|---|
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
| mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。 |
| mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 |
| mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
- 测试Stream中间操作:映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void streamTest06(){
List<String> strings = Arrays.asList("a", "b", "c");
List<Employee> employees = EmployeeData.getEmployees();
//map(Function f) 接受一个函数作为参数,将元素转换成其他类型或者提取信息,该函数会被应用到每个元素上,并将其映射成新元素
strings.stream().map(String::toUpperCase).forEach(System.out::println);
System.out.println("----------------------------");
// 练习:获取员工姓名长度大于3的员工的姓名。
employees.stream().map(Employee::getName).filter(employee -> employee.length() > 3).forEach(System.out::println);
System.out.println("----------------------------");
//练习:将字符串中的多个字符构成的集合转换为对应的Stream实例
}
中间操作:排序
| 方法 | 描述 |
|---|---|
| sorted() | 产生一个新流,其中按自然顺序排序 |
| sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
- 测试方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void streamTest07(){
List<Integer> nums = Arrays.asList(114, 514, 340, 199, 233);
List<Employee> employees = EmployeeData.getEmployees();
//1. 自然排序
nums.stream().sorted().forEach(System.out::println);
//2. 定制排序 先按照年龄升序排,再按照工资降序排 只输出名字
employees.stream().sorted((o1, o2) -> {
int compare = Integer.compare(o1.getAge(),o2.getAge());
if(compare!=0) return compare;
else return -Double.compare(o1.getSalary(),o2.getSalary());
}).map(Employee::getName).forEach(System.out::println);
}
终止操作:匹配与查找
终止操作会从流的流水线生成成果,其结果可以是任何不是流的值,例如List,Interger甚至是void
因为流是个抽象的概念 不能被输出 输出的必须是流的流水线生成出来的成果
流进行了终止操作之后,不能再次使用
方法 描述 allMatch(Predicate p) 检查是否匹配所有元素 anyMatch(Predicate p) 检查是否至少匹配一个元素 noneMatch(Predicate p) 检查是否没有匹配所有元素 findFirst() 返回第一个元素 findAny() 返回当前流中的任意元素 count() 返回流中元素总数 max(Comparator c) 返回流中最大值 min(Comparator c) 返回流中最小值 forEach(Consumer c) 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) 测试方法:
1
2
3
4
5
6
7
8
9
10
11
12
public void streamTest08(){
List<Employee> employees = EmployeeData.getEmployees();
// allMatch(Predicate p)——检查是否匹配所有元素。
// 练习:是否所有的员工的工资是否都大于5000
System.out.print("所有的员工的工资是否都大于5000? 结果是" +
employees.stream().allMatch(employee -> employee.getSalary() > 5000)
+ "那么是哪些人公司没大于5000呢? 是"
);
employees.stream().filter(employee -> employee.getSalary() > 5000).map(Employee::getName).forEach(name -> System.out.print(name + " "));
}
终止操作:归约
| 方法 | 描述 |
|---|---|
| reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回T |
| reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回Optional |
- 测试方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void test22() {
List<Integer> nums = Arrays.asList(13, 32, 23, 31, 94, 20, 77, 21, 17);
List<Employee> employees = EmployeeData.getEmployees();
// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
// 练习1:计算1-10的自然数的和
System.out.println(nums.stream().reduce(0, Integer::sum));
//reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
// 练习2:计算公司所有员工工资总和
System.out.println(employees.stream().map(Employee::getSalary).reduce((o1, o2) -> o1 + o2));
// 别的写法,计算年龄总和
System.out.println(employees.stream().map(Employee::getAge).reduce(Integer::sum));
}
终止操作:收集
| 方法 | 描述 |
|---|---|
| collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
测试方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
public void test23() {
// collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
// 练习1:查找工资大于6000的员工,结果返回为一个List
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> list = employees.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("--------------------");
// 练习2:查找年龄大于20的员工,结果返回为一个List
employees.add(new Employee(9527,"yin_bo_",21,9999));
Set<Employee> set = employees.stream().filter(employee -> employee.getAge() > 20).collect(Collectors.toSet());
set.forEach(System.out::println);
}好了,终于学完了Stream API 这里去解读一下黑马点评中用到它的代码:
1
2
3
4
5
6
7List<UserDTO> userDTOS = userService
.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
....................
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());好吧,简单的不想去解释,但是当时做项目的时候难了我大半天😄
6. 结语
半天时间搞定,以后我也是掌握Java8新特性的javaer了😍
- 标题: Java8新特性
- 作者: yin_bo_
- 创建于 : 2025-11-20 15:44:28
- 更新于 : 2025-11-20 22:01:23
- 链接: https://www.blog.yinbo.xyz/2025/11/20/Java学习/Java8新特性/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。