Java 8 核心新特性實(shí)戰(zhàn)指南
Java 8 是 Java 發(fā)展史上最具里程碑意義的版本之一。它引入了函數(shù)式編程思想,極大地簡(jiǎn)化了代碼編寫,提升了開發(fā)效率和系統(tǒng)性能。本教程將帶你深入掌握 Java 8 的核心特性,并通過(guò)實(shí)戰(zhàn)示例讓你快速上手。
一、 Lambda 表達(dá)式:開啟函數(shù)式編程之門
Lambda 表達(dá)式是 Java 8 最核心的特性,它允許你將代碼視為數(shù)據(jù),使代碼更加簡(jiǎn)潔、可讀。
什么是 Lambda 表達(dá)式?
Lambda 表達(dá)式本質(zhì)上是一個(gè)匿名函數(shù),它可以作為參數(shù)傳遞,或作為方法的返回值。它極大地簡(jiǎn)化了函數(shù)式接口(只有一個(gè)抽象方法的接口)的實(shí)現(xiàn)。
基本語(yǔ)法
(parameters) -> expression 或 (parameters) -> { statements; }
示例:從匿名類到 Lambda
假設(shè)我們要對(duì)一個(gè)字符串列表進(jìn)行排序。
Java 7 及之前(使用匿名類)
List<String> names = Arrays.asList("Steve", "Tim", "Lucy", "Patricia", "Ella");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a); // 降序
}
});
Java 8(使用 Lambda 表達(dá)式)
List<String> names = Arrays.asList("Steve", "Tim", "Lucy", "Patricia", "Ella");
Collections.sort(names, (a, b) -> b.compareTo(a));
// 或者更簡(jiǎn)潔地使用方法引用
Collections.sort(names, Comparator.reverseOrder());
可以看到,Lambda 表達(dá)式讓代碼變得極其簡(jiǎn)潔。
四大核心函數(shù)式接口
Java 8 在 java.util.function 包中定義了四個(gè)最常用的函數(shù)式接口,它們是 Lambda 編程的基石。
| 接口 | 描述 | 抽象方法 |
|---|---|---|
Predicate<T> | 斷言,用于判斷 | boolean test(T t) |
Consumer<T> | 消費(fèi),用于執(zhí)行操作 | void accept(T t) |
Function<T, R> | 函數(shù),用于轉(zhuǎn)換 | R apply(T t) |
Supplier<T> | 供給,用于提供值 | T get() |
示例
// Predicate: 判斷字符串是否以'a'開頭
Predicate<String> predicate = s -> s.startsWith("a");
System.out.println(predicate.test("apple")); // true
// Consumer: 打印字符串
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Hello, Lambda!"); // Hello, Lambda!
// Function: 將字符串轉(zhuǎn)換為大寫
Function<String, String> function = s -> s.toUpperCase();
System.out.println(function.apply("hello")); // HELLO
// Supplier: 提供一個(gè)隨機(jī)數(shù)
Supplier<Double> supplier = () -> Math.random();
System.out.println(supplier.get());二、 Stream API:聲明式數(shù)據(jù)處理
Stream API 是 Java 8 引入的用于處理集合數(shù)據(jù)的強(qiáng)大工具。它允許你以聲明式的方式對(duì)數(shù)據(jù)進(jìn)行過(guò)濾、映射、排序、聚合等操作,代碼更清晰、更易于并行化。
核心概念
- 聲明式:你只需告訴程序“做什么”,而不是“怎么做”。
- 管道:Stream 操作分為中間操作(如
filter,map)和終止操作(如collect,forEach)。中間操作會(huì)返回一個(gè)新的 Stream,可以鏈?zhǔn)秸{(diào)用;終止操作會(huì)消費(fèi) Stream 并產(chǎn)生結(jié)果。 - 惰性求值:中間操作不會(huì)立即執(zhí)行,只有當(dāng)終止操作被調(diào)用時(shí),整個(gè)處理流程才會(huì)真正開始。
創(chuàng)建 Stream
// 從集合創(chuàng)建
List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> streamFromList = list.stream();
// 從數(shù)組創(chuàng)建
String[] array = {"cat", "dog", "mouse"};
Stream<String> streamFromArray = Arrays.stream(array);
// 使用 Stream.of()
Stream<String> streamOfValues = Stream.of("red", "green", "blue");常用操作實(shí)戰(zhàn)
讓我們通過(guò)一個(gè)訂單處理的例子來(lái)體驗(yàn) Stream 的強(qiáng)大。
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
class Order {
private Long userId;
private BigDecimal orderAmount;
private int orderStatus; // 1: paid, 0: unpaid
private Date createTime;
private String orderNo;
// 構(gòu)造函數(shù)、Getter 和 Setter 省略
public Order(Long userId, BigDecimal orderAmount, int orderStatus, Date createTime, String orderNo) {
this.userId = userId;
this.orderAmount = orderAmount;
this.orderStatus = orderStatus;
this.createTime = createTime;
this.orderNo = orderNo;
}
public Long getUserId() { return userId; }
public BigDecimal getOrderAmount() { return orderAmount; }
public int getOrderStatus() { return orderStatus; }
public Date getCreateTime() { return createTime; }
public String getOrderNo() { return orderNo; }
}
public class StreamExample {
private static final List<Order> ORDER_LIST = Arrays.asList(
new Order(1L, new BigDecimal("100.00"), 1, new Date(), "ORD001"),
new Order(2L, new BigDecimal("250.50"), 0, new Date(), "ORD002"),
new Order(1L, new BigDecimal("80.00"), 1, new Date(), "ORD003"),
new Order(3L, new BigDecimal("500.00"), 1, new Date(), "ORD004")
);
public static void main(String[] args) {
// 1. 篩選與映射:獲取所有已付款訂單的訂單號(hào)
List<String> paidOrderNos = ORDER_LIST.stream()
.filter(order -> order.getOrderStatus() == 1) // 篩選已付款
.map(Order::getOrderNo) // 映射為訂單號(hào)
.collect(Collectors.toList()); // 收集到列表
System.out.println("已付款訂單號(hào): " + paidOrderNos);
// 2. 聚合統(tǒng)計(jì):計(jì)算訂單總金額
BigDecimal totalAmount = ORDER_LIST.stream()
.map(Order::getOrderAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("訂單總金額: " + totalAmount);
// 3. 分組統(tǒng)計(jì):按用戶ID分組,統(tǒng)計(jì)每個(gè)用戶的訂單數(shù)量
Map<Long, Long> userOrderCountMap = ORDER_LIST.stream()
.collect(Collectors.groupingBy(Order::getUserId, Collectors.counting()));
System.out.println("用戶訂單數(shù)量統(tǒng)計(jì): " + userOrderCountMap);
// 4. 排序:按訂單金額降序排序
List<Order> sortedOrderList = ORDER_LIST.stream()
.sorted(Comparator.comparing(Order::getOrderAmount).reversed())
.collect(Collectors.toList());
System.out.println("按金額降序排序的訂單: " + sortedOrderList.stream().map(Order::getOrderNo).collect(Collectors.toList()));
}
}三、 全新的日期時(shí)間 API (java.time)
舊的 java.util.Date 和 java.util.Calendar 存在線程不安全、設(shè)計(jì)混亂等問(wèn)題。Java 8 引入了全新的 java.time 包,提供了清晰、不可變且線程安全的日期時(shí)間類。
核心類
LocalDate: 表示日期,不含時(shí)區(qū)(如 2026-04-02)。LocalTime: 表示時(shí)間,不含時(shí)區(qū)(如 16:19:58)。LocalDateTime: 表示日期和時(shí)間,不含時(shí)區(qū)(如 2026-04-02T16:19:58)。ZonedDateTime: 表示帶時(shí)區(qū)的日期和時(shí)間。DateTimeFormatter: 用于格式化和解析日期時(shí)間,線程安全,替代了舊的SimpleDateFormat。
示例
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeExample {
public static void main(String[] args) {
// 獲取當(dāng)前日期和時(shí)間
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("當(dāng)前日期: " + date);
System.out.println("當(dāng)前時(shí)間: " + time);
System.out.println("當(dāng)前日期時(shí)間: " + dateTime);
// 格式化日期時(shí)間
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = dateTime.format(formatter);
System.out.println("格式化后: " + formattedDateTime);
// 解析日期時(shí)間
LocalDateTime parsedDateTime = LocalDateTime.parse("2026-04-02 16:19:58", formatter);
System.out.println("解析后: " + parsedDateTime);
}
}四、 接口默認(rèn)方法與靜態(tài)方法
Java 8 允許在接口中定義帶有方法體的默認(rèn)方法和靜態(tài)方法,這為接口的演進(jìn)提供了極大的靈活性,解決了接口升級(jí)會(huì)破壞實(shí)現(xiàn)類的難題。
默認(rèn)方法 (default method)
使用 default 關(guān)鍵字修飾,為接口方法提供默認(rèn)實(shí)現(xiàn)。實(shí)現(xiàn)類可以選擇性地重寫它。
interface Vehicle {
default void start() {
System.out.println("車輛正在啟動(dòng)...");
}
}
class Car implements Vehicle {
// 可以重寫,也可以不重寫
}
public class DefaultMethodTest {
public static void main(String[] args) {
Car myCar = new Car();
myCar.start(); // 輸出: 車輛正在啟動(dòng)...
}
}靜態(tài)方法 (static method)
接口中的靜態(tài)方法只能通過(guò)接口名調(diào)用,不能被實(shí)現(xiàn)類繼承。
interface Vehicle {
static void stop() {
System.out.println("車輛正在停止...");
}
}
public class StaticMethodTest {
public static void main(String[] args) {
Vehicle.stop(); // 直接通過(guò)接口名調(diào)用
}
}五、 Optional 類:優(yōu)雅地處理空指針
NullPointerException 是 Java 中最常見的異常。Optional 是一個(gè)容器類,它可以包含一個(gè)非空的值,或者為空。它強(qiáng)制你顯式地處理值為空的情況,從而避免空指針異常。
常用方法
of(T value): 創(chuàng)建一個(gè)包含非空值的 Optional。ofNullable(T value): 創(chuàng)建一個(gè)可能為空的 Optional。isPresent(): 判斷值是否存在。ifPresent(Consumer<? super T> action): 如果值存在,則執(zhí)行給定的操作。orElse(T other): 如果值存在則返回值,否則返回other。map(Function<? super T, ? extends U> mapper): 如果值存在,則對(duì)其進(jìn)行轉(zhuǎn)換。
示例
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
String str = null;
// 傳統(tǒng)方式
if (str != null) {
System.out.println(str.length());
}
// 使用 Optional
Optional<String> optionalStr = Optional.ofNullable(str);
optionalStr.ifPresent(s -> System.out.println(s.length())); // 值存在才執(zhí)行
int length = optionalStr.map(String::length).orElse(-1); // 提供默認(rèn)值
System.out.println("字符串長(zhǎng)度: " + length); // 輸出: 字符串長(zhǎng)度: -1
}
}六、 生產(chǎn)環(huán)境最佳實(shí)踐與避坑指南
掌握了基本用法后,了解如何在生產(chǎn)環(huán)境中正確使用這些特性至關(guān)重要。
1. 嚴(yán)格區(qū)分流的適用場(chǎng)景
- 簡(jiǎn)單遍歷:對(duì)于簡(jiǎn)單的循環(huán),使用傳統(tǒng)的
for循環(huán)可能性能更高,因?yàn)?Stream 有初始化開銷。 - 復(fù)雜集合操作:當(dāng)需要進(jìn)行多步驟的過(guò)濾、映射、聚合時(shí),Stream 能極大提升代碼可讀性。
- CPU 密集型、大數(shù)據(jù)量:使用并行流 (
parallelStream()) 可以充分利用多核 CPU 的性能。 - IO 密集型、阻塞型操作:禁止使用并行流,因?yàn)樗鼤?huì)阻塞
ForkJoinPool的公共線程池,影響整個(gè)應(yīng)用的性能。
2. 避免自動(dòng)裝箱拆箱的性能損耗
處理基本數(shù)據(jù)類型(如 int, long)時(shí),優(yōu)先使用 IntStream, LongStream, DoubleStream 等原始類型流,避免頻繁的裝箱和拆箱操作。
// 錯(cuò)誤示例:會(huì)產(chǎn)生大量的 Integer 對(duì)象 long count = orderList.stream().map(Order::getId).count(); // 正確示例:使用原始類型流,性能更高 long count = orderList.stream().mapToLong(Order::getId).count();
3. 禁止流的復(fù)用
Stream 是一次性的。調(diào)用終止操作后,Stream 就會(huì)被消費(fèi)掉,再次使用會(huì)拋出 IllegalStateException。
// 錯(cuò)誤示例 Stream<Order> orderStream = orderList.stream(); orderStream.count(); orderStream.forEach(System.out::println); // 會(huì)拋出異常 // 正確示例:每次使用時(shí)重新創(chuàng)建流 orderList.stream().count(); orderList.stream().forEach(System.out::println);
4. 并行流的線程安全規(guī)范
在并行流中,禁止向非線程安全的集合(如 ArrayList)中添加元素,這會(huì)導(dǎo)致數(shù)據(jù)丟失或并發(fā)修改異常。應(yīng)始終使用 collect 方法來(lái)安全地收集結(jié)果。
// 錯(cuò)誤示例:線程不安全
List<Long> userIdList = new ArrayList<>();
orderList.parallelStream().forEach(order -> userIdList.add(order.getUserId()));
// 正確示例:使用 collect,線程安全
List<Long> userIdList = orderList.parallelStream()
.map(Order::getUserId)
.collect(Collectors.toList());5. 自定義并行流線程池
并行流默認(rèn)使用全局的 ForkJoinPool,其線程數(shù)等于 CPU 核心數(shù)。長(zhǎng)時(shí)間運(yùn)行的任務(wù)會(huì)阻塞這個(gè)公共池。對(duì)于關(guān)鍵任務(wù),可以創(chuàng)建自定義的 ForkJoinPool 來(lái)實(shí)現(xiàn)線程隔離。
ForkJoinPool customPool = new ForkJoinPool(4); // 自定義線程數(shù)
customPool.submit(() ->
orderList.parallelStream()
.collect(Collectors.groupingBy(Order::getUserId))
).join();
到此這篇關(guān)于Java 8 核心新特性實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Java 8 核心新特性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMvc/SpringBoot HTTP通信加解密的實(shí)現(xiàn)
這篇文章主要介紹了SpringMvc/SpringBoot HTTP通信加解密的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
springboot結(jié)合vue實(shí)現(xiàn)sse接口詳細(xì)流程
這篇文章主要為大家詳細(xì)介紹了springboot結(jié)合vue實(shí)現(xiàn)sse接口詳細(xì)流程,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下2026-02-02
java中g(shù)et()方法和set()方法的作用淺析
這篇文章主要給大家介紹了關(guān)于java中g(shù)et()方法和set()方法的作用,set是是對(duì)數(shù)據(jù)進(jìn)行設(shè)置,而get是對(duì)數(shù)據(jù)進(jìn)行獲取,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07
SpringBoot獲取客戶端的IP地址的實(shí)現(xiàn)示例
在Web應(yīng)用程序中,獲取客戶端的IP地址是一項(xiàng)非常常見的需求,本文主要介紹了SpringBoot獲取客戶端的IP地址的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
Jpa中Specification的求和sum不生效原理分析
這篇文章主要為大家介紹了Jpa中Specification的求和sum不生效原理示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
SpringBoot使用Interceptor攔截器的實(shí)例
這篇文章主要介紹了SpringBoot使用Interceptor攔截器的相關(guān)知識(shí),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03

