JAVA8新特性(上)

1. 簡介

毫無疑問,Java 8是Java自Java 5(發佈於2004年)之後的最重要的版本。這個版本包含語言、編譯器、庫、工具和JVM等方面的十多個新特性。

在本文中我們將學習這些新特性,並用實際的例子說明在什麼場景下適合使用。

這個教程包含Java開發者經常面對的幾類問題:

  • 語言
  • 編譯器
  • 工具
  • 運行時(JVM)

2. Java語言的新特性

Java 8是Java的一個重大版本,有人認爲,雖然這些新特性領Java開發人員十分期待,但同時也需要花不少精力去學習。在這一小節中,我們將介紹Java 8的大部分新特性。

2.1 Lambda表達式和函數式接口

  Lambda表達式(也稱爲閉包)是Java 8中最大和最令人期待的語言改變。它允許我們將函數當成參數傳遞給某個方法,或者把代碼本身當作數據處理:函數式開發者非常熟悉這些概念。

(parameters參數) -> expression表達式或方法體

paramaters:類似方法中的形參列表,這裏的參數是函數式接口裏的參數     ->:可理解爲“被用於”的意思

Lambda的設計耗費了很多時間和很大的社區力量,最終找到一種折中的實現方案,可以實現簡潔而緊湊的語言結構。

Lambda表達式可以引用類成員和局部變量(會將這些變量隱式得轉換成final的),例如下列兩個代碼塊的效果完全相同:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

 

匿名傳統內部類與使用lambad比較

// 使用 java 7 排序(傳統的使用匿名內部類)
    private void sortUsingJava7(List<String> names) {
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
    }

    // 使用 java 8 排序(使用lambad)
    private void sortUsingJava8(List<String> names) {
        Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
}

公共定義的函數式接口

a.功能性接口:Function<T, R>,有輸入參數,有返回值,是對接收一個T類型參數,返回R類型的結果的方法的抽象,通過調用apply方法執行內容

需求:給定一個字符串,返回字符串長度

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        int length = testFun(str, (s) -> s.length());
        System.out.println(length);
    }
    public static int testFun(String str, Function<String, Integer> fun) {
        // 執行
        Integer length = fun.apply(str);
        return length;
    }

}

實例方法引用

排序後輸出

b.消費型接口:Consumer<T>

有輸入參數,沒返回值

對應的方法類型爲接收一個參數,沒有返回值

一般來說使用Consumer接口往往伴隨着一些期望狀態的改變

或者事件的發生,典型的forEach就是使用的Consumer接口

雖然沒有任何的返回值,但是向控制檯輸出結果

Consumer 使用accept對參數執行行爲

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        testCon(str,(s)->System.out.println(s));
    }

    public static void testCon(String str, Consumer<String> con) {
        // 執行
        con.accept(str);
    }

}

c.供給型接口:Supplier<T>,無傳入參數,有返回值,該接口對應的方法類型不接受參數,但是提供一個返回值,使用get()方法獲得這個返回值

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        String resultStr = testSup(()->str);
        System.out.println(resultStr);
    }

    public static String testSup(Supplier<String> sup) {
        // 執行
        String s = sup.get();
        return s;
    }
}

d.斷言型接口:Predicate<T>

有傳入參數,有返回值Boolean

該接口對應的方法爲接收一個參數,返回一個Boolean類型值

多用於判斷與過濾,使用test()方法執行這段行爲

需求:輸入字符串,判斷長度是否大於0

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        Boolean flg = testSup(str,(s)->str.length()>0);
        if (flg){
            System.out.println("字符串大於0");
        }else {
            System.out.println("字符串小於0");
        }
    }
    public static Boolean testSup(String str,Predicate<String> pre) {
        // 執行
        Boolean b = pre.test(str);
        return b;
    }
}

2.2 lambad表達式優點

a.極大的減少代碼冗餘,同時可讀性也好過冗長的匿名內部類

b.與集合類批處理操作結合,實現內部迭代,並充分利用現代多核CPU進行並行計算。之前集合類的迭代都是外部的,即客戶代碼。而內部迭代意味着由Java類庫來進行迭代,而不是客戶代碼

流的性能測試:https://mp.weixin.qq.com/s/-p-N-K_oY-vGtvBIKUqShA

2. Java官方庫的新特性

Java 8增加了很多新的工具類(date/time類),並擴展了現存的工具類,以支持現代的併發編程、函數式編程等。

2.1 Optional

Java應用中最常見的bug就是空值異常。在Java 8之前,Google Guava引入了Optionals類來解決NullPointerException,從而避免源碼被各種null檢查污染,以便開發者寫出更加整潔的代碼。Java 8也將Optional加入了官方庫。

接下來看一點使用Optional的例子:可能爲空的值或者某個類型的值:

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );        
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

如果Optional實例持有一個非空值,則isPresent()方法返回true,否則返回false;orElseGet()方法,Optional實例持有null,則可以接受一個lambda表達式生成的默認值;map()方法可以將現有的Opetional實例的值轉換成新的值;orElse()方法與orElseGet()方法類似,但是在持有null的時候返回傳入的默認值。

上述代碼的輸出結果如下:

Full Name is set? false
Full Name: [none]
Hey Stranger!

2.2 Streams

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        strings.forEach(s -> System.out.println(s));//全部輸出
        List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());//經過過濾後輸出
        filtered.forEach(s -> System.out.println(s));

A.流中使用forEach來迭代流中的每個數據

Random random = new Random();
random.ints().limit(10).forEach(System.out :: println);
List<String> strings1 = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
long count = strings1.stream().filter(s -> s.isEmpty()).count();

B.Filter方法用於通過設置的條件過濾出元素(見上面程序)

C.limit 方法用於獲取指定數量的流見下面程序(獲取10個數)

Random random2 = new Random();
random2.ints().limit(10).sorted().forEach(System.out::println);

D.sorted 方法用於對流進行排序,見上面程序

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
Long count2 = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("並行(parallel)程序空字符串的數量" + count2);

E.並行處理字符串(與fork/join原理一樣,利用多核CPU)

List<String> strings3 = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered1  = strings3.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
System.out.println("篩選列表: " + filtered1);

//將流中的元素放置到一個列表集合中去。這個列表默認爲ArrayList

 String mergedString  = strings3.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(","));
 System.out.println("合併字符串: " + mergedString);

//joining的目的是將流中的元素全部以字符序列的方式連接到一起,可以指定連接符,甚至是結果的前後綴。

2.3 Nashorn JavaScript引擎

Java 8提供了新的Nashorn JavaScript引擎,使得我們可以在JVM上開發和運行JS應用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一個實現版本,這類Script引擎遵循相同的規則,允許Java和JavaScript交互使用,例子代碼如下:

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

2.4 Base64

對Base64編碼的支持已經被加入到Java 8官方庫中,這樣不需要使用第三方庫就可以進行Base64編碼,例子代碼如下:

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 );
    }
}

這個例子的輸出結果如下:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

新的Base64API也支持URL和MINE的編碼解碼。
(Base64.getUrlEncoder() / Base64.getUrlDecoder()Base64.getMimeEncoder() / Base64.getMimeDecoder())。

BASE64不是用來加密的,是BASE64編碼後的字符串,例如:在上傳圖片時候就會將圖片轉成base64,之前用過的freemark生成world文檔就是。全部都是由標準鍵盤上面的常規字符組成,這樣編碼後的字符串在網關之間傳遞不會產生UNICODE字符串不能識別或者丟失的現象。你再仔細研究下EMAIL就會發現其實EMAIL就是用base64編碼過後再發送的。然後接收的時候再還原。

2.5 並行數組

Java8版本新增了很多新的方法,用於支持並行數組處理。最重要的方法是parallelSort(),可以顯著加快多核機器上的數組排序。下面的例子論證了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 + " " ) );//取前10位數
        System.out.println();
        Arrays.parallelSort( arrayOfLong );        
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );//排序後取前10位數
        System.out.println();
    }
}

上述這些代碼使用parallelSetAll()方法生成20000個隨機數,然後使用parallelSort()方法進行排序。這個程序會輸出亂序數組和排序數組的前10個元素。上述例子的代碼輸出的結果是:

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
Sorted: 39 220 263 268 325 607 655 678 723 793

3. 新的Java工具

Java 8提供了一些新的命令行工具,這部分會講解一些對開發者最有用的工具。

3.1 Nashorn引擎:jjs

jjs是一個基於標準Nashorn引擎的命令行工具,可以接受js源碼並執行。例如,我們寫一個func.js文件,內容如下:

function f() { 
     return 1; 
}; 

print( f() + 1 );

可以在命令行中執行這個命令:jjs func.js,控制檯輸出結果是:

2

如果需要了解細節,可以參考官方文檔

3.2 類依賴分析器:jdeps

jdeps是一個相當棒的命令行工具,它可以展示包層級和類層級的Java類依賴關係,它以.class文件、目錄或者Jar文件爲輸入,然後會把依賴關係輸出到控制檯。

我們可以利用jedps分析下Spring Framework庫,爲了讓結果少一點,僅僅分析一個JAR文件:org.springframework.core-3.0.5.RELEASE.jar

jdeps org.springframework.core-3.0.5.RELEASE.jar

這個命令會輸出很多結果,我們僅看下其中的一部分:依賴關係按照包分組,如果在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

 

 

其他補充

Lambda的設計者們爲了讓現有的功能與Lambda表達式良好兼容,考慮了很多方法,於是產生了函數接口這個概念。函數接口指的是隻有一個函數的接口,這樣的接口可以隱式轉換爲Lambda表達式。java.lang.Runnablejava.util.concurrent.Callable是函數式接口的最佳例子。在實踐中,函數式接口非常脆弱:只要某個開發者在該接口中添加一個函數,則該接口就不再是函數式接口進而導致編譯失敗。爲了克服這種代碼層面的脆弱性,並顯式說明某個接口是函數式接口,Java 8 提供了一個特殊的註解@FunctionalInterface(Java 庫中的所有相關接口都已經帶有這個註解了),舉個簡單的函數式接口的定義:

@FunctionalInterface
public interface Functional {
    void method();
}

不過有一點需要注意,默認方法和靜態方法不會破壞函數式接口的定義,因此如下的代碼是合法的。

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {            
    }        
}

Lambda表達式作爲Java 8的最大賣點,它有潛力吸引更多的開發者加入到JVM平臺,並在純Java編程中使用函數式編程的概念。如果你需要了解更多Lambda表達式的細節,可以參考官方文檔

 

Date/Time API(JSR 310)

Java 8引入了新的Date-Time API(JSR 310)來改進時間、日期的處理。時間和日期的管理一直是最令Java開發者痛苦的問題。java.util.Date和後來的java.util.Calendar一直沒有解決這個問題(甚至令開發者更加迷茫)。

1、本地化日期時間 API

public class Main {

    public static void main(String[] args) {
        Main java8tester = new Main();
        java8tester.testLocalDateTime();
    }

    public void testLocalDateTime(){

        // 獲取當前的日期時間
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("當前時間: " + currentTime);

        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("date1: " + date1);

        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int seconds = currentTime.getSecond();

        System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);

        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date2: " + date2);

        // 12 december 2014
        LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
        System.out.println("date3: " + date3);

        // 22 小時 15 分鐘
        LocalTime date4 = LocalTime.of(22, 15);
        System.out.println("date4: " + date4);

        // 解析字符串
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date5: " + date5);
    }
}

輸出結果

2、使用時區的日期時間API

public class Main {

    public static void main(String[] args) {
        Main java8tester = new Main();
        java8tester.testZonedDateTime();
    }

    public void testZonedDateTime(){

        // 獲取當前時間日期
        ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
        System.out.println("date1: " + date1);

        ZoneId id = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId: " + id);

        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("當期時區: " + currentZone);
    }
}

待分析:

方法引用

重複註解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章