Java 8新特性簡介
-
速度更快
-
代碼更少(增加了新的語法 Lambda 表達式)
-
強大的 Stream API
-
便於並行
-
最大化減少空指針異常 Optional
其中最為核心的為 Lambda 表達式與Stream API
1 Lambda表達式
Lambda 是一個匿名函數,我們可以把 Lambda 表達式理解為是一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。可以寫出更簡潔、更靈活的代碼。作為一種更緊湊的代碼風格,使Java的語言表達能力得到了提升。
從匿名類到 Lambda 的轉換
Lambda 表達式語法
Lambda 表達式在Java 語言中引入了一個新的語法元素和操作符。這個操作符為 “->” , 該操作符被稱為 Lambda 操作符或剪頭操作符。它將 Lambda 分為兩個部分:
左側:指定了 Lambda 表達式需要的所有參數
右側:指定了 Lambda 體,即 Lambda 表達式要執行的功能。
語法格式一:無參,無返回值,Lambda 體只需一條語句
語法格式二:Lambda 需要一個參數
語法格式三:Lambda 只需要一個參數時,參數的小括號可以省略
語法格式四:Lambda 需要兩個參數,並且有返回值
語法格式五:當 Lambda 體只有一條語句時,return 與大括號可以省略
類型推斷
上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然可以編譯,這是因為 javac 根據程序的上下文,在后台推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的 “類型推斷”。
2 函數式接口
什么是函數式接口
只包含一個抽象方法的接口,稱為函數式接口。
你可以通過 Lambda 表達式來創建該接口的對象。(若 Lambda 表達式拋出一個受檢異常,那么該異常需要在目標接口的抽象方 法上進行聲明)。
我們可以在任意函數式接口上使用 @FunctionalInterface 注解, 這樣做可以檢查它是否是一個函數式接口,同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口。
自定義函數式接口
函數式接口中使用泛型:
作為參數傳遞 Lambda 表達式
作為參數傳遞 Lambda 表達式:為了將 Lambda 表達式作為參數傳遞,接收Lambda 表達式的參數類型必須是與該 Lambda 表達式兼容的函數式接口的類型。
Java 內置四大核心函數式接口
函數式接口 |
參數類型 |
返回類型 |
用途 |
Consumer<T> 消費型接口 |
T |
void |
對類型為T的對象應用操作,包含方法: void accept(T t) |
Supplier<T> 供給型接口 |
無 |
T |
返回類型為T的對象,包含方法:T get(); |
Function<T, R> 函數型接口 |
T |
R |
對類型為T的對象應用操作,並返回結果。結果是R類型的對象。包含方法:R apply(T t); |
Predicate<T> 斷定型接口 |
T |
boolean |
確定類型為T的對象是否滿足某約束,並返回boolean 值。包含方法boolean test(T t); |
其他接口
函數式接口 |
參數類型 |
返回類型 |
用途 |
BiFunction<T, U, R> |
T, U |
R |
對類型為 T, U 參數應用操作, 返回 R 類型的結果。包含方法為 R apply(T t, U u); |
UnaryOperator<T> (Function子接口) |
T |
T |
對類型為T 的對象進行一元運算, 並返回T 類型的結果。包含方法為 T apply(T t); |
BinaryOperator<T> (BiFunction 子接口) |
T, T |
T |
對類型為T 的對象進行二元運算, 並返回T類型的結果。包含方法為 T apply(T t1, T t2); |
BiConsumer<T, U> |
T, U |
void |
對類型為T, U 參數應用操作。包含方法為 void accept(T t, U u) |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> |
T |
int long double |
分 別 計 算 int 、 long 、 double、值的函數 |
IntFunction<R> LongFunction<R> DoubleFunction<R> |
int long double |
R |
參數分別為int 、long 、 double 類型的函數 |
3 方法引用與構造器引用
方法引用
當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用!(實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致!)
方法引用:使用操作符 “::” 將方法名和對象或類的名字分隔開來。
如下三種主要使用情況:
對象::實例方法
類::靜態方法
類::實例方法
方法引用
例如:
等同於:
例如:
等同於:
例如:
等同於:
注意:當需要引用方法的第一個參數是調用對象,並且第二個參數是需要引用方法的第二個參數(或無參數)時:ClassName::methodName
構造器引用
格式: ClassName::new
與函數式接口相結合,自動與函數式接口中方法兼容。 可以把構造器引用賦值給定義的方法,與構造器參數列表要與接口中抽象方法的參數列表一致!
例如:
等同於:
數組引用
格式: type[] :: new
例 如 :
等同於:
4 強大的 Stream API
了解 Stream
Java8中有兩大最為重要的改變。第一個是 Lambda 表達式;另外一個則是 Stream API(java.util.stream.*)。
Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常復雜的查找、過濾和映射數據等操作。 使用Stream API 對集合數據進行操作,就類似於使用 SQL 執行的數據庫查詢。也可以使用 Stream API 來並行執行操作。簡而言之, Stream API 提供了一種高效且易於使用的處理數據的方式。
什么是 Stream
流(Stream) 到底是什么呢?
是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。
“集合講的是數據,流講的是計算!”
注意:
①Stream 自己不會存儲元素。
②Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream。
③Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。
Stream 的操作三個步驟
創建 Stream
一個數據源(如:集合、數組),獲取一個流
中間操作
一個中間操作鏈,對數據源的數據進行處理
終止操作(終端操作)
一個終止操作,執行中間操作鏈,並產生結果
創建 Stream
Java8 中的 Collection 接口被擴展,提供了兩個獲取流的方法:
default Stream<E> stream() : 返回一個順序流
default Stream<E> parallelStream() : 返回一個並行流
由數組創建流
Java8 中的 Arrays 的靜態方法 stream() 可以獲取數組流:
static <T> Stream<T> stream(T[] array): 返回一個流
重載形式,能夠處理對應基本類型的數組:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
由值創建流
可以使用靜態方法 Stream.of(), 通過顯示值創建一個流。它可以接收任意數量的參數。
public static<T> Stream<T> of(T... values) : 返回一個流
由函數創建流:
創建無限流
可以使用靜態方法 Stream.iterate() 和Stream.generate(), 創建無限流。
迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
生成
public static<T> Stream<T> generate(Supplier<T> s) :
Stream 的中間操作
多個中間操作可以連接起來形成一個流水線,除非流水 線上觸發終止操作,否則中間操作不會執行任何的處理! 而在終止操作時一次性全部處理,稱為“惰性求值”。
篩選與切片
方 法 |
描 述 |
filter(Predicate p) |
接收 Lambda , 從流中排除某些元素。 |
distinct() |
篩選,通過流所生成元素的 hashCode() 和 equals() 去除重復元素 |
limit(long maxSize) |
截斷流,使其元素不超過給定數量。 |
skip(long n) |
跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補 |
Stream 的中間操作
映射
方 法 |
描 述 |
map(Function f) |
接收一個函數作為參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。 |
mapToDouble(ToDoubleFunction f) |
接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新DoubleStream。 |
mapToInt(ToIntFunction f) |
接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 IntStream。 |
mapToLong(ToLongFunction f) |
接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的LongStream。 |
flatMap(Function f) |
接收一個函數作為參數,將流中的每個值都換成另一個流,然后把所有流連接成一個流 |
Stream 的中間操作
排序
方 法 |
描 述 |
sorted() |
產生一個新流,其中按自然順序排序 |
sorted(Comparator comp) | 產生一個新流,其中按比較器順序排序 |
Stream 的終止操作
終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
查找與匹配
方 法 |
描 述 |
allMatch(Predicate p) |
檢查是否匹配所有元素 |
anyMatch(Predicate p) |
檢查是否至少匹配一個元素 |
noneMatch(Predicate p) |
檢查是否沒有匹配所有元素 |
findFirst() |
返回第一個元素 |
findAny() |
返回當前流中的任意元素 |
Stream 的終止操作
count() |
返回流中元素總數 |
max(Comparator c) |
返回流中最大值 |
min(Comparator c) |
返回流中最小值 |
forEach(Consumer c) |
內部迭代(使用 Collection 接口需要用戶去做迭代,稱為外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了) |
歸約
reduce(T iden, BinaryOperator b) |
可以將流中元素反復結合起來,得到一個值。返回 T |
reduce(BinaryOperator b) |
可以將流中元素反復結合起來,得到一個值。返回 Optional<T> |
備注:map 和 reduce 的連接通常稱為 map-reduce 模式,因 Google 用它來進行網絡搜索而出名。
Stream 的終止操作
收集
方 法 |
描 述 |
collect(Collector c) |
將流轉換為其他形式。接收一個 Collector接口的實現,用於給Stream中元素做匯總的方法 |
Collector 接口中方法的實現決定了如何對流執行收集操作(如收集到 List、Set、Map)。但是 Collectors 實用類提供了很多靜態方法,可以方便地創建常見收集器實例,具體方法與實例如下表:
方法 |
返回類型 |
作用 |
toList |
List<T> |
把流中元素收集到List |
List<Employee> emps= list.stream().collect(Collectors.toList()); |
||
toSet |
Set<T> |
把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet()); |
||
toCollection |
Collection<T> |
把流中元素收集到創建的集合 |
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new)); |
||
counting |
Long |
計算流中元素的個數 |
long count = list.stream().collect(Collectors.counting()); |
||
summingInt |
Integer |
對流中元素的整數屬性求和 |
inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary)); |
||
averagingInt |
Double |
計算流中元素Integer屬性的平均 值 |
doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary)); |
||
summarizingInt |
IntSummaryStatistics |
收集流中Integer屬性的統計值。 如:平均值 |
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); |
joining |
String |
連接流中每個字符串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); |
||
maxBy |
Optional<T> |
根據比較器選擇最大值 |
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); |
||
minBy |
Optional<T> |
根據比較器選擇最小值 |
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); |
||
reducing |
歸約產生的類型 |
從一個作為累加器的初始值開始,利用BinaryOperator與流中元素逐個結合,從而歸 約成單個值 |
inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); |
||
collectingAndThen |
轉換函數返回的類型 |
包裹另一個收集器,對其結 果轉換函數 |
inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); |
||
groupingBy |
Map<K, List<T>> |
根據某屬性值對流分組,屬 性為K,結果為V |
Map<Emp.Status, List<Emp>> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus)); |
||
partitioningBy |
Map<Boolean, List<T>> |
根據true或false進行分區 |
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
並行流與串行流
並行流就是把一個內容分成多個數據塊,並用不同的線程分
別處理每個數據塊的流。
Java 8 中將並行進行了優化,我們可以很容易的對數據進行並行操作。Stream API 可以聲明性地通過 parallel() 與sequential() 在並行流與順序流之間進行切換。
了解 Fork/Join 框架
Fork/Join 框架:就是在必要的情況下,將一個大任務,進行拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行 join 匯總.
Fork/Join 框架與傳統線程池的區別
采用 “工作竊取”模式(work-stealing):
當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到線 程隊列中,然后再從一個隨機線程的隊列中偷一個並把它放在自己的隊列中。
相對於一般的線程池實現,fork/join框架的優勢體現在對其中包含的任務的處理方式上.在一般的線程池中,如果一個線程正在執行的任務由於某些原因無法繼續運行,那么該線程會處於等待狀態.而在fork/join框架實現中,如果某個子問題由於等待另外一個子問題的完成而無法繼續運行.那么處理該子問題的線程會主動尋找其他尚未運行的子問題來執行.這種方式減少了線程的等待時間,提高了性能.
5 新時間日期 API
使用 LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime 類的實例是不可變的對象,分別表示使用 ISO-8601日歷系統的日期、時間、日期和時間。它們提供 了簡單的日期或時間,並不包含當前的時間信息。也不包含與時區相關的信息。
注:ISO-8601日歷系統是國際標准化組織制定的現代公民的日期和時間的表示法
方法 |
描述 |
示例 |
now() |
靜態方法,根據當前時間創建對象 |
LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now(); |
of() |
靜態方法,根據指定日期/時間創建 對象 |
LocalDate localDate = LocalDate.of(2016, 10, 26); LocalTime localTime = LocalTime.of(02, 22, 56); LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55); |
plusDays, plusWeeks, plusMonths, plusYears |
向當前 LocalDate 對象添加幾天、幾周、幾個月、幾年 |
|
minusDays, minusWeeks, minusMonths, minusYears |
從當前 LocalDate 對象減去幾天、幾周、幾個月、幾年 |
|
plus, minus |
添加或減少一個 Duration 或 Period |
|
withDayOfMonth, withDayOfYear, withMonth, withYear |
將月份天數、年份天數、月份、年 份 修 改 為 指 定 的 值 並 返 回 新 的 LocalDate 對象 |
|
getDayOfMonth |
獲得月份天數(1-31) |
|
getDayOfYear |
獲得年份天數(1-366) |
|
getDayOfWeek |
獲得星期幾(返回一個 DayOfWeek 枚舉值) |
|
getMonth |
獲得月份, 返回一個 Month 枚舉值 |
|
getMonthValue |
獲得月份(1-12) |
|
getYear |
獲得年份 |
|
until |
獲得兩個日期之間的 Period 對象, 或者指定 ChronoUnits 的數字 |
|
isBefore, isAfter |
比較兩個 LocalDate |
|
isLeapYear |
判斷是否是閏年 |
|
Instant 時間戳
用於“時間戳”的運算。它是以Unix元年(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的描述進行運算
Duration 和 Period
Duration:用於計算兩個“時間”間隔
Period:用於計算兩個“日期”間隔
日期的操縱
TemporalAdjuster : 時間校正器。有時我們可能需要獲取例如:將日期調整到“下個周日”等操作。
TemporalAdjusters : 該類通過靜態方法提供了大量的常用 TemporalAdjuster 的實現。
例如獲取下個周日:
解析與格式化
java.time.format.DateTimeFormatter 類:該類提供了三種格式化方法:
預定義的標准格式
語言環境相關的格式
自定義的格式
時區的處理
Java8 中加入了對時區的支持,帶時區的時間為分別為:
ZonedDate、ZonedTime、ZonedDateTime
其中每個時區都對應着 ID,地區ID都為 “{區域}/{城市}”的格式
例如 :Asia/Shanghai 等
ZoneId:該類中包含了所有的時區信息getAvailableZoneIds() : 可以獲取所有時區時區信息of(id) : 用指定的時區信息獲取 ZoneId 對象
與傳統日期處理的轉換
類 |
To 遺留類 |
From 遺留類 |
java.time.Instant java.util.Date |
Date.from(instant) |
date.toInstant() |
java.time.Instant java.sql.Timestamp |
Timestamp.from(instant) |
timestamp.toInstant() |
java.time.ZonedDateTime java.util.GregorianCalendar |
GregorianCalendar.from(zonedDateTim e) |
cal.toZonedDateTime() |
java.time.LocalDate java.sql.Time |
Date.valueOf(localDate) |
date.toLocalDate() |
java.time.LocalTime java.sql.Time |
Date.valueOf(localDate) |
date.toLocalTime() |
java.time.LocalDateTime java.sql.Timestamp |
Timestamp.valueOf(localDateTime) |
timestamp.toLocalDateTime() |
java.time.ZoneId java.util.TimeZone |
Timezone.getTimeZone(id) |
timeZone.toZoneId() |
java.time.format.DateTimeFormatter java.text.DateFormat |
formatter.toFormat() |
無 |
6 接口中的默認方法與靜態方法
接口中的默認方法
Java 8中允許接口中包含具有具體實現的方法,該方法稱為“默認方法”,默認方法使用 default 關鍵字修飾。
例如:
接口中的默認方法
接口默認方法的”類優先”原則
若一個接口中定義了一個默認方法,而另外一個父類或接口中又定義了一個同名的方法時
選擇父類中的方法。如果一個父類提供了具體的實現,那么接口中具有相同名稱和參數的默認方法會被忽略。
接口沖突。如果一個父接口提供一個默認方法,而另一個接口也提供了一個具有相同名稱和參數列表的方法(不管方法是否是默認方法),那么必須覆蓋該方法來解決沖突
接口中的靜態方法
Java8 中,接口中允許添加靜態方法。
例如:

7 其他新特性
Optional 類
Optional<T> 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在, 原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指針異常。
常用方法:
Optional.of(T t) : 創建一個 Optional 實例Optional.empty() : 創建一個空的 Optional 實例
Optional.ofNullable(T t):若 t 不為 null,創建 Optional 實例,否則創建空實例isPresent() : 判斷是否包含值
orElse(T t) : 如果調用對象包含值,返回該值,否則返回t orElseGet(Supplier s) :如果調用對象包含值,返回該值,否則返回 s 獲取的值
map(Function f): 如果有值對其處理,並返回處理后的Optional,否則返回 Optional.empty()
flatMap(Function mapper):與 map 類似,要求返回值必須是Optional
重復注解與類型注解
Java 8對注解處理提供了兩點改進:可重復的注解及可用於類型的注解。