java8新特性總結(jié)-1
java8新特性總結(jié)-2
體現(xiàn)在語言,類庫,編譯器,工具,運行時(JVM)五個方面
lambda表達式
lambda表達式只能作用于函數(shù)式接口(即只有一個抽象方法,不過有一點需要注意,默認(rèn)方法和靜態(tài)方法不會破壞函數(shù)式接口的定義,因此如下的代碼是合法的)
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
最簡單的Lambda表達式可由逗號分隔的參數(shù)列表、->符號和語句塊組成,參數(shù)類型可以指定,也可以不指定,編譯器會判斷出;有多個參數(shù)的話,需要用括號括起來;語句塊只有一條的話,不需要花括號,多條需要;如果有返回值,并且語句只有一條,那么return關(guān)鍵字也可以省略,編譯器自動推斷出(包括返回值類型和返回值)。lambda表達式簡化了匿名類的使用,lambda表達式和匿名類使用時,如果引用了局部變量,則必須是final修飾的。因為回調(diào)函數(shù)不是立即執(zhí)行,而是在將來執(zhí)行,如果局部變量不聲明為final的,本方法結(jié)束后就會被銷毀掉,等到回調(diào)函數(shù)執(zhí)行的時候,就不存在了;而如果是final修飾的話,final變量是放在方法區(qū)的,方法結(jié)束后,final變量并不會被銷毀
Arrays.asList( "a", "b", "d" ).forEach( (String e) -> {
System.out.print( e );
System.out.print( e );
} );
函數(shù)接口
抽象方法只有一個(接口的方法默認(rèn)都是抽象的,但可以有默認(rèn)方法和靜態(tài)方法)的接口,使用@FunctionalInterface注解,像Runnable,Callable,Comparator這些接口現(xiàn)在都有這個注解了。函數(shù)接口使得函數(shù)好像變成了一個對象,試想一下,一個函數(shù)接口類型的對象,不就是一個函數(shù)嘛

接口的默認(rèn)方法和靜態(tài)方法
加入接口默認(rèn)方法這種機制,使得能夠兼容以前的實現(xiàn)類。在這之前,如果我們修改了接口(比如添加了新的方法),那么所有的實現(xiàn)類必須跟著修改(比如要實現(xiàn)接口中新添加的方法),但是加入了默認(rèn)方法機制后,以前的實現(xiàn)類可以不用修改代碼就能繼承接口新添加的默認(rèn)方法
接口可以有默認(rèn)方法和靜態(tài)方法,默認(rèn)方法使用default關(guān)鍵字聲明。首先,之前的接口是個雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當(dāng)需要修改接口時候,需要修改全部實現(xiàn)該接口的類,目前的java 8之前的集合框架沒有foreach方法,通常能想到的解決辦法是在JDK里給相關(guān)的接口添加新的方法及實現(xiàn)。然而,對于已經(jīng)發(fā)布的版本,是沒法在給接口添加新方法的同時不影響已有的實現(xiàn)。所以引進的默認(rèn)方法。他們的目的是為了解決接口的修改與現(xiàn)有的實現(xiàn)不兼容的問題。
- 多個默認(rèn)方法
一個接口有默認(rèn)方法,考慮這樣的情況,一個類實現(xiàn)了多個接口,且這些接口有相同的默認(rèn)方法,以下實例說明了這種情況的解決方法:
public interface vehicle {
default void print() {
System.out.println("我是一輛車!");
}
}
public interface fourWheeler {
default void print() {
System.out.println("我是一輛四輪車!");
}
}
第一個解決方案是創(chuàng)建自己的默認(rèn)方法,來覆蓋重寫接口的默認(rèn)方法:
public class Car implements vehicle, fourWheeler {
@Override
public void print() {
System.out.println("我是一輛四輪汽車!");
}
}
第二種解決方案可以使用 super 來調(diào)用指定接口的默認(rèn)方法:
public class Car implements vehicle, fourWheeler {
@Override
public void print() {
vehicle.super.print();
}
}
- 靜態(tài)方法
public interface vehicle {
default void print() {
System.out.println("我是一輛車!");
}
// 靜態(tài)方法
static void blowHorn() {
System.out.println("按喇叭!!!");
}
}
方法引用
- ClassName::new的形式引用類的構(gòu)造函數(shù),該構(gòu)造函數(shù)沒有參數(shù)
- ClassName::static_method的形式引用靜態(tài)方法,該靜態(tài)方法有一個參數(shù)
- ClassName::method的形式引用成員方法,該方法沒有參數(shù)
- instance::method的形式引用成員方法,該方法有一個參數(shù)
重復(fù)注解
允許在同一個地方多次使用同一個注解,該注解需要有@Repeatable
拓寬的注解的使用場景
現(xiàn)在,注解幾乎可以使用在任何元素上:局部變量、接口類型、超類和接口實現(xiàn)類,甚至可以用在函數(shù)的異常定義上
更好的類型推斷
Java 8編譯器在類型推斷方面有很大的提升,在很多場景下編譯器可以推導(dǎo)出某個參數(shù)的數(shù)據(jù)類型,從而使得代碼更為簡潔
獲取參數(shù)名稱
為了在運行時獲得Java程序中方法的參數(shù)名稱,老一輩的Java程序員必須使用不同方法,例如Paranamer liberary。Java 8終于將這個特性規(guī)范化,在語言層面(使用反射API和Parameter.getName()方法)和字節(jié)碼層面(使用新的javac編譯器以及-parameters參數(shù))提供支持。
package com.javacodegeeks.java8.parameter.names;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ParameterNames {
public static void main(String[] args) throws Exception {
Method method = ParameterNames.class.getMethod( "main", String[].class );
for( final Parameter parameter: method.getParameters() ) {
System.out.println( "Parameter: " + parameter.getName() );
}
}
}
在Java 8中這個特性是默認(rèn)關(guān)閉的,因此如果不帶-parameters參數(shù)編譯上述代碼并運行,輸出為Parameter: arg0;如果帶-parameters參數(shù)編譯,輸出為Parameter: args,如果使用maven進行項目管理,則可以在maven-compiler-plugin編譯器的配置項中配置-parameters參數(shù):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
Optional
Java應(yīng)用中最常見的bug就是空值異常。在Java 8之前,Google Guava引入了Optionals類來解決NullPointerException,從而避免源碼被各種null檢查污染,以便開發(fā)者寫出更加整潔的代碼。Java 8也將Optional加入了官方庫。Optional僅僅是一個容器:存放T類型的值或者null。它提供了一些有用的接口來避免顯式的null檢查,可以參考Java 8官方文檔了解更多細節(jié)。
Optional 類是一個可以為null的容器對象。如果值存在則isPresent()方法會返回true,調(diào)用get()方法會返回該對象。Optional 是個容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。Optional 類的引入很好的解決空指針異常。
Stream
新增的Stream API(java.util.stream)將生成環(huán)境的函數(shù)式編程引入了Java庫中。這是目前為止最大的一次對Java庫的完善,以便開發(fā)者能夠?qū)懗龈佑行?、更加簡潔和緊湊的代碼。Steam API極大得簡化了集合操作(后面我們會看到不止是集合)Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。
Stream使用一種類似用SQL語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對Java集合運算和表達的高階抽象。
Stream API可以極大提高Java程序員的生產(chǎn)力,讓程序員寫出高效率、干凈、簡潔的代碼。
這種風(fēng)格將要處理的元素集合看作一種流,流在管道中傳輸,并且可以在管道的節(jié)點上進行處理,比如篩選,排序,聚合等。
元素流在管道中經(jīng)過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結(jié)果。
Stream(流)是一個來自數(shù)據(jù)源的元素隊列并支持聚合操作
元素:是特定類型的對象,形成一個隊列。Java中的Stream并不會存儲元素,而是按需計算。
數(shù)據(jù)源 :流的來源??梢允羌?,數(shù)組,I/O channel,產(chǎn)生器generator等。
聚合操作: 類似SQL語句一樣的操作,比如filter, map, reduce, find,match, sorted等。
和以前的Collection操作不同,Stream操作還有兩個基礎(chǔ)的特征:
Pipelining::中間操作都會返回流對象本身。這樣多個操作可以串聯(lián)成一個管道,如同流式風(fēng)格(fluent style)。這樣做可以對操作進行優(yōu)化,比如延遲執(zhí)行(laziness)和短路( short-circuiting)。
內(nèi)部迭代:以前對集合遍歷都是通過Iterator或者For-Each的方式,顯式的在集合外部進行迭代,這叫做外部迭代。Stream提供了內(nèi)部迭代的方式,通過訪問者模式(Visitor)實現(xiàn)。
- 在Java 8中,集合接口有兩個方法來生成流:
stream() ?為集合創(chuàng)建串行流。
parallelStream() ? 為集合創(chuàng)建并行流。
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
}
- forEach
Stream 提供了新的方法 'forEach' 來迭代流中的每個數(shù)據(jù)。以下代碼片段使用forEach 輸出了10個隨機數(shù):
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
- map
map 方法用于映射每個元素到對應(yīng)的結(jié)果,以下代碼片段使用 map 輸出了元素對應(yīng)的平方數(shù):
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 獲取對應(yīng)的平方數(shù)
List<Integer> squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
- filter
filter 方法用于通過設(shè)置條件過濾出元素。以下代碼片段使用filter 方法過濾出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 獲取空字符串的數(shù)量
int count = (int) strings.stream().filter(string -> string.isEmpty()).count();
- limit
limit 方法用于獲取指定數(shù)量的流。以下代碼片段使用 limit 方法打印出 10 條數(shù)據(jù):
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
- sorted
sorted 方法用于對流進行排序。以下代碼片段使用 sorted 方法對輸出的 10 個隨機數(shù)進行排序:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
- 并行(parallel)程序
parallelStream 是流并行處理程序的代替方法。以下實例我們使用parallelStream 來輸出空字符串的數(shù)量:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// 獲取空字符串的數(shù)量
int count = (int) strings.parallelStream().filter(string -> string.isEmpty()).count();
- Collectors
Collectors 類實現(xiàn)了很多歸約操作,例如將流轉(zhuǎn)換成集合和聚合元素。Collectors可用于返回列表或字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("篩選列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
- 統(tǒng)計
另外,一些產(chǎn)生統(tǒng)計結(jié)果的收集器也非常有用。它們主要用于int、double、long等基本類型上,它們可以用來產(chǎn)生類似如下的統(tǒng)計結(jié)果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的數(shù) : " + stats.getMax());
System.out.println("列表中最小的數(shù) : " + stats.getMin());
System.out.println("所有數(shù)之和 : " + stats.getSum());
System.out.println("平均數(shù) : " + stats.getAverage());
Date/Time API
新的java.time包包含了所有關(guān)于日期、時間、時區(qū)、Instant(跟日期類似但是精確到納秒)、duration(持續(xù)時間)和時鐘操作的類。
Java 8通過發(fā)布新的Date-Time API (JSR 310)來進一步加強對日期與時間的處理。
在舊版的Java 中,日期時間API 存在諸多問題,其中有:
非線程安全 ? java.util.Date 是非線程安全的,所有的日期類都是可變的,這是Java日期類最大的問題之一。
設(shè)計很差 ? Java的日期/時間類的定義并不一致,在java.util和java.sql的包中都有日期類,此外用于格式化和解析的類在java.text包中定義。java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期,將其納入java.sql包并不合理。另外這兩個類都有相同的名字,這本身就是一個非常糟糕的設(shè)計。
時區(qū)處理麻煩 ? 日期類并不提供國際化,沒有時區(qū)支持,因此Java引入了java.util.Calendar和java.util.TimeZone類,但他們同樣存在上述所有的問題。
Java 8 在 java.time 包下提供了很多新的 API。以下為兩個比較重要的 API:
Local(本地) ? 簡化了日期時間的處理,沒有時區(qū)的問題。
Zoned(時區(qū)) ? 通過制定的時區(qū)處理日期時間。
新的java.time包涵蓋了所有處理日期,時間,日期/時間,時區(qū),時刻(instants),過程(during)與時鐘(clock)的操作。
- Clock類
- LocalDate和LocalTime類
- LocalDateTime類包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日歷系統(tǒng)中的時區(qū)信息
- ZoneDateTime類
- Duration類,它持有的時間精確到秒和納秒。這使得我們可以很容易得計算兩個日期之間的不同
// Get duration between two dates
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
Nashorn JavaScript引擎
Java 8提供了新的Nashorn JavaScript引擎,使得我們可以在JVM上開發(fā)和運行JS應(yīng)用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一個實現(xiàn)版本,這類Script引擎遵循相同的規(guī)則,允許Java和JavaScript交互使用。從JDK1.8開始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成為Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1規(guī)范以及一些擴展。它使用基于JSR292的新語言特性,其中包含在JDK 7中引入的 invokedynamic,將JavaScript編譯成Java字節(jié)碼。與先前的Rhino實現(xiàn)相比,這帶來了2到10倍的性能提升。
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
輸出為:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
Base64編碼
對Base64編碼的支持已經(jīng)被加入到Java 8官方庫中,這樣不需要使用第三方庫就可以進行Base64編碼,新的Base64API也支持URL和MINE的編碼解碼。
(Base64.getUrlEncoder() / Base64.getUrlDecoder(),Base64.getMimeEncoder() / Base64.getMimeDecoder()),例子代碼如下:
package com.javacodegeeks.java8.base64;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64
.getEncoder()
.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
System.out.println( encoded );
final String decoded = new String(
Base64.getDecoder().decode( encoded ),
StandardCharsets.UTF_8 );
System.out.println( decoded );
}
}
并行數(shù)組
Java8版本新增了很多新的方法,用于支持并行數(shù)組處理。最重要的方法是parallelSort(),可以顯著加快多核機器上的數(shù)組排序。下面的例子論證了parallexXxx系列的方法:
package com.javacodegeeks.java8.parallel.arrays;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelArrays {
public static void main( String[] args ) {
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong,
index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
}
}
上述這些代碼使用parallelSetAll()方法生成20000個隨機數(shù),然后使用parallelSort()方法進行排序。這個程序會輸出亂序數(shù)組和排序數(shù)組的前10個元素。上述例子的代碼輸出的結(jié)果是:
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
并發(fā)
基于新增的lambda表達式和steam特性,為Java 8中為java.util.concurrent.ConcurrentHashMap類添加了新的方法來支持聚焦操作;另外,也為java.util.concurrentForkJoinPool類添加了新的方法來支持通用線程池操作(更多內(nèi)容可以參考我們的并發(fā)編程課程)。
Java 8還添加了新的java.util.concurrent.locks.StampedLock類,用于支持基于容量的鎖——該鎖有三個模型用于支持讀寫操作(可以把這個鎖當(dāng)做是java.util.concurrent.locks.ReadWriteLock的替代者)。
在java.util.concurrent.atomic包中也新增了不少工具類,列舉如下:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
Nashorn引擎:jjs
Java 8提供了一些新的命令行工具,這部分會講解一些對開發(fā)者最有用的工具。jjs是一個基于標(biāo)準(zhǔn)Nashorn引擎的命令行工具,可以接受js源碼并執(zhí)行。例如,我們寫一個func.js文件,內(nèi)容如下:
function f() {
return 1;
};
print( f() + 1 );
可以在命令行中執(zhí)行這個命令:jjs func.js,控制臺輸出結(jié)果是:2
類依賴分析器:jdeps
jdeps是一個相當(dāng)棒的命令行工具,它可以展示包層級和類層級的Java類依賴關(guān)系,它以.class文件、目錄或者Jar文件為輸入,然后會把依賴關(guān)系輸出到控制臺。
我們可以利用jedps分析下Spring Framework庫,為了讓結(jié)果少一點,僅僅分析一個JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
jdeps org.springframework.core-3.0.5.RELEASE.jar
這個命令會輸出很多結(jié)果,我們僅看下其中的一部分:依賴關(guān)系按照包分組,如果在classpath上找不到依賴,則顯示"not found"。
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
-> java.io
-> java.lang
-> java.lang.annotation
-> java.lang.ref
-> java.lang.reflect
-> java.util
-> java.util.concurrent
-> org.apache.commons.logging not found
-> org.springframework.asm not found
-> org.springframework.asm.commons not found
org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
-> java.lang
-> java.lang.annotation
-> java.lang.reflect
-> java.util
JVM新特性
使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM參數(shù)方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原來的-XX:PermSize和-XX:MaxPermSize。
四大函數(shù)式接口
Function<T, R>
T:入?yún)㈩愋?,R:出參類型
調(diào)用方法:R apply(T t);
定義函數(shù)示例:Function<Integer, Integer> func = p -> p * 10; // 輸出入?yún)⒌?0倍
調(diào)用函數(shù)示例:func.apply(10); // 結(jié)果100Consumer<T>
T:入?yún)㈩愋?;沒有出參
調(diào)用方法:void accept(T t);
定義函數(shù)示例:Consumer<String> consumer= p -> System.out.println(p); // 因為沒有出參,常用于打印、發(fā)送短信等消費動作
調(diào)用函數(shù)示例:consumer.accept("18800008888");Supplier<T>
T:出參類型;沒有入?yún)?br> 調(diào)用方法:T get();
定義函數(shù)示例:Supplier<Integer> supplier= () -> 100; // 常用于業(yè)務(wù)“有條件運行”時,符合條件再調(diào)用獲取結(jié)果的應(yīng)用場景;運行結(jié)果須提前定義,但不運行。
調(diào)用函數(shù)示例:supplier.get();Predicate<T>
T:入?yún)㈩愋?;出參類型是Boolean
調(diào)用方法:boolean test(T t);
定義函數(shù)示例:Predicate<Integer> predicate = p -> p % 2 == 0; // 判斷是否、是不是偶數(shù)
調(diào)用函數(shù)示例:predicate.test(100); // 運行結(jié)果true