Java 8 是自 Java 5 以来最具革命性的版本,引入了许多改变Java编程方式的重大特性。本教程将系统性地介绍Java 8的核心新特性,包括Lambda表达式、函数式接口、Stream API、Optional类、新的日期时间API等,并通过大量代码示例展示如何在实际开发中应用这些特性。
一、Lambda表达式
Lambda表达式(也称为闭包)是Java 8最受期待的特性,它允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
1. Lambda表达式基础语法
Java 8中引入了一个新的操作符”->”,称为箭头操作符或Lambda操作符。它将Lambda表达式分成两部分:
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中要执行的功能(Lambda体)
基本语法格式:
(parameters) -> expression
或
(parameters) -> { statements; }
2. Lambda表达式六种语法格式
- 无参数,无返回值:
() -> System.out.println("Hello Lambda");
// 示例
Runnable r1 = () -> System.out.println("Hello Lambda");
- 一个参数,无返回值:
(x) -> System.out.println(x)
// 可简化为(一个参数时可省略括号)
x -> System.out.println(x)
- 多个参数,有返回值,Lambda体有多条语句:
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
- Lambda体只有一条语句,return和大括号可省略:
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
- Lambda参数列表的数据类型可省略(JVM编译器通过上下文推断):
Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x,y);
// 可简化为
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
- 无参数,有返回值:
() -> 42
3. Lambda表达式示例
传统写法 vs Lambda写法:
// 传统写法 - 匿名内部类
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
// Lambda写法
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
集合排序示例:
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 45, 8888.88),
new Employee("王五", 58, 7777.77)
);
// 按年龄排序
Collections.sort(employees, (e1, e2) -> {
if(e1.getAge() == e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else{
return Integer.compare(e1.getAge(), e2.getAge());
}
});
二、函数式接口
1. 什么是函数式接口
函数式接口(Functional Interface)就是只包含一个抽象方法的接口。可以使用@FunctionalInterface
注解来检查它是否是一个函数式接口。
Java 8中java.util.function
包下内置了四大核心函数式接口:
- Consumer:消费型接口
- 方法:
void accept(T t)
- 用途:对类型为T的对象应用操作
- Supplier:供给型接口
- 方法:
T get()
- 用途:返回类型为T的对象
- Function:函数型接口
- 方法:
R apply(T t)
- 用途:对类型为T的对象应用操作,并返回结果R
- Predicate:断言型接口
- 方法:
boolean test(T t)
- 用途:确定类型为T的对象是否满足约束
2. 内置函数式接口示例
Consumer示例:
public void happy(double money, Consumer<Double> con){
con.accept(money);
}
@Test
public void test(){
happy(1000, m -> System.out.println("消费了"+m+"元"));
}
Supplier示例:
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for(int i=0; i<num; i++){
list.add(sup.get());
}
return list;
}
@Test
public void test(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random()*100));
numList.forEach(System.out::println);
}
Function示例:
public String strHandler(String str, Function<String,String> fun){
return fun.apply(str);
}
@Test
public void test(){
String newStr = strHandler("hello world", str -> str.toUpperCase());
System.out.println(newStr);
}
Predicate示例:
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for(String str : list){
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
@Test
public void test(){
List<String> list = Arrays.asList("Hello", "Java8", "Lambda", "ok");
List<String> result = filterStr(list, s -> s.length() > 3);
result.forEach(System.out::println);
}
三、方法引用与构造器引用
方法引用是Lambda表达式的一种简化形式,当Lambda体中的内容已经有方法实现了,可以使用方法引用。
1. 方法引用的三种形式
- 对象::实例方法名
Consumer<String> con = (x) -> System.out.println(x);
// 等价于
Consumer<String> con = System.out::println;
- 类::静态方法名
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
// 等价于
Comparator<Integer> com = Integer::compare;
- 类::实例方法名
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
// 等价于
BiPredicate<String,String> bp = String::equals;
2. 构造器引用
语法:ClassName::new
Supplier<Employee> sup = () -> new Employee();
// 等价于
Supplier<Employee> sup = Employee::new;
3. 数组引用
语法:Type[]::new
Function<Integer,String[]> fun = (x) -> new String[x];
// 等价于
Function<Integer,String[]> fun = String[]::new;
四、Stream API
Stream API是Java 8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
1. Stream操作的三个步骤
- 创建Stream:通过数据源(集合、数组等)获取流
- 中间操作:对数据源的数据进行处理
- 终止操作:执行中间操作链,产生结果
2. 创建Stream的四种方式
// 1. 通过Collection系列集合的stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// 2. 通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);
// 3. 通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa","bb","cc");
// 4. 创建无限流
// 迭代
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x+2);
// 生成
Stream.generate(() -> Math.random());
3. 常用中间操作
- 筛选与切片
filter
:接收Lambda,从流中排除某些元素limit
:截断流,使其元素不超过给定数量skip(n)
:跳过元素,返回一个扔掉了前n个元素的流distinct
:筛选,通过流所生成元素的hashCode()和equals()去除重复元素
- 映射
map
:接收Lambda,将元素转换成其他形式或提取信息flatMap
:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
- 排序
sorted()
:自然排序sorted(Comparator com)
:定制排序
4. 常用终止操作
- 查找与匹配
allMatch
:检查是否匹配所有元素anyMatch
:检查是否至少匹配一个元素noneMatch
:检查是否没有匹配所有元素findFirst
:返回第一个元素findAny
:返回当前流中的任意元素count
:返回流中元素的总个数max
:返回流中最大值min
:返回流中最小值
- 归约
reduce(T identity, BinaryOperator)
:可以将流中元素反复结合起来,得到一个值reduce(BinaryOperator)
:可以将流中元素反复结合起来,得到一个值
- 收集
collect
:将流转换为其他形式,接收一个Collector接口的实现
5. Stream API示例
List<Employee> emps = Arrays.asList(
new Employee("张三", 18, 9999.99, Status.FREE),
new Employee("李四", 45, 8888.88, Status.BUSY),
new Employee("王五", 58, 7777.77, Status.VOCATION),
new Employee("赵六", 25, 6666.66, Status.FREE),
new Employee("田七", 13, 5555.55, Status.BUSY)
);
// 1. 获取工资大于6000的员工
List<Employee> list = emps.stream()
.filter(e -> e.getSalary() > 6000)
.collect(Collectors.toList());
// 2. 获取所有员工姓名
List<String> names = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
// 3. 统计员工状态分组
Map<Status, List<Employee>> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
// 4. 获取工资最高的员工
Optional<Employee> max = emps.stream()
.max(Comparator.comparingDouble(Employee::getSalary));
// 5. 检查是否有状态为BUSY的员工
boolean anyBusy = emps.stream()
.anyMatch(e -> e.getStatus() == Status.BUSY);
五、接口的默认方法与静态方法
Java 8允许接口中包含具有具体实现的方法,这种方法称为默认方法,使用default
关键字修饰。
1. 默认方法
interface MyInterface {
default String getName(){
return "默认方法";
}
}
2. 静态方法
interface MyInterface {
static void show(){
System.out.println("接口中的静态方法");
}
}
3. 默认方法冲突解决
如果一个类实现了多个接口,且这些接口有相同的默认方法,那么实现类必须覆盖该方法:
interface A {
default void hello(){
System.out.println("A");
}
}
interface B {
default void hello(){
System.out.println("B");
}
}
class C implements A, B {
@Override
public void hello() {
A.super.hello(); // 显式选择调用A接口的默认方法
}
}
六、Optional类
Optional类是一个容器类,代表一个值存在或不存在,用来避免空指针异常。
1. Optional常用方法
Optional.of(T t)
:创建一个Optional实例Optional.empty()
:创建一个空的Optional实例Optional.ofNullable(T t)
:若t不为null则创建Optional实例,否则创建空实例isPresent()
:判断是否包含值get()
:获取值,若值为null则抛出NoSuchElementExceptionorElse(T other)
:如果有值则返回,否则返回指定的other对象orElseGet(Supplier other)
:如果有值则返回,否则返回由Supplier接口实现提供的对象orElseThrow(Supplier exceptionSupplier)
:如果有值则返回,否则抛出由Supplier接口实现提供的异常map(Function mapper)
:如果有值,则对其应用Function接口实现,并返回结果flatMap(Function mapper)
:与map类似,要求返回值必须是Optional
2. Optional示例
public String getEmployeeName(Employee emp){
return Optional.ofNullable(emp)
.map(e -> e.getName())
.orElse("Unknown");
}
@Test
public void test(){
Employee emp = null;
System.out.println(getEmployeeName(emp)); // Unknown
emp = new Employee("张三");
System.out.println(getEmployeeName(emp)); // 张三
}
七、新的日期时间API
Java 8引入了全新的日期时间API,位于java.time
包下。
1. 主要类
- LocalDate:表示日期,格式为yyyy-MM-dd
- LocalTime:表示时间,格式为HH:mm:ss
- LocalDateTime:表示日期时间,格式为yyyy-MM-dd HH:mm:ss
- Instant:时间戳(以Unix元年开始计算)
- Duration:计算两个”时间”之间的间隔
- Period:计算两个”日期”之间的间隔
- DateTimeFormatter:格式化日期时间
2. 日期时间API示例
// 1. 获取当前日期时间
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt); // 2025-04-07T15:30:45.123
// 2. 指定日期时间
LocalDateTime ldt2 = LocalDateTime.of(2025, 4, 7, 15, 30, 45);
System.out.println(ldt2); // 2025-04-07T15:30:45
// 3. 日期时间运算
LocalDateTime ldt3 = ldt.plusYears(2); // 加2年
LocalDateTime ldt4 = ldt.minusMonths(3); // 减3个月
// 4. 格式化日期时间
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String strDate = ldt.format(dtf);
System.out.println(strDate); // 2025年04月07日 15:30:45
// 5. 时间戳
Instant instant = Instant.now(); // 默认获取UTC时区
System.out.println(instant); // 2025-04-07T07:30:45.123Z
// 6. 计算时间间隔
LocalDate ld1 = LocalDate.now();
LocalDate ld2 = LocalDate.of(2026, 1, 1);
Period period = Period.between(ld1, ld2);
System.out.println(period.getMonths() + "个月" + period.getDays() + "天");
八、其他新特性
1. 重复注解
Java 8允许在同一位置多次使用同一个注解,前提是该注解被@Repeatable
修饰。
@Repeatable(Filters.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Filter {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface Filters {
Filter[] value();
}
@Filter("filter1")
@Filter("filter2")
public interface Filterable {
}
public static void main(String[] args) {
for(Filter filter : Filterable.class.getAnnotationsByType(Filter.class)) {
System.out.println(filter.value());
}
}
2. 类型注解
Java 8新增了类型注解,可以用于任何使用类型的地方。
@Target(ElementType.TYPE_USE)
public @interface NotNull {
}
public static void main(String[] args) {
@NotNull String str = null; // 编译时会检查
}
3. Base64支持
Java 8内置了Base64编解码器。
// 编码
String base64 = Base64.getEncoder().encodeToString("Java8".getBytes());
System.out.println(base64); // SmF2YTg=
// 解码
byte[] bytes = Base64.getDecoder().decode("SmF2YTg=");
System.out.println(new String(bytes)); // Java8
九、总结
Java 8的新特性为Java语言带来了革命性的变化,使Java能够更好地支持函数式编程风格。主要特性包括:
- Lambda表达式:使代码更简洁,支持函数式编程
- 函数式接口:配合Lambda表达式使用,内置四大核心函数式接口
- 方法引用与构造器引用:进一步简化Lambda表达式
- Stream API:强大的集合操作工具,支持并行处理
- 接口的默认方法与静态方法:增强接口的能力
- Optional类:更好地处理空指针异常
- 新的日期时间API:解决了旧日期API的线程安全问题
- 其他特性:重复注解、类型注解、Base64支持等
掌握这些新特性可以显著提高Java开发效率和代码质量,是现代Java开发者必备的技能。