Java 8 新特性详细教程
Java 8 是 Java 语言中的一个重要版本,它引入了许多革命性的功能,比如 Lambda 表达式、Stream API、Optional 类和全新的日期时间 API 等。这些功能显著提升了 Java 开发效率和代码的可读性。本教程将对这些新特性进行深入讲解,并提供丰富的示例代码。
目录
- [Lambda表达式](#1-Lambda 表达式)
- 函数式接口
- 方法引用
- [Stream API](#4-Stream API)
- [Optional 类](#5-Optional 类)
- [新的日期时间 API](#6-新的日期时间 API)
- [Collectors 工具类](#7-Collectors 工具类)
- [并行 Stream](#8-并行 Stream)
- 其他重要改进
1. Lambda 表达式
1.1 什么是 Lambda 表达式?
Lambda 表达式是 Java 8 中引入的一种语法糖,用来替代匿名内部类,主要用于简化函数式接口的实现。通过 Lambda 表达式,代码可以更加简洁和优雅。
1.2 语法结构
Lambda 表达式的基本语法如下:
(parameters) -> expression
或者包含多条语句时:
(parameters) -> { statements; }
- 参数:指定传递给方法的参数,可以没有参数、单个参数或多个参数。
- 箭头
->
:表示将输入的参数传递到表达式或代码块。 - 表达式或代码块:实现逻辑。
1.3 使用场景
- 代替匿名内部类:适用于只有一个抽象方法的接口(即函数式接口)。
- 简化集合操作:如
forEach
遍历。
1.4 示例
传统匿名类实现:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println("Hello, " + name);
}
});
Lambda 表达式实现:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println("Hello, " + name));
1.5 Lambda 表达式的详细示例
1.5.1 单参数的 Lambda 表达式
names.forEach(name -> System.out.println(name));
1.5.2 多参数的 Lambda 表达式
Comparator<String> comparator = (a, b) -> a.compareToIgnoreCase(b);
List<String> sortedNames = names.stream()
.sorted(comparator)
.collect(Collectors.toList());
1.5.3 无参数的 Lambda 表达式
Runnable runnable = () -> System.out.println("Lambda running!");
new Thread(runnable).start();
1.6 省略规则
- 参数类型可以省略,Java 会自动推导。
- 单个参数时,可以省略括号。
- 单条语句时,可以省略大括号和
return
关键字。
示例:
names.forEach(name -> System.out.println(name)); // 等价于 name -> { System.out.println(name); }
2. 函数式接口
2.1 什么是函数式接口?
函数式接口是一个只有一个抽象方法的接口。这种接口可以直接使用 Lambda 表达式进行实例化。
常见函数式接口:
Consumer<T>
:接受一个参数,无返回值。Function<T, R>
:接受一个参数,返回一个结果。Predicate<T>
:接受一个参数,返回布尔值。Supplier<T>
:无参数,返回一个结果。
2.2 自定义函数式接口
@FunctionalInterface
interface Greeting {
void sayHello(String name);
}
使用示例:
Greeting greeting = name -> System.out.println("Hello, " + name);
greeting.sayHello("Alice");
注意:
- 函数式接口可以包含
default
和static
方法。 - 添加
@FunctionalInterface
注解可以避免误添加多余的抽象方法。
3. 方法引用
方法引用是 Lambda 表达式的一种简洁写法,直接引用已有的方法。使用 ::
符号表示方法引用。
3.1 类型
- 静态方法引用:
ClassName::methodName
- 实例方法引用:
instance::methodName
- 构造方法引用:
ClassName::new
3.2 示例
3.2.1 静态方法引用
names.forEach(System.out::println); // 等价于 name -> System.out.println(name)
3.2.2 实例方法引用
List<String> sortedNames = names.stream()
.sorted(String::compareToIgnoreCase)
.collect(Collectors.toList());
3.2.3 构造方法引用
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> newList = listSupplier.get();
4. Stream API
4.1 什么是 Stream?
Stream 是 Java 8 中新增的工具类,专为处理集合数据而设计。它支持函数式编程风格,提供强大的数据操作能力。
4.2 Stream 的核心操作
- 创建 Stream:通过
stream()
方法创建流。 - 中间操作:
filter
:过滤数据。map
:对数据进行映射。sorted
:对数据排序。
- 终止操作:
forEach
:遍历数据。collect
:将结果收集为集合。
4.3 示例
4.3.1 基本使用
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
4.3.2 复杂操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squaredNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squaredNumbers); // 输出: [4, 16]
5. Optional 类
5.1 为什么需要 Optional?
在 Java 中,null
是引发空指针异常的主要原因。Optional
提供了一种优雅的方式来处理可能为空的值。
5.2 常用方法
-
创建 Optional
Optional.of(value)
:创建包含非空值的 Optional。Optional.ofNullable(value)
:允许空值。Optional.empty()
:创建空的 Optional。
-
处理值
isPresent()
:检查值是否存在。get()
:获取值(不推荐直接使用)。orElse(defaultValue)
:为空时返回默认值。orElseGet(supplier)
:为空时动态生成默认值。map()
:对值进行操作。
5.3 示例
5.3.1 创建和检查值
Optional<String> optional = Optional.ofNullable("Hello");
if (optional.isPresent()) {
System.out.println(optional.get());
}
5.3.2 使用 map
转换值
Optional<String> optional = Optional.of("hello");
optional.map(String::toUpperCase).ifPresent(System.out::println);
5.3.3 默认值
Optional<String> emptyOptional = Optional.empty();
System.out.println(emptyOptional.orElse("Default Value"));
6. 新的日期时间 API
6.1 简介
Java 8 提供了全新的日期时间 API,用来替代旧的 java.util.Date
和 Calendar
,设计更合理,使用更直观。
6.2 常用类
- LocalDate:仅表示日期。
- LocalTime:仅表示时间。
- LocalDateTime:同时表示日期和时间。
6.3 示例
6.3.1 获取当前日期时间
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("Date: " + date);
System.out.println("Time: " + time);
System.out.println("DateTime: " + dateTime);
6.3.2 日期运算
LocalDate date = LocalDate.of(2024, 11, 12);
LocalDate nextWeek = date.plusWeeks(1);
System.out.println("Next Week: " + nextWeek);
7. Collectors 工具类
7.1 什么是 Collectors?
Collectors
是一个工具类,提供了许多静态方法,用于汇总和转换 Stream
中的数据,常见功能包括:
- 转换为集合(
toList
、toSet
等) - 数据分组(
groupingBy
) - 数据分区(
partitioningBy
) - 字符串拼接(
joining
)
7.2 常用方法
7.2.1 转换为集合
import java.util.*;
import java.util.stream.Collectors;
public class CollectorsExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alice");
// 转换为 Set,去重
Set<String> uniqueNames = names.stream()
.collect(Collectors.toSet());
System.out.println("Unique Names: " + uniqueNames);
}
}
7.2.2 数据分组
import java.util.*;
import java.util.stream.Collectors;
public class GroupingExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Daniel");
// 按名字首字母分组
Map<Character, List<String>> groupedByFirstLetter = names.stream()
.collect(Collectors.groupingBy(name -> name.charAt(0)));
System.out.println(groupedByFirstLetter);
}
}
7.2.3 数据分区
import java.util.*;
import java.util.stream.Collectors;
public class PartitioningExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 分区为奇数和偶数
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitioned);
}
}
7.2.4 字符串拼接
import java.util.*;
import java.util.stream.Collectors;
public class JoiningExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 拼接为逗号分隔的字符串
String result = names.stream()
.collect(Collectors.joining(", "));
System.out.println("Joined String: " + result);
}
}
8. 并行 Stream
8.1 什么是并行 Stream?
并行 Stream 是 Java 8 中为流操作引入的多线程处理能力,它能充分利用多核处理器,提高数据处理速度。
8.2 并行和串行的区别
- 串行流(Sequential Stream):流中的操作按顺序执行,单线程处理。
- 并行流(Parallel Stream):流中的操作被分成多个线程并发执行。
8.3 示例
8.3.1 串行流
import java.util.stream.IntStream;
public class SequentialStreamExample {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 使用串行流计算 1 到 1,000,000 的偶数个数
long count = IntStream.range(1, 1_000_001)
.filter(n -> n % 2 == 0)
.count();
long end = System.currentTimeMillis();
System.out.println("Count: " + count + ", Time: " + (end - start) + " ms");
}
}
8.3.2 并行流
import java.util.stream.IntStream;
public class ParallelStreamExample {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 使用并行流计算 1 到 1,000,000 的偶数个数
long count = IntStream.range(1, 1_000_001)
.parallel()
.filter(n -> n % 2 == 0)
.count();
long end = System.currentTimeMillis();
System.out.println("Count: " + count + ", Time: " + (end - start) + " ms");
}
}
注意:
- 并行流适合计算密集型任务,对于 I/O 密集型任务可能不如串行流。
- 不要在并行流中操作共享变量,否则可能引发线程安全问题。
9. 其他重要改进
9.1 接口中的默认方法和静态方法
Java 8 允许在接口中定义默认方法和静态方法,从而为接口提供更多灵活性。
默认方法
默认方法可以为接口方法提供一个默认实现,使得在扩展接口时无需修改现有实现类。
示例:
interface Vehicle {
default void start() {
System.out.println("Vehicle is starting");
}
}
class Car implements Vehicle {}
public class DefaultMethodExample {
public static void main(String[] args) {
Vehicle car = new Car();
car.start(); // 输出: Vehicle is starting
}
}
静态方法
接口中的静态方法只能通过接口名调用,不能通过实现类调用。
示例:
interface Vehicle {
static void stop() {
System.out.println("Vehicle is stopping");
}
}
public class StaticMethodExample {
public static void main(String[] args) {
Vehicle.stop(); // 输出: Vehicle is stopping
}
}
9.2 Map 的增强操作
Java 8 为 Map
添加了许多便捷操作,比如 forEach
、computeIfAbsent
、merge
。
forEach
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.forEach((key, value) -> System.out.println(key + ": " + value));
computeIfAbsent
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.computeIfAbsent("B", key -> 42); // 如果 "B" 不存在,则插入 42
System.out.println(map);
merge
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.merge("A", 2, Integer::sum); // 如果 "A" 存在,执行合并操作
System.out.println(map);
9.3 Base64 编码解码
Java 8 内置了 Base64 编码和解码功能,无需再引入外部库。
示例
import java.util.Base64;
public class Base64Example {
public static void main(String[] args) {
// 编码
String original = "Java8 Base64";
String encoded = Base64.getEncoder().encodeToString(original.getBytes());
System.out.println("Encoded: " + encoded);
// 解码
String decoded = new String(Base64.getDecoder().decode(encoded));
System.out.println("Decoded: " + decoded);
}
}