1. 程式人生 > >java8新特性實踐

java8新特性實踐

Lambda表示式

  • Lambda允許把函式作為一個方法的引數(函式作為引數傳遞進方法中),或者把程式碼看成資料

  • 最簡單的形式中,一個lambda可以由用逗號分隔的引數列表、–>符號與函式體三部分表示:

    Arrays.asList( 1, 2, 3 ).forEach( e -> System.out.println( e ) );
    
  • Lambda可以引用類的成員變數與區域性變數(如果這些變數不是final的話,它們會被隱含的轉為final,這樣效率更高):

    String str = ",";           //等價於:final String str = ",";
    Arrays.asList( "a", "b", "c" ).forEach( 
        ( String e ) -> System.out.print( e + str ) );  
    
  • Lambda可能會返回一個值。返回值的型別也是由編譯器推測出來的。如果lambda的函式體只有一行的話,那麼沒有必要顯式使用return語句:

    List<Integer> list = Arrays.asList( 4, 2, 1,3);
    list.sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
    list.forEach(e->System.out.println(e)); 
    

函式式介面

  • 如何使現有的函式友好地支援lambda,java8採取了增加函式式介面的概念。
  • 函式式介面就是一個只有一個方法的普通介面,可以隱式的轉換成lambda表示式,除了特殊方法:預設方法,靜態方法以及繼承自Object類的一些方法(toString(),equels()等)
  • 在使用中,函式式介面容易出錯,如果介面中被定義了另一個方法,那麼介面將不再是函式式介面,導致編譯失敗。為此Java8新增註解@FunctionalInterface。注意default預設方法和靜態方法不會影響函式式介面。

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    
  • java8新增預設方法和靜態方法擴充套件介面的宣告。

    public interface Convert {
    
        //可以包含靜態方法
        public static void hasStaticMethod(){
            System.out.println("包含靜態方法");
        }
    
        default void hasDefaultMethod(){
            System.out.println("包含預設方法");
        }
    }
    
  • 函式式介面示例

    @FunctionalInterface
    public interface Convert<F,T> {
    
        T convert(F from);
    
        //可以包含靜態方法
        public static void hasStaticMethod(){
            System.out.println("包含靜態方法");
        }
    
        default void hasDefaultMethod(){
            System.out.println("包含預設方法");
        }
    }
    
    public class ConvertMain {
    public static void main(String[] args) {
    
        Convert<String, Integer> converter = (from) -> Integer.valueOf(from);
        Integer converted = converter.convert("123");
        System.out.println(converted);
        converter.hasDefaultMethod();
        Convert.hasStaticMethod();
    
    
        // Function<T, R> -T作為輸入,返回的R作為輸出
        Function<String,String> function = (x) -> {System.out.print(x+": ");return "Function";};
        System.out.println(function.apply("hello world"));
    
        //Predicate<T> -T作為輸入,返回的boolean值作為輸出
        Predicate<String> pre = (x) ->{System.out.print(x);return false;};
        System.out.println(": "+pre.test("hello World"));
    
        //Consumer<T> - T作為輸入,執行某種動作但沒有返回值
        Consumer<String> con = (x) -> {System.out.println(x);};
        con.accept("hello world");
    
        //Supplier<T> - 沒有任何輸入,返回T
        Supplier<String> supp = () -> {return "Supplier";};
        System.out.println(supp.get());
    
    
        //BinaryOperator<T> -兩個T作為輸入,返回一個T作為輸出,對於“reduce”操作很有用
        BinaryOperator<String> bina = (x,y) ->{System.out.print(x+" "+y);return "BinaryOperator";};
        System.out.println("  "+bina.apply("hello ","world"));
    
    }
    

    }

方法引用(::)

  • 以直接引用已有Java類或物件(例項)的方法或構造器。與lambda聯合使用

  • 第一種方法引用是構造器引用,它的語法是Class::new,或者更一般的Class< T >::new。請注意構造器沒有引數。

    final Car car = Car.create( Car::new );
    final List< Car > cars = Arrays.asList( car );
    
  • 第二種方法引用是靜態方法引用,它的語法是Class::static_method。請注意這個方法接受一個Car型別的引數。

    cars.forEach( Car::collide );
    
  • 第三種方法引用是特定類的任意物件的方法引用,它的語法是Class::method。請注意,這個方法沒有引數。

    cars.forEach( Car::repair );
    
  • 最後,第四種方法引用是特定物件的方法引用,它的語法是instance::method。請注意,這個方法接受一個Car型別的引數

    final Car police = Car.create( Car::new );
    cars.forEach( police::follow );     
    
    
    private void sort(){
        List<Commodity> commodities=new ArrayList<>();
        //java 8之前
        commodities.sort(new Comparator<Commodity>() {
        @Override
        public int compare(Commodity o1, Commodity o2) {
                return o1.getPrice()-o2.getPrice();
            }
        });
        //java 8 lambda的寫法
        commodities.sort((Commodity o1,Commodity o2)->o1.getPrice()-o2.getPrice());
        //java 8 方法應用的寫法
        commodities.sort(Comparator.comparing(Commodity::getPrice));
    }
    

重複註解

  • java5開始引用註解機制,然而相同註解在同樣的地方只能宣告一次,java8引入重複註解。

  • 重複註解機制本身必須用@Repeatable註解。事實上是編譯器技巧的改變

更好的型別推斷

  • Java 8在型別推測方面有了很大的提高。在很多情況下,編譯器可以推測出確定的引數型別,這樣就能使程式碼更整潔。

    public class Value< T > {
        public static< T > T defaultValue() { 
            return null; 
        }
    
        public T getOrDefault( T value, T defaultValue ) {
            return ( value != null ) ? value : defaultValue;
        }
    }
    
    public class TypeInference {
        public static void main(String[] args) {
            final Value< String > value = new Value<>();
            value.getOrDefault( "22", Value.defaultValue() );
        }
    }
    

擴充套件註解的支援

  • Java 8擴充套件了註解的上下文。現在幾乎可以為任何東西添加註解:區域性變數、泛型類、父類與介面的實現,方法的異常也可以添加註解。

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

    ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是兩個新新增的用於描述適當的註解上下文的元素型別。

Optional

  • 新增類庫,為解決java中常見的空指標異常導致程式無法正常執行

  • Optional實際上是個容器:它可以儲存型別T的值,或者僅僅儲存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。

    //允許為空值,當值為空時也不會報錯
    Optional< String > name = Optional.ofNullable( null );
    System.out.println( "name是否有值 " + name.isPresent() );        
    System.out.println( "如果名稱為空: " + name.orElseGet( () -> "代替名稱" ) ); 
    System.out.println( name.map( s -> "名稱為: " + s  ).orElse( "名稱為空" ) );
    
    //不允許為空值,當值為空時會報錯
    Optional< String > firstName = Optional.of( "name" );
    System.out.println( "firstName是否有值 " + firstName.isPresent() );        
    System.out.println( "如果名稱為空: " + firstName.orElseGet( () -> "代替名稱" ) ); 
    System.out.println( firstName.map( s -> "名稱為: " + s  ).orElse( "名稱為空" ) );
    

    Optional文件

Stream

  • 把真正的函數語言程式設計風格引入到Java中。簡化了集合框架的處理。

    public class Streams  {
        private enum Status {
            OPEN, CLOSED
        };
    
        private static final class Task {
            private final Status status;
            private final Integer points;
    
            Task( final Status status, final Integer points ) {
                this.status = status;
                this.points = points;
            }
    
            public Integer getPoints() {
                return points;
            }
    
            public Status getStatus() {
                return status;
            }
    
            @Override
            public String toString() {
                return String.format( "[%s, %d]", status, points );
            }
        }
    }
    
    //task集合
    final Collection< Task > tasks = Arrays.asList(
        new Task( Status.OPEN, 5 ),
        new Task( Status.OPEN, 13 ),
        new Task( Status.CLOSED, 8 ) 
    );
    
    
    //所有狀態為OPEN的任務一共有多少分數
    final long totalPointsOfOpenTasks = tasks
        .stream()
        .filter( task -> task.getStatus() == Status.OPEN )
        .mapToInt( Task::getPoints )
        .sum();
    
    System.out.println( "Total points: " + totalPointsOfOpenTasks );
    

    首先,task集合被轉換化為stream。然後,filter操作過濾掉狀態為CLOSED的task。下一步,mapToInt操作通過Task::getPoints方法呼叫把Task的stream轉化為Integer的stream。最後,用sum函式把所有的分數加起來,得到最終的結果。

    stream注意事項:Ops

  • .stream操作被分成了中間操作與最終操作,中間操作返回一個新的stream物件。中間操作總是採用惰性求值方式,執行一個像filter這樣的中間操作實際上沒有進行任何過濾,相反它在遍歷元素時會產生了一個新的stream物件,這個新的stream物件包含原始stream中符合給定謂詞的所有元素。

  • 像forEach、sum這樣的最終操作可能直接遍歷stream,產生一個結果。當最終操作執行結束之後,stream管道被認為已經被消耗了,不能再使用。在大多數情況下,最終操作都是採用及早求值方式,及早完成底層資料來源的遍歷

  • stream另一個有價值的地方是能夠原生支援並行處理

    final double totalPoints = tasks
       .stream()
       .parallel()
       .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
       .reduce( 0, Integer::sum );
    
    System.out.println( "Total points (all tasks): " + totalPoints );
    
    
    //按照某種準則來對集合中的元素進行分組。
    final Map< Status, List< Task > > map = tasks
        .stream()
        .collect( Collectors.groupingBy( Task::getStatus ) );
    System.out.println( map );
    
    //計算整個集合中每個task分數(或權重)的平均值來結束task
    final Collection< String > result = tasks
        .stream()                                        // Stream< String >
        .mapToInt( Task::getPoints )                     // IntStream
        .asLongStream()                                  // LongStream
        .mapToDouble( points -> points / totalPoints )   // DoubleStream
        .boxed()                                         // Stream< Double >
        .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
        .mapToObj( percentage -> percentage + "%" )      // Stream< String> 
        .collect( Collectors.toList() );                 // List< String > 
    
    System.out.println( result );
    
  • Stream API不僅僅處理Java集合框架。像從文字檔案中逐行讀取資料這樣典型的I/O操作也很適合用Stream API來處理

    final Path path = new File( filename ).toPath();
    try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
        lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
    }
    

    對一個stream物件呼叫onClose方法會返回一個在原有功能基礎上新增了關閉功能的stream物件,當對stream物件呼叫close()方法時,與關閉相關的處理器就會執行。

Date/Time API (JSR 310)

  • Joda-Time——一個可替換標準日期/時間處理且功能非常強大的Java API

  • Clock類,它通過指定一個時區,然後就可以獲取到當前的時刻,日期與時間。Clock可以替換System.currentTimeMillis()與TimeZone.getDefault()。

    final Clock clock = Clock.systemUTC();
    System.out.println( clock.instant() );
    System.out.println( clock.millis() );
    
  • LocaleDate與LocalTime。LocaleDate只持有ISO-8601格式且無時區資訊的日期部分。相應的,LocaleTime只持有ISO-8601格式且無時區資訊的時間部分。LocaleDate與LocalTime都可以從Clock中得到

    final LocalDate date = LocalDate.now();
    final LocalDate dateFromClock = LocalDate.now( clock );
    
    System.out.println( date );
    System.out.println( dateFromClock );
    
    // Get the local date and local time
    final LocalTime time = LocalTime.now();
    final LocalTime timeFromClock = LocalTime.now( clock );
    
    System.out.println( time );
    System.out.println( timeFromClock );
    
  • LocaleDateTime把LocaleDate與LocaleTime的功能合併起來,它持有的是ISO-8601格式無時區資訊的日期與時間。

    final LocalDateTime datetime = LocalDateTime.now();
    final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
    
    System.out.println( datetime );
    System.out.println( datetimeFromClock );
    
  • 如果你需要特定時區的日期/時間,可以使用ZonedDateTime。它持有ISO-8601格式具具有時區資訊的日期與時間

    final ZonedDateTime zonedDatetime = ZonedDateTime.now();
    final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
    final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
    
    System.out.println( zonedDatetime );
    System.out.println( zonedDatetimeFromClock );
    System.out.println( zonedDatetimeFromZone );
    
  • Duration類:Duration使計算兩個日期間的差變的十分簡單。

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

JavaScript引擎Nashorn

  • Nashorn,一個新的JavaScript引擎隨著Java 8一起公諸於世,它允許在JVM上開發執行某些JavaScript應用。Nashorn就是javax.script.ScriptEngine的另一種實現,並且它們倆遵循相同的規則,允許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;" ) );
    

Base64:Java 8中,Base64編碼已經成為Java類庫的標準。

        import java.nio.charset.StandardCharsets;
        import java.util.Base64;

        public class Base64Test {
            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 );
            }
        }

    Base64類同時還提供了對URL、MIME友好的編碼器與解碼器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。

並行(parallel)陣列

  • Java 8增加了大量的新方法來對陣列進行並行處理。可以說,最重要的是parallelSort()方法,因為它可以在多核機器上極大提高陣列排序的速度。

        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個元素的陣列進行隨機賦值。然後,呼叫parallelSort方法。這個程式首先打印出前10個元素的值,之後對整個陣列排序。
    

併發(Concurrency)

  • 在新增Stream機制與lambda的基礎之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支援聚集操作。同時也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支援共有資源池(common pool)

  • 新增的java.util.concurrent.locks.StampedLock類提供一直基於容量的鎖,這種鎖有三個模型來控制讀寫操作(它被認為是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。

    在java.util.concurrent.atomic包中還增加了下面這些類:

    DoubleAccumulator
    DoubleAdder
    LongAccumulator
    LongAdder
    

類依賴分析器jdeps

  • jdeps是一個很有用的命令列工具。它可以顯示Java類的包級別或類級別的依賴。它接受一個.class檔案,一個目錄,或者一個jar檔案作為輸入。jdeps預設把結果輸出到系統輸出(控制檯)上。

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

Java虛擬機器(JVM)的新特性

  • PermGen空間被移除了,取而代之的是Metaspace(JEP 122)。JVM選項-XX:PermSize與-XX:MaxPermSize分別被-XX:MetaSpaceSize與-XX:MaxMetaspaceSize所代替。