# Java教程 - Java8新特性
Java 8 (又称为jdk 1.8) 是Java 语言开发的一个主要版本,是 Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
下面介绍一下 Java8 常用的新特性。
# 1.1 接口的默认方法与静态方法
以前的版本定义接口是不能有实现机制的,现在可以使用 default
关键字声明默认方法,然后子类可以重写,也可以直接使用了。另外接口中也可以声明静态方法并提供方法的实现。以后的工具类就可以参考接口来设计,例如大量的方法被添加到 java.util.Collection
接口中去,例如 stream(),parallelStream(),forEach(),removeIf()等。
声明默认方法和静态方法:
interface FirstInterface {
// 方法和常量,默认就是public的
// 不写final static,默认就是常量
String key = "abc";
/**
* 定义默认方法
*/
default void commomMethod() {
System.out.println(key);
}
/**
* 定义静态方法
*/
static void firstMethod(int value) {
System.out.println(key + value);
}
}
//实现
public class InterfaceTest implements FirstInterface {
public static void main(String[] args) {
//调用静态方法
FirstInterface.firstMethod(123);
InterfaceTest test = new InterfaceTest();
test.commomMethod();
}
/**
* 重写默认方法
*/
@Override
public void commomMethod() {
System.out.println("你好");
}
}
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
但是需要注意不能用默认方法来重写 equals()
、hashCode()
和 toString()
,接口不能提供对 Object
类的任何方法的默认实现。
# 1.2 Lambda表达式
在讲 Lambda
表达式之前需要先说函数式接口。
# 1 函数式接口
什么是函数式接口?
函数式接口就是只包含一个方法的接口。比如 Java 标准库中的 java.lang.Runnable
和 java.util.Comparator
都是典型的函数式接口。java 8提供 @FunctionalInterface
作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断, 但最好在接口上使用注解@FunctionalInterface
进行声明,以免团队的其他人员错误地往接口中添加新的方法。因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
举个栗子:
在 Java8 版本之前对集合进行排序的话,代码如下:
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class LambdaTest {
public static void main(String[] args) {
// 创建集合
List<String> nameList = Arrays.asList("doubi", "niubi", "shabi", "erbi");
// 排序
Collections.sort(nameList, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
System.out.println(nameList);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在 Java8 中就没必要使用这种传统的匿名对象的方式了,Java8 可以使用 lambda
表达式,lambda
表达式就是函数式接口里面方法的实现,即是 Comparator
接口 compare
的实现。
public class LambdaTest {
public static void main(String[] args) {
// 创建集合
List<String> nameList = Arrays.asList("doubi", "niubi", "shabi", "erbi");
// 排序
Collections.sort(nameList, (String a, String b) -> {
return b.compareTo(a);
});
System.out.println(nameList);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
可以将形式参数类型省略:
// 排序
Collections.sort(nameList, (a, b) -> {
return b.compareTo(a);
});
2
3
4
因为 Lambda 表达式的方法体只有一行代码,所以可以省略 {}
和 return
:
// 排序
Collections.sort(nameList, (a, b) -> b.compareTo(a));
2
Java中的 Lambda
无法单独出现,它需要一个函数式接口,Lambda
表达式的本质是函数式接口的实例,是对象,这和其他语言的 Lambda 有所区别,其他语言的 Lambda表达式是一个函数(方法)。
# 2 Lambda表达式的语法
表达式包含三个部分:
- 形参列表:一个括号内用逗号分隔的形式参数,参数是 函数式接口 里面方法的参数;参数类型可以省略;如果形参只有一个参数,形参的括号
()
可以省略; - 一个箭头符号:
->
; - 方法体:可以是表达式或代码块,方法体是函数式接口里面方法的实现。如果是代码块,则必须用
{}
来包裹起来。如果只是一行表达式,那么可以省略return
和{}
。
下面举一些例子:
// 1. 不需要参数,返回值为 5
() -> {return 5;}
// 或
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
(int x) -> {return 2 * x;}
// 或
(x) -> 2 * x
// 或
x -> 2 * x
// 3. 接收2个int型整数,返回他们的和
(int x, int y) -> {return x + y;}
// 或
(int x, int y) -> x + y
// 或
(x, y) -> x + y
// 4. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> {System.out.print(s);}
// 或
(s) -> System.out.print(s)
// 或
s -> System.out.print(s)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Lambda 表达式需要与函数式接口的实现对应。
# 3 自定义函数式接口
定义一个函数式接口,只包含一个抽象方法:
建议添加 @FunctionalInterface 注解。
@FunctionalInterface
interface MyLambdaInterface {
int toInt(String arg);
}
2
3
4
使用 Lambda 表达式匹配函数式接口:
public class LambdaTest {
public static void main(String[] args) {
// 在Java8之前的版本
testMethod("123", new MyLambdaInterface() {
@Override
public int toInt(String arg) {
return Integer.parseInt(arg);
}
});
// 使用 Lambda表达式
testMethod("123", arg -> Integer.parseInt(arg));
}
public static void testMethod(String strArg, MyLambdaInterface lambdaInterface) {
int i = lambdaInterface.toInt(strArg);
System.out.println(i);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1.3 方法引用
当 Lambda 方法体的操作已经有现成可以使用的方法了,就可以使用方法引用。方法引用是 Lambda 表达式的一个简化写法,所引用的方法其实是 Lambda 表达式的方法体实现,语法也很简单,左边是类名或对象,中间是 ::
,右边是相应的方法名。
方法引用可以分为以下几种形式:
- 如果是静态方法,使用
类名::方法名
; - 如果是实例方法,使用
类名::方法名
或对象::方法名
; - 构造函数,使用
ClassName::new
。
举个栗子,继续用上面的例子。
首先是一个函数式接口:
@FunctionalInterface
interface MyLambdaInterface {
int toInt(String arg);
}
2
3
4
使用方法引用 :
public class LambdaTest {
public static void main(String[] args) {
// 使用Lambda表达式
testMethod("123", arg -> Integer.parseInt(arg));
// 使用方法引用
testMethod("123", Integer::parseInt);
}
public static void testMethod(String strArg, MyLambdaInterface lambdaInterface) {
int i = lambdaInterface.toInt(strArg);
System.out.println(i);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
如果函数式接口中的抽象方法的形参和返回值 和 方法引用的方法的参数和返回值一致,就可以使用方法引用了。
上面 int toInt(String arg)
与 Integer.parseInt(arg)
形参和返回值相同,所以可以使用方法引用。
再举个例子,遍历集合:
public class LambdaTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("doubi", "niubi", "shabi");
// 使用Lambda表达式,遍历输出列表中的元素
list.forEach((item) -> System.out.println(item));
// 使用方法引用,遍历输出列表中的元素
list.forEach(System.out::println); // 输出列表中的元素
}
}
2
3
4
5
6
7
8
9
10
11
forEach()
方法的参数是 Consumer
接口的实现,Consumer
接口是一个函数式接口,接口中的抽象方法是 void accept(T t);
,参数和返回值与 System.out.println()
方法参数和返回值兼容,所以可以使用方法引用。
再举个例子:
@FunctionalInterface
interface EqualsInterface {
boolean eq(String s1, String s2);
}
public class LambdaTest {
public static void main(String[] args) {
// 使用Lambda表达式
testMethod("abc", "abc", (s1, s2) -> s1.equals(s2));
// 使用方法引用
testMethod("abc", "abc", String::equals);
}
public static void testMethod(String str1, String str2, EqualsInterface equalsInterface) {
boolean equals = equalsInterface.eq(str1, str2);
System.out.println(equals);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上面的函数式接口中的参数和 String 类中的 equals() 方法参数不一致,但是也是可以使用方法引用,有一个参数作为调用对象。
再举个构造方法的例子:
class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
}
@FunctionalInterface
interface GenerateInterface {
// 返回一个Student对象
Student generate(String name);
}
public class LambdaTest {
public static void main(String[] args) {
// 使用Lambda表达式
testMethod("doubibiji", name -> new Student(name));
// 使用方法引用
testMethod("abc", Student::new);
}
public static void testMethod(String name, GenerateInterface equalsInterface) {
Student stu = equalsInterface.generate(name);
System.out.println(stu);
}
}
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
上面函数式接口返回的是一个 Student对象,那么在实现函数式接口的时候,就可以使用构造方法的方法引用。
# 1.4 Stream
Stream
是用于处理集合数据的流式操作。它提供了一种更便利、高效的方式来处理集合数据。
什么是 Stream?
- Stream 是一种数据流:Stream不会存储元素,它不是数据结构,而是一种用于操作集合数据的流式处理机制。
- Stream 提供了丰富的操作方法:可以进行过滤、映射、排序、聚合等操作。
- Stream 是惰性求值的:只有在需要结果时才会执行操作,可以提高性能。
Stream的特点:
- 链式操作:可以将多个操作连接起来形成一个流水线,提高代码可读性。
- 不修改原始数据:Stream 操作不会修改原始集合数据,而是生成新的 Stream。
先举个栗子:
public class StreamTest {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 获取Stream
Stream<Integer> stream = numbers.stream();
// 遍历输出集合中的偶数,然后转换为新的集合
List<Integer> newList = stream.filter(n -> n % 2 == 0).toList();
System.out.println(newList);// 输出结果: 2, 4, 6
//stream.filter(n -> n > 5);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
上面首先获取到 Stream 对象,然后执行操作 filter,最后通过 toList() 转换为一个集合。 toList() 是一个终止操作,所以后面无法再试用 Stream。如果要使用,只能重新获取新的对象。
所以 Stream的执行流程是:
- Stream的实例化;
- 进行一系列的中间操作,例如过滤、映射、排序等;
- 终止操作
# 1 获取Stream
获取Stream有如下四种方式:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamTest {
public static void main(String[] args) {
// 方式一:通过集合获取
List<String> nameList = Arrays.asList("doubi", "niubi", "shabi", "erbi");
// 返回一个顺序流
Stream<String> stream1 = nameList.stream();
// 返回一个并行流
Stream<String> stream2 = nameList.parallelStream();
// 方式二:通过数组获取
String[] names = new String[]{"doubi", "niubi", "shabi", "erbi"};
Stream<String> stream3 = Arrays.stream(names);
// 方式三:通过Stream.of获取
Stream<String> stream4 = Stream.of("doubi", "niubi", "shabi", "erbi");
// 方式四:创建无限流,不常用
// 通过迭代的方式,获取10个偶数,如果没有limit,不会停止,下一个元素在上一个元素的基础上+2
Stream.iterate(0, t -> t + 2).limit(10).forEach(num -> System.out.println(num));
// 通过生成的方式,获取10个随机数,通过limit限制个数
Stream.generate(() -> Math.random()).limit(10).forEach(num -> System.out.println(num));
}
}
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
顺序流就是按照集合中的元素顺序依次处理,并行流是一种并行处理元素的流处理方式,它利用多线程同时处理元素,加快处理速度,适用于大数据量或需要并发处理的场景。
# 2 中间操作
Stream常用的中间操作主要分为三类:筛选和切片、映射、排序
# 筛选和切片
# filter
作用:根据给定的条件过滤流中的元素。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> stream = numbers.stream();
// 遍历输出集合中的偶数
stream.filter(n -> n % 2 == 0)
.forEach(item -> System.out.println(item));
// 输出结果: 2, 4, 6
2
3
4
5
6
7
8
上面是通过 forEach 遍历了筛选的结果,也可以将筛选的结果收集成一个集合。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> stream = numbers.stream();
List<Integer> evenNumbers = stream.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出结果为 [2, 4, 6]
2
3
4
5
6
7
注意:上面的 forEach
和 collect
方法都是终止函数,也就是后面无法再跟其他函数了,而且获取到的 stream 也无法继续使用了,如果想再使用,只能重新获取 Stream。
# limit和skip
作用:
limit(n)
:截断流,使其最多只包含前n
个元素。skip(n)
:跳过流中的前n
个元素,返回剩余的元素。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 只获取5个
List<Integer> limitedNumbers = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println(limitedNumbers);// 输出结果为 [1, 2, 3, 4, 5]
// 跳过前面5个
List<Integer> skippedNumbers = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println(skippedNumbers);// 输出结果为 [6, 7, 8, 9, 10]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# distinct
作用:去除流中重复的元素,就是去重。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
// 去重
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers);// 输出结果为 [1, 2, 3, 4]
2
3
4
5
6
7
8
如果是对象元素,根据 hashCode()
和 equals()
去除重复元素。
# 映射
# map
作用:将流中的每个元素转换映射为另一个元素。
举个栗子:
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 获取集合中元素的长度,组成另一个集合
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(wordLengths);// 输出结果为 [5, 6, 6]
2
3
4
5
6
7
8
再举个例子:
获取学生成绩在90分以上的学生姓名:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
public class StreamTest {
public static void main(String[] args) {
List<Student> stuList = new ArrayList<>();
stuList.add(new Student("doubi", 80));
stuList.add(new Student("erbi", 93));
stuList.add(new Student("niubi", 98));
stuList.add(new Student("shibi", 79));
stuList.add(new Student("shabi", 62));
List<String> nameList = stuList.stream().filter(stu -> stu.getScore() > 90).map((stu) -> stu.getName()).collect(Collectors.toList());
System.out.println(nameList);
}
}
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
# flatMap
作用:将流中的每个元素映射为一个流,然后将这些流连接成一个流。
举个栗子:
List<List<Integer>> numbers = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5, 6));
// 将多个集合合成一个结合
List<Integer> flattenedNumbers = numbers.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(flattenedNumbers); // 输出结果为 [1, 2, 3, 4, 5, 6]
2
3
4
5
6
7
# peek
作用:对流中的每个元素执行操作,返回流本身。
举个栗子:
List<String> words = Arrays.asList("apple", "banana", "cherry");
List<String> modifiedWords = words.stream()
.peek(word -> System.out.println("Processing: " + word))
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(modifiedWords);
// 输出结果为:
// Processing: apple
// Processing: banana
// Processing: cherry
// [APPLE, BANANA, CHERRY]
2
3
4
5
6
7
8
9
10
11
12
13
# 排序
# sorted
作用:对流中的元素进行排序。
举个栗子:
自然排序:
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出结果为 [1, 1, 2, 3, 4, 5, 6, 9]
2
3
4
5
6
定制排序,传入 Comparator
接口实例:
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
List<Integer> sortedNumbers = numbers.stream()
.sorted((o1, o2) -> Integer.compare(o1, o2) * -1) // *-1取倒序
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出结果为 [9, 6, 5, 4, 3, 2, 1, 1]
2
3
4
5
6
# 3 终止操作
终止操作是指会触发流的处理并产生一个结果的操作。这些操作会消耗流中的数据,并且一旦执行了终止操作,流就不能再被使用了。终止操作是流处理流程中的最后一步,它们将中间操作的结果转换为某种形式的汇总数据(如列表、集合、数组、单个值等)。
常用的终止操作有如下类型:查找和匹配、归约、收集和遍历。
# 查找和匹配
# allMatch
作用:检查是否所有元素都匹配给定的条件。
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 是否所有的水果都以a开头
boolean allFruitsStartWithA = list.stream()
.allMatch(fruit -> fruit.startsWith("a"));
System.out.println(allFruitsStartWithA);// false
2
3
4
5
6
7
# anyMatch
作用:检查是否至少有一个元素匹配给定的条件。
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 是否有一个水果以a开头
boolean hasFruitStartingWithA = list.stream()
.anyMatch(fruit -> fruit.startsWith("a"));
System.out.println(hasFruitStartingWithA);// true
2
3
4
5
6
7
# noneMatch
作用:检查是否所有的元素都不满足给定的条件。
举个栗子:
List<String> list = Arrays.asList("banana", "orange", "kiwi");
// 是否所有的水果都不以a开头
boolean noFruitsStartWithA = list.stream()
.noneMatch(fruit -> fruit.startsWith("a")); // true
System.out.println(noFruitsStartWithA);// true
2
3
4
5
6
7
# findFirst
作用:
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 获取第一个元素
Optional<String> firstFruit = list.stream()
.findFirst();
String fruit = firstFruit.get();
System.out.println(fruit); // apple
2
3
4
5
6
7
8
# findAny
作用:返回流中的任意元素
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
Optional<String> anyFruit = list.stream().findAny();
String fruit = anyFruit.get();
System.out.println(fruit);// 可能返回"apple"、"banana"或"cherry"中的任意一个
2
3
4
5
# 归约和汇总
# count
作用:返回流中元素的个数。
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
long count = list.stream().count();
System.out.println(count);// 3
2
3
4
5
# max和min
作用:计算流中元素的最大值和最小值。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 方式1:求最大值
Optional<Integer> max1 = numbers.stream()
.max(Integer::compare);
System.out.println(max1.get()); // 5
// 方式2:求最大值
OptionalInt max2 = numbers.stream()
.mapToInt(Integer::intValue)
.max();
System.out.println(max2.getAsInt()); // 5
// 方式1:求最小值
Optional<Integer> min1 = numbers.stream()
.min(Integer::compare);
System.out.println(min1.get()); // 1
// 方式2:求最小值
OptionalInt min2 = numbers.stream()
.mapToInt(Integer::intValue)
.min();
System.out.println(min2.getAsInt()); // 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# sum和average
作用:计算流中元素的和和平均值。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println(sum); // 15
// 求平均值
OptionalDouble average = numbers.stream()
.mapToDouble(Integer::doubleValue)
.average();
System.out.println(average.getAsDouble()); // 3.0
2
3
4
5
6
7
8
9
10
11
12
13
# reduce
作用:将流中的元素结合起来,得到一个值。
举个栗子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = numbers.stream()
.reduce(0, Integer::sum); // 15
2
3
# 收集
# collect
作用:将流中的元素收集到一个新的集合中(如List、Set、Map等)。
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
List<String> upperCaseFruits = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseFruits); // ["APPLE", "BANANA", "CHERRY"]
2
3
4
5
6
7
Collectors
有 toList()
、 toSet()
、toMap()
等方法。
还可以直接使用 toList()
或 toArray()
方法:
List<String> list = Arrays.asList("apple", "banana", "cherry");
List<String> upperCaseFruitList = list.stream()
.map(String::toUpperCase)
.toList();
String[] upperCaseFruitArray = list.stream()
.map(String::toUpperCase)
.toArray(String[]::new);
System.out.println(upperCaseFruitList); // ["APPLE", "BANANA", "CHERRY"]
System.out.println(Arrays.toString(upperCaseFruitArray)); // ["APPLE", "BANANA", "CHERRY"]
2
3
4
5
6
7
8
9
10
11
12
# 遍历
# forEach
作用:对流中的每个元素执行某个操作。
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
.forEach(fruit -> System.out.println(fruit)); // 打印所有水果
2
3
# forEachOrdered
作用:对于有序流,按照流中元素的顺序,对每个元素执行某个操作。
举个栗子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.stream()
.forEachOrdered(fruit -> System.out.println(fruit)); // 按照顺序打印所有水果
2
3
注意:对于顺序流,forEach
和forEachOrdered
的行为是相同的。但如果你在处理一个并行流,并且关心元素的顺序,那么应该使用forEachOrdered
。