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所代替。