Java8的新特性
Java8的新特性
1. 簡介。1
Java 8于2014年3月19日發(fā)布正式版,是自Java5以來最具革命性的版本星瘾,在語言、編譯器惧辈、類庫琳状、開發(fā)工具以及Java虛擬機(jī)等方面都帶來了不少新特性。
2. Java語言的新特性
2.1 函數(shù)式接口
如果一個(gè)接口定義個(gè)唯一一個(gè)抽象方法盒齿,那么這個(gè)接口就成為函數(shù)式接口念逞。這樣的接口可以被隱式轉(zhuǎn)換為lambda表達(dá)式。java.lang.Runnable與java.util.concurrent.Callable是函數(shù)式接口最典型的兩個(gè)例子边翁。在實(shí)際使用過程中翎承,如有某個(gè)人在接口定義中增加了另一個(gè)方法,這時(shí)符匾,這個(gè)接口就不再是函數(shù)式的了叨咖,并且編譯過程也會(huì)失敗。為了克服函數(shù)式接口的這種問題待讳,Java8增加了一種特殊的注解@FunctionalInterface(Java8中所有類庫的已有接口都添加了@FunctionalInterface注解)芒澜。
@FunctionalInterface //通過該注解聲明該接口為函數(shù)式接口
public interface Functional {
void method();
}
2.2 Lambda表達(dá)式
Lambda允許把函數(shù)作為一個(gè)方法的參數(shù).在最簡單的形式中仰剿,一個(gè)lambda可以由用逗號(hào)分隔的參數(shù)列表创淡、–>符號(hào)與函數(shù)體三部分表示。
lambda表達(dá)式的結(jié)構(gòu)
- 參數(shù)可以是零個(gè)或多個(gè)
- 參數(shù)類型可指定南吮,可省略(根據(jù)表達(dá)式上下文推斷)
- 參數(shù)包含在圓括號(hào)中琳彩,用逗號(hào)分隔
- 表達(dá)式主體可以是零條或多條語句,包含在花括號(hào)中
- 表達(dá)式主體只有一條語句時(shí),花括號(hào)可省略
- 表達(dá)式主體有一條以上語句時(shí),表達(dá)式的返回類型與代碼塊的返回類型一致
- 表達(dá)式只有一條語句時(shí)部凑,表達(dá)式的返回類型與該語句的返回類型一致
通常情況下露乏,我們要設(shè)置一個(gè)監(jiān)聽事件,需要使用匿名內(nèi)部類的寫法如下:
button.addActionListener(new ActionListener) {
public void actionPerformed(ActionEvent e) {
ui.dazzle(e.getModifiers());
}
}
使用lambda表達(dá)式
//lambda表達(dá)式設(shè)置事件監(jiān)聽
button.setOnClickListener((e) -> System.out.print("aaa"));
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
參數(shù)e的類型可以省略涂邀,由編譯器推測(cè)出來的瘟仿。
如果函數(shù)有多行代碼,可以將函數(shù)體放在花括號(hào)中比勉。
Lambda可以引用類的成員變量與局部變量(如果這些變量不是final的話劳较,它們會(huì)被隱含的轉(zhuǎn)為final驹止,這樣效率更高)
編譯器負(fù)責(zé)推導(dǎo)lambda表達(dá)式的類型,檢查lambda表達(dá)式的類型和目標(biāo)類型的方法簽名(method signature)是否一致观蜗。當(dāng)且僅當(dāng)下面所有條件均滿足時(shí)臊恋,lambda表達(dá)式才可以被賦給目標(biāo)類型T:
- T是一個(gè)函數(shù)式接口
- lambda表達(dá)式的參數(shù)和T的方法參數(shù)在數(shù)量和類型上一一對(duì)應(yīng)
- lambda表達(dá)式的返回值和T的方法返回值相兼容(Compatible)
- lambda表達(dá)式內(nèi)所拋出的異常和T的方法throws類型相兼容
2.3 方法引用
方法引用提供了非常有用的語法,可以直接引用已有Java類或?qū)ο螅▽?shí)例)的方法或構(gòu)造器墓捻。與lambda聯(lián)合使用抖仅,方法引用可以使語言的構(gòu)造更緊湊簡潔,減少冗余代碼砖第。
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
//構(gòu)造器引用撤卢,它的語法是Class::new,或者更一般的Class< T >::new梧兼。
final Car car = Car.create( Car::new );
//靜態(tài)方法引用凸丸,它的語法是Class::static_method
cars.forEach( Car::collide );
//特定類的任意對(duì)象的方法引用,它的語法是Class::method
cars.forEach( Car::repair );
//特定對(duì)象的方法引用袱院,它的語法是instance::method
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
再舉個(gè)栗子:
interface StringFunc {
String func(String n);
}
class MyStringOps {
//靜態(tài)方法: 反轉(zhuǎn)字符串
public static String strReverse(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
public class MethodRefDemo {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
//一般寫法
String outStr1 = stringOp(new StringFunc() {
@Override
public String func(String n) {
return MyStringOps.strReverse(n);
}
}, inStr);
//使用lambda表達(dá)式
String outStr2 = stringOp(n -> MyStringOps.strReverse(n), inStr);
//MyStringOps::strReverse 相當(dāng)于實(shí)現(xiàn)了接口方法func()屎慢,strReverse參數(shù)個(gè)數(shù)和類型需要和stringOp保持一致
String outStr3 = stringOp(MyStringOps::strReverse, inStr);
}
}
2.4 默認(rèn)方法和靜態(tài)方法
默認(rèn)方法讓我們能給我們的軟件庫的接口增加新的方法,并且能保證對(duì)使用這個(gè)接口的老版本代碼的兼容性忽洛。此外在Java8的接口中腻惠,不光能寫默認(rèn)方法,還能寫靜態(tài)方法.
public interface TimeClient {
void setTime(int hour, int minute, int second);
void setDate(int day, int month, int year);
//靜態(tài)方法
static public ZoneId getZoneId (String zoneString) {
try {
return ZoneId.of(zoneString);
} catch (DateTimeException e) {
System.err.println("Invalid time zone: " + zoneString +
"; using default time zone instead.");
return ZoneId.systemDefault();
}
}
//默認(rèn)方法
default ZonedDateTime getZonedDateTime(String zoneString) {
return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
}
默認(rèn)方法關(guān)鍵字為default欲虚,以往我們只能在接口中定義只有聲明沒有實(shí)現(xiàn)的方法集灌。有了默認(rèn)方法,我們就不需要修改繼承接口的實(shí)現(xiàn)類复哆,就給接口添加了新的方法實(shí)現(xiàn)欣喧。
繼承或?qū)崿F(xiàn)一個(gè)含有默認(rèn)方法的接口一般有以下三種情況:
- 繼承的接口直接繼承默認(rèn)方法
- 重新聲明默認(rèn)方法,這樣會(huì)使得這個(gè)方法變成抽象方法
- 重新定義默認(rèn)方法梯找,這樣會(huì)使得方法被重寫
2.5 擴(kuò)展注解
Java8引入了類型注解和重復(fù)注解機(jī)制.
2.5.1 類型注解
在java 8之前唆阿,注解只能是在聲明的地方所使用,java8開始锈锤,注解可以應(yīng)用在任何地方.
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder< @NonEmpty T > extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder< String > holder = new @NonEmpty Holder< String >();
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
}
}
類型注解被用來支持在Java的程序中做強(qiáng)類型檢查驯鳖。配合第三方插件工具可以在編譯的時(shí)候檢測(cè)出運(yùn)行時(shí)異常。
ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是兩個(gè)新添加的用于描述適當(dāng)?shù)淖⒔馍舷挛牡脑仡愋途妹狻T贘ava語言中浅辙,注解處理API也有小的改動(dòng)來識(shí)別新增的類型注解。
2.4.2 重復(fù)注解
相同的注解可以在同一地方使用多次阎姥。Java8以前的版本使用注解有一個(gè)限制是相同的注解在同一位置只能使用一次记舆。
@interface MyHints {
Hint[] value();
}
@Repeatable(MyHints.class)
@interface Hint {
String value();
}
//使用包裝類當(dāng)容器來存多個(gè)注解(舊版本方法)改艇,但可讀性不是很好
@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}
//多重注解
@Hint("hint1")
@Hint("hint2")
class Person {}
for (Hint hint : MyHints.class.getAnnotationsByType(Hint.class)) {
System.out.println(hint.value());
}
3. JavaScript引擎Nashorn
Nashorn说榆,一個(gè)新的JavaScript引擎隨著Java8一起公諸于世季惩,它允許在JVM上開發(fā)運(yùn)行某些JavaScript應(yīng)用村视。允許Java與JavaScript相互調(diào)用。下面看一個(gè)例子:
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
在 JavaScript 端調(diào)用 Java 方法
//先定義一個(gè)Java類
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
//JS中通過Java.type API來引用Java類
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
// Hi there from Java, John Doe
// greetings from java
4. Java 類庫的新特性
Java 8 通過增加大量新類盛正,擴(kuò)展已有類的功能的方式來改善對(duì)并發(fā)編程删咱、函數(shù)式編程、日期/時(shí)間相關(guān)操作以及其他更多方面的支持豪筝。
4.1 Optional
Optional是一個(gè)容器對(duì)象痰滋,可以用它來封裝可能為空的引用。Optional對(duì)象通過缺失值代表null续崖。這個(gè)類有許多實(shí)用的方法來促使代碼能夠處理一些像可用或者不可用的值敲街,而不是檢查那些空值(null)。
Optional的優(yōu)點(diǎn)
- 顯式的提醒你需要關(guān)注null的情況严望,對(duì)程序員是一種字面上的約束
- 將平時(shí)的一些顯式的防御性檢測(cè)給標(biāo)準(zhǔn)化了多艇,并提供一些可串聯(lián)操作
- 解決null會(huì)導(dǎo)致疑惑的概念
Optional類的用法:
public class NewFeaturesTester {
public static void main(String args[]){
NewFeaturesTester tester = new NewFeaturesTester();
Integer value1 = null;
Integer value2 = new Integer(5);
// ofNullable 允許傳參時(shí)給出 null
Optional<Integer> a = Optional.ofNullable(value1);
// 如果傳遞的參數(shù)為null,那么 of 將拋出空指針異常(NullPointerException)
Optional<Integer> b = Optional.of(value2);
System.out.println(tester.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
// isPresent 用于檢查值是否存在
System.out.println("First parameter is present: " + a.isPresent());
System.out.println("Second parameter is present: " + b.isPresent());
// 如果a為null像吻,則返回傳入的參數(shù)值
Integer value1 = a.orElse(new Integer(0));
// get 用于獲得值峻黍,條件是這個(gè)值必須存在
Integer value2 = b.get();
return value1 + value2;
}
}
Optional.ofNullable()
//將一個(gè)對(duì)象放入Optional容器。傳 null 進(jìn)到就得到 Optional.empty(), 非 null 就調(diào)用 Optional.of(obj)
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
Optional.of()
//將一個(gè)對(duì)象放入Optional容器拨匆。要求傳入的 obj 不能是 null 值的姆涩,否則NullPointerException
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
Optional.isPresent()
//返回一個(gè)值是否存在
public boolean isPresent() {
return value != null;
}
Optional.orElse()
//返回Optional容器中的值,如果為空則返回傳入的默認(rèn)參數(shù)值
public T orElse(T other) {
return value != null ? value : other;
}
Optional.get()
//如果一個(gè)值存在于當(dāng)前Optional中惭每,則返回這個(gè)值骨饿;否則將拋出一個(gè)NoSuchElementException異常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
4.2 Stream
Java8添加的Stream API(java.util.stream)把真正的函數(shù)式編程風(fēng)格引入到Java中,是 Java 8用來補(bǔ)充集合類。允許我們?nèi)ケ磉_(dá)我們想要完成什么而不是要怎樣做台腥。
4.2.1 Stream一般語法
4.2.2 創(chuàng)建Stream
- 通過Stream接口的靜態(tài)工廠方法
Stream.of(1, 2, 3, 5);
Stream.generate(Math::random);
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
- 通過Collection接口的默認(rèn)方法
new ArrayList<>().stream();
Arrays.stream(Object[])
4.2.3 轉(zhuǎn)換Stream
轉(zhuǎn)換Stream其實(shí)就是把一個(gè)Stream通過某些行為轉(zhuǎn)換成一個(gè)新的Stream宏赘。它不會(huì)修改原始的數(shù)據(jù)源,而且是由在終點(diǎn)操作開始的時(shí)候才真正開始執(zhí)行黎侈。Stream接口中定義了幾個(gè)常用的轉(zhuǎn)換方法:
- distinct: 對(duì)于Stream中包含的元素進(jìn)行去重操作(去重邏輯依賴元素的equals方法)察署,新生成的Stream中沒有重復(fù)的元素。該方法依賴于toString方法
- filter: 對(duì)于Stream中包含的元素使用給定的過濾函數(shù)進(jìn)行過濾操作蜓竹,新生成的Stream只包含符合條件的元素
- limit:對(duì)一個(gè)Stream進(jìn)行截?cái)嗖僮骰福@取其前N個(gè)元素储藐,如果原Stream中包含的元素個(gè)數(shù)小于N俱济,那就獲取其所有的元素
- peek: 生成一個(gè)包含原Stream的所有元素的新Stream,同時(shí)會(huì)提供一個(gè)消費(fèi)函數(shù)(Consumer實(shí)例)钙勃,新Stream每個(gè)元素被消費(fèi)的時(shí)候都會(huì)執(zhí)行給定的消費(fèi)函數(shù)
int[] number = {1,2,3,4,5};
System.out.print("和為:" +Arrays.stream(number).filter(num -> num > 0).distinct().peek(System.out::println).skip(2).sum());
結(jié)果:
1
2
3
4
5
和為:12
性能問題:
轉(zhuǎn)換操作都是lazy的蛛碌,多個(gè)轉(zhuǎn)換操作只會(huì)在匯聚操作的時(shí)候融合起來,一次循環(huán)完成辖源。我們可以這樣簡單的理解蔚携,Stream里有個(gè)操作函數(shù)的集合希太,每次轉(zhuǎn)換操作就是把轉(zhuǎn)換函數(shù)放入這個(gè)集合中,在匯聚操作的時(shí)候循環(huán)Stream對(duì)應(yīng)的集合酝蜒,然后對(duì)每個(gè)元素執(zhí)行所有的函數(shù)誊辉。
stream提供了parallelStream使用多線程進(jìn)行操作,加大了運(yùn)算效率.
4.2.4 匯聚/歸納(Reduce)Stream
匯聚操作(也稱為折疊)接受一個(gè)元素序列為輸入,反復(fù)使用某個(gè)合并操作亡脑,把序列中的元素合并成一個(gè)匯總的結(jié)果堕澄。比如查找一個(gè)數(shù)字列表的總和或者最大值,或者把這些數(shù)字累積成一個(gè)List對(duì)象霉咨。
常用操作:
collect:把Stream中的元素收集到一個(gè)結(jié)果容器中
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());
forEach:接收一個(gè) Lambda 表達(dá)式蛙紫,然后在 Stream 的每一個(gè)元素上執(zhí)行該表達(dá)式
Arrays.asList(1,2,3,4,5,6).stream().forEach(i -> System.out.println(i + "") );
4.3 Date/Time API (JSR 310)
Java8新的日期和時(shí)間庫很好的解決了以前日期和時(shí)間類的很多弊端。并且也借鑒了第三方日期庫joda很多的優(yōu)點(diǎn)途戒。
Java8新增的time包中的是類是不可變且線程安全的坑傅。新的時(shí)間及日期API位于java.time中,下面是一些關(guān)鍵類
Instant——它代表的是時(shí)間戳
LocalDate——不包含具體時(shí)間的日期喷斋,比如2014-01-14唁毒。它可以用來存儲(chǔ)生日,周年紀(jì)念日星爪,入職日期等
LocalTime——它代表的是不含日期的時(shí)間
LocalDateTime——它包含了日期及時(shí)間枉证,不過還是沒有偏移信息或者說時(shí)區(qū)。
ZonedDateTime——這是一個(gè)包含時(shí)區(qū)的完整的日期時(shí)間移必,偏移量是以UTC/格林威治時(shí)間為基準(zhǔn)的室谚。
//java8自帶了Clock類,用來獲取某個(gè)時(shí)區(qū)下的瞬時(shí)時(shí)間崔泵、日期秒赤。
final Clock clock = Clock.systemUTC();
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
//通過of方法將日期格式化輸出
//LocalDateTime相當(dāng)于LocalDate和LocalTime的結(jié)合
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 );
//獲取當(dāng)前時(shí)間戳≡魅常可與Date類互相轉(zhuǎn)換
Instant.now()
//使用自定義的格式器來解析日期
String holiday = "0713 2017";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd YYYY");
LocalDate formateDate = LocalDate.parse(holiday, formatter);
輸出結(jié)果:
16:24:42.742
08:24:42.742
2017-07-13T16:24:42.742
2017-07-13T08:24:42.742
Duration in days: 365
Duration in hours: 8783
4.5 Base64
Base64就是用來將非ASCII字符的數(shù)據(jù)轉(zhuǎn)換成ASCII字符的一種方法入篮。適合在http,mime協(xié)議下快速傳輸數(shù)據(jù)幌甘。
示例:
String str = Base64.getEncoder().encodeToString("Hello world!".getBytes("utf-8"));
System.out.println(str);
byte[] bytes=Base64.getDecoder().decode(str);
System.out.println(new String(bytes,"utf-8"));
6. 其他新特性
更好的類型推測(cè)機(jī)制:Java8在類型推測(cè)方面有了很大的提高潮售,這就使代碼更整潔,不需要太多的強(qiáng)制類型轉(zhuǎn)換了锅风。
編譯器優(yōu)化:Java 8將方法的參數(shù)名加入了字節(jié)碼中酥诽,這樣在運(yùn)行時(shí)通過反射就能獲取到參數(shù)名,只需要在編譯時(shí)使用-parameters參數(shù)皱埠。
并行(parallel)數(shù)組:支持對(duì)數(shù)組進(jìn)行并行處理肮帐,主要是parallelSort()方法,它可以在多核機(jī)器上極大提高數(shù)組排序的速度。
內(nèi)存模型換成紅黑樹,有利于gc,減少內(nèi)存泄露java內(nèi)存分代進(jìn)行了改進(jìn),取消了永久代,變成了metaSpace
concurrentHashMap變成了cas無鎖模式,只有在擴(kuò)容的時(shí)候才會(huì)用sync進(jìn)行鎖,cas的無鎖模式使得concurrentHashMap在吞吐量上有了一定等級(jí)的提升
hashMap的優(yōu)化.在hash沖突的時(shí)候,鏈表長度大于默認(rèn)因子數(shù)8的時(shí)候,會(huì)變更為紅黑樹,利用紅黑樹快速增刪改查的特點(diǎn)提高HashMap的性能,用以提高效率.
7. Android中會(huì)用Java8
Android N(SDK25)開始支持Java8的部分特性训枢。
如果不是Android N托修,需要在gradle中添加如下設(shè)置
android {
...
defaultConfig {
...
jackOptions {
enabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}