Java 8特性
目錄
- 1.?Java8的新特性
- 1.1.?Lambda表達式和函數式接口
- 1.2.?接口的默認方法和靜態方法
- 1.3.?方法引用
- 2.?Java 8?庫的新特性
- 2.1.?Optional
- 2.2.?Stream
- 2.3.?日期時間API(JSR310)
- 2.4.?Base64
- 2.5.?并行數組
- 2.6.?并發
- 3.?新的工具
- 3.1.?類依賴分析工具:jdeps
- 4.?JVM的新特性
- 5. 參考資料
?
正文
回到頂部1.?Java8的新特性
1.1.?Lambda表達式和函數式接口
最簡單的Lambda表達式可以用逗號分隔的參數列表、->符號和功能語句塊來表示。示例如下:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );?請注意到編譯器會根據上下文來推測參數的類型,或者你也可以顯示地指定參數類型,只需要將類型包在括號里。舉個例子:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );?Lambda表達式可能會引用類的成員或者局部變量會被隱式地轉變成final類型,下面兩種寫法的效果是一樣的:
String separator = ","; //separator = ";;";//該行編譯時會報錯:從lambda 表達式引用的本地變量必須是最終變量或實際上的最終變量 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 ) );?Lambda表達式可能會有返回值,編譯器會根據上下文推斷返回值的類型。如果lambda的語句塊只有一行,不需要return關鍵字。下面兩個寫法是等價的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );?和
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {int result = e1.compareTo( e2 );return result; } );?Java語言的設計者們思考了很多如何讓現有的功能和lambda表達式友好兼容。于是就有了函數接口這個概念。函數接口是一種只有一個方法的接口,函數接口可以隱式地轉換成lambda表達式。
java.lang.Runnable?和java.util.concurrent.Callable是函數接口兩個最好的例子。但是在實踐中,函數接口是非常脆弱的,只要有人在接口里添加多一個方法,那么這個接口就不是函數接口了,就會導致編譯失敗。Java 8提供了一個特殊的注解@FunctionalInterface來克服上面提到的脆弱性并且顯示地表明函數接口的目的(java里所有現存的接口都已經加上了@FunctionalInterface)。讓我們看看一個簡單的函數接口定義:
@FunctionalInterface public interface Functional {void method(); }?我們要記住默認的方法和靜態方法(下一節會具體解釋)不會違反函數接口的約定,例子如下:
@FunctionalInterface public interface FunctionalDefaultMethods {void method();default void defaultMethod() {} }?函數式接口的重要屬性是:我們能夠使用lambda實例化它們。下面是實例化Runnable函數式接口的一個例子。
Runnable r = () ->{ System.out.println("Running!"); }?新版本向?java.util.function包中添加了很多新的函數式接口。下面是一些例子:
Function<T, R>——將T作為輸入,返回R作為輸出
Predicate<T>——將T作為輸入,返回一個布爾值作為輸出
Consumer<T>——將T作為輸入,不返回任何內容
Supplier<T>——沒有輸入,返回T
BinaryOperator<T>——將兩個T作為輸入,返回一個T作為輸出
?
1.2.?接口的默認方法和靜態方法
Java 8增加了兩個新的概念在接口聲明的時候:默認和靜態方法。默認方法允許我們在接口里添加新的方法,而不會破壞實現這個接口的已有類的兼容性,也就是說不會強迫實現接口的類實現默認方法。
默認方法和抽象方法的區別是抽象方法必須要被實現,默認方法不是。作為替代方式,接口可以提供一個默認的方法實現,所有這個接口的實現類都會通過繼承得到這個方法(如果有需要也可以重寫這個方法),讓我們來看看下面的例子:
private interface Defaulable {// Interfaces now allow default methods, the implementer may or// may not implement (override) them.default String notRequired() {return "Default implementation";} }private static class DefaultableImpl implements Defaulable {}private static class OverridableImpl implements Defaulable {@Overridepublic String notRequired() {return "Overridden implementation";} }?接口Defaulable使用default關鍵字聲明了一個默認方法notRequired(),類DefaultableImpl實現了Defaulable接口,沒有對默認方法做任何修改。另外一個類OverridableImpl重寫類默認實現,提供了自己的實現方法。注意,接口不能為Object類中的任何方法提供默認的實現。
Java 8?的另外一個有意思的新特性是接口里可以聲明靜態方法,并且可以實現。例子如下:
private interface DefaulableFactory {// Interfaces now allow static methodsstatic Defaulable create( Supplier< Defaulable > supplier ) {return supplier.get();} }?下面是把接口的靜態方法和默認方法放在一起的示例(::new?是構造方法引用,后面會有詳細描述):
public static void main( String[] args ) {Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );System.out.println( defaulable.notRequired() );defaulable = DefaulableFactory.create( OverridableImpl::new );System.out.println( defaulable.notRequired() ); }?控制臺的輸出如下:
Default implementation Overridden implementation??
1.3.?方法引用
方法引用提供了一個很有用的語義來直接訪問類或者實例的已經存在的方法或者構造方法。結合Lambda表達式,方法引用使語法結構緊湊簡明。不需要復雜的引用。
下面我們用Car?這個類來做示例,Car這個類有不同的方法定義。讓我們來看看java 8支持的4種方法引用。
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() );} }?第一種方法引用是構造方法引用,語法是: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 );?運行這些例子我們將會在控制臺得到如下信息(Car的實例可能會不一樣):?
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d??
回到頂部2.?Java 8?庫的新特性
2.1.?Optional
著名的NullPointerException?是引起系統失敗最常見的原因。很久以前Google Guava項目引入了Optional作為解決空指針異常的一種方式,不贊成代碼被null檢查的代碼污染,期望程序員寫整潔的代碼。受Google Guava的鼓勵,Optional?現在是Java 8庫的一部分。
Optional?只是一個容器,它可以保存一些類型的值或者null。它提供很多有用的方法,所以沒有理由顯式地檢查null。
讓我們看看兩個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的值為空時接受一個方法返回默認值。map()方法轉化Optional當前的值并且返回一個新的Optional實例。orElse方法和orElseGet類似,但是它不接受一個方法,而是接受一個默認值。上面代碼運行結果如下:
Full Name is set? false Full Name: [none] Hey Stranger!?讓我們大概看看另外一個例子。
Optional< String > firstName = Optional.of( "Tom" ); System.out.println( "First Name is set? " + firstName.isPresent() ); System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); System.out.println();?輸出如下:
First Name is set? true First Name: Tom Hey Tom!??2.2.?Stream
新增加的Stream API?(java.util.stream)引入了在Java里可以工作的函數式編程。這是目前為止對java庫最大的一次功能添加,希望程序員通過編寫有效、整潔和簡明的代碼,能夠大大提高生產率。
list轉map
?
常用方式
?
public Map<Long, String> getIdNameMap(List<Account> accounts) {return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername)); }?
?
收集成實體本身map
?
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account)); }?
?
account -> account是一個返回本身的lambda表達式,其實還可以使用Function接口中的一個默認方法代替,使整個方法更簡潔優雅:
?
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity())); }?
?
重復key的情況
?
代碼如下:
?
public Map<String, Account> getNameAccountMap(List<Account> accounts) {return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity())); }?
?
這個方法可能報錯(java.lang.IllegalStateException: Duplicate key),因為name是有可能重復的。toMap有個重載方法,可以傳入一個合并的函數來解決key沖突問題:
?
public Map<String, Account> getNameAccountMap(List<Account> accounts) {return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2)); }?
?
這里只是簡單的使用后者覆蓋前者來解決key重復問題。
?
指定具體收集的map
?
toMap還有另一個重載方法,可以指定一個Map的具體實現,來收集數據:
?
public Map<String, Account> getNameAccountMap(List<Account> accounts) {return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new)); }?
?
?
Stream API讓集合處理簡化了很多(我們后面會看到不僅限于Java集合類)。讓我們從一個簡單的類Task開始來看看Stream的用法。
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;}@Overridepublic String toString() {return String.format( "[%s, %d]", status, points );} } }?Task類有一個分數的概念(或者說是偽復雜度),其次是還有一個值可以為OPEN或CLOSED的狀態.讓我們引入一個Task的小集合作為演示例子:
final Collection< Task > tasks = Arrays.asList(new Task( Status.OPEN, 5 ),new Task( Status.OPEN, 13 ),new Task( Status.CLOSED, 8 ) );?第一個問題是所有的開放的Task的點數是多少?在java 8?之前,通常的做法是用foreach迭代。但是Java8里頭我們會用Stream。Stream是多個元素的序列,支持串行和并行操作。
// Calculate total points of all active tasks using sum() final long totalPointsOfOpenTasks = tasks.stream().filter( task -> task.getStatus() == Status.OPEN ).mapToInt( Task::getPoints ).sum(); System.out.println( "Total points: " + totalPointsOfOpenTasks );?控制臺的輸出將會是:
Total points: 18?上面代碼執行的流程是這樣的,首先Task集合會被轉化為Stream表示,然后filter操作會過濾掉所有關閉的Task,接下來使用Task::getPoints?方法取得每個Task實例的點數,mapToInt方法會把Task Stream轉換成Integer Stream,最后使用Sum方法將所有的點數加起來得到最終的結果。
在我們看下一個例子之前,我們要記住一些關于Stream的說明。Stream操作被分為中間操作和終點操作。
中間操作返回一個新的Stream。這些中間操作是延遲的,執行一個中間操作比如filter實際上不會真的做過濾操作,而是創建一個新的Stream,當這個新的Stream被遍歷的時候,它里頭會包含有原來Stream里符合過濾條件的元素。
終點操作比如說forEach或者sum會遍歷Stream從而產生最終結果或附帶結果。終點操作執行完之后,Stream管道就被消費完了,不再可用。在幾乎所有的情況下,終點操作都是即時完成對數據的遍歷操作。
Stream的另外一個價值是Stream創造性地支持并行處理。讓我們看看下面這個例子,這個例子把所有task的點數加起來。
// Calculate total points of all tasks 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 );?這個例子跟上面那個非常像,除了這個例子里使用了parallel()方法???????并且計算最終結果的時候使用了reduce方法。
輸出如下:
Total points (all tasks): 26.0?經常會有這個一個需求:我們需要按照某種準則來對集合中的元素進行分組。Stream也可以處理這樣的需求,下面是一個例子:
// Group tasks by their status final Map< Status, List< Task > > map = tasks.stream().collect( Collectors.groupingBy( Task::getStatus ) ); System.out.println( map );?控制臺的輸出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}按照某種準則來對集合中的元素進行分組并統計每組個數:
// Group tasks by their status final Map< Status, Long > map = tasks.stream().collect( Collectors.groupingBy( Task::getStatus, Collectors.counting()) );System.out.println( map );
?按照某種準則來對集合中的元素進行分組并統計每組里某個字段的平均值:?
// Group tasks by their status final Map< Status, Long > map = tasks.stream().collect( Collectors.groupingBy( Task::getStatus, Collectors.averagingInt(Task::getPoints)) );System.out.println( map );
?讓我們來計算整個集合中每個task分數(或權重)的平均值來結束task的例子。
// Calculate the weight of each tasks (as percent of total points) 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 );?控制臺輸出如下:
[19%, 50%, 30%]?最后,就像前面提到的,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()方法被調用的時候這個句柄會被執行。
流可以是無限的、有狀態的,可以是順序的,也可以是并行的。在使用流的時候,你首先需要從一些來源中獲取一個流,執行一個或者多個中間操作,然后執行一個最終操作。中間操作包括filter、map、flatMap、peel、distinct、sorted、limit和substream。終止操作包括forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和findAny。?java.util.stream.Collectors是一個非常有用的實用類。該類實現了很多歸約操作,例如將流轉換成集合和聚合元素。
?Stream有串行和并行兩種,串行Stream上的操作是在一個線程中依次完成,而并行Stream則是在多個線程上同時執行。
?下面的例子展示了是如何通過并行Stream來提升性能:
?首先我們創建一個沒有重復元素的大表:
int max = 1000000; List<String> values = new ArrayList<>(max); for (int i = 0; i < max; i++) {UUID uuid = UUID.randomUUID();values.add(uuid.toString()); }?然后我們計算一下排序這個Stream要耗時多久,
串行排序:
long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // 串行耗時: 899 ms?并行排序:
long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // 并行排序耗時: 472 ms?上面兩個代碼幾乎是一樣的,但是并行版的快了50%之多,唯一需要做的改動就是將stream()改為parallelStream()。
??
2.3.?日期時間API(JSR310)
新的java.time包包含了所有關于日期、時間、日期時間、時區、Instant(跟日期類似但精確到納秒)、duration(持續時間)和時鐘操作的類。設計這些API的時候很認真地考慮了這些類的不變性(從java.util.Calendar吸取的痛苦教訓)。如果需要修改時間對象,會返回一個新的實例。
- Clock
Clock使用時區來訪問當前的instant, date和time。Clock類可以替換?System.currentTimeMillis()?和?TimeZone.getDefault().
- LocalDate
LocalDate只保存有ISO-8601日期系統的日期部分,有時區信息
- LocalTime
LocalTime只保存ISO-8601日期系統的時間部分,沒有時區信息。
- LocalDateTime
LocalDateTime類合并了LocalDate和LocalTime,它保存有ISO-8601日期系統的日期和時間,但是沒有時區信息。
- ZonedDateTime
如果您需要一個類持有日期時間和時區信息,可以使用ZonedDateTime,它保存有ISO-8601日期系統的日期和時間,而且有時區信息。
- Duration
Duration持有的時間精確到納秒。它讓我們很容易計算兩個日期中間的差異。
2.4.?Base64
對Base64的支持最終成了Java 8標準庫的一部分,非常簡單易用:
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()).
?
2.5.?并行數組
Java 8新增加了很多方法支持并行的數組處理。最重要的大概是parallelSort()這個方法顯著地使排序在多核計算機上速度加快。下面的小例子演示了這個新的方法(parallelXXX)的行為。
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()?方法填充這個長度是2000的數組,然后使用parallelSort()?排序。這個程序輸出了排序前和排序后的10個數字來驗證數組真的已經被排序了。示例可能的輸出如下(請注意這些數字是隨機產生的)
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
?
2.6.?并發
在新增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包中還增加了下面這些類:
?
回到頂部3.?新的工具
3.1.?類依賴分析工具:jdeps
Jdeps是一個功能強大的命令行工具,它可以幫我們顯示出包層級或者類層級java類文件的依賴關系。它接受class文件、目錄、jar文件作為輸入,默認情況下,jdeps會輸出到控制臺。
作為例子,讓我們看看現在很流行的Spring框架的庫的依賴關系報告。為了讓報告短一些,我們只分析一個jar:?org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar?這個命令輸出內容很多,我們只看其中的一部分,這些依賴關系根絕包來分組,如果依賴關系在classpath里找不到,就會顯示not found.
??
回到頂部4.?JVM的新特性
JVM內存永久區已經被metaspace替換(JEP 122)。JVM參數?-XX:PermSize?和?–XX:MaxPermSize被XX:MetaSpaceSize?和?-XX:MaxMetaspaceSize代替。
??
回到頂部5. 參考資料
http://ifeve.com/java-8-features-tutorial/
https://blog.chou.it/2014/03/java-8-new-features/
http://www.infoq.com/cn/news/2013/08/everything-about-java-8#
http://www.importnew.com/17313.html
分類:?java基礎轉載于:https://www.cnblogs.com/DreamRecorder/p/9203399.html
總結
- 上一篇: Redis可视化工具 Redis Des
- 下一篇: 使用postman模拟登录请求