愈來愈多的項目已經使用 Java 8 了,毫無疑問,Java 8 是Java自Java 5(發佈於2004年)以後的最重要的版本。這個版本包含語言、編譯器、庫、工具和 JVM 等方面的十多個新特性。在本文中咱們將學習這些新特性,並用實際的例子說明在什麼場景下適合使用。html
引用:本文參考了這兩篇文章,加以本身的理解,整理成一份最容易理解的 Java8 新特性文章,有少部分章節可能內容一致,但絕對不是抄襲,只是爲了文章的完整性,大部分經常使用的地方加了我本身的理解和示例。java
http://www.javashuo.com/article/p-rtcgnvtk-a.htmlgit
https://blog.csdn.net/maosijunzi/article/details/38658095spring
Java8 的 lambda 的使用確實方便了許多,但也使初次瞭解的人感受到難以閱讀,實際上是你不習慣的緣由。不少語言從一開始就支持了 Lambda 表達式,像 Groovy,Scala 等。數據庫
在 Java8 之前,咱們想要讓一個方法能夠與用戶進行交互,好比說使用方法內的局部變量;這時候就只能使用接口作爲參數,讓用戶實現這個接口或使用匿名內部類的形式,把局部變量經過接口方法傳給用戶。apache
傳統匿名內部類缺點:代碼臃腫,難以閱讀編程
Lambda 表達式將函數當成參數傳遞給某個方法,或者把代碼自己看成數據處理;api
語法格式:併發
->
符號Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
等價於oracle
List<String> list = Arrays.asList( "a", "b", "d" ); for(String e:list){ System.out.println(e); }
若是語句塊比較複雜,使用 {}
包起來
Arrays.asList( "a", "b", "d" ).forEach( e -> { String m = "9420 "+e; System.out.print( m ); });
Lambda 本質上是匿名內部類的改裝,因此它使用到的變量都會隱式的轉成 final
的
String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.print( e + separator ) );
等價於
final String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.print( e + separator ) );
Lambda 的返回值和參數類型由編譯器推理得出,不須要顯示定義,若是隻有一行代碼能夠不寫 return 語句
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
等價於
List<String> list = Arrays.asList("a", "b", "c"); Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } });
@FunctionalInterface
標記@FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } static void staticMethod(){ } }
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 { @Override public String notRequired() { return "Overridden implementation"; } } // 也能夠由接口覆蓋 public interface OverridableInterface extends Defaulable{ @Override public String notRequired() { return "interface Overridden implementation"; } }
因爲JVM上的默認方法的實如今字節碼層面提供了支持,所以效率很是高。默認方法容許在不打破現有繼承體系的基礎上改進接口。該特性在官方庫中的應用是:給 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。
咱們基本不須要定義本身的函數式接口,Java8 已經給咱們提供了大量的默認函數式接口,基本夠用,在 rt.jar
包的 java.util.function
目錄下能夠看到全部默認的函數式接口,大體分爲幾類
Function<T,R>
T 做爲輸入,返回的 R 做爲輸出Predicate<T>
T 做爲輸入 ,返回 boolean 值的輸出Consumer<T>
T 做爲輸入 ,沒有輸出Supplier<R>
沒有輸入 , R 做爲輸出BinaryOperator<T>
兩個 T 做爲輸入 ,T 一樣是輸出UnaryOperator<T>
是 Function
的變種 ,輸入輸出者是 T 其它的都是上面幾種的各類擴展,只爲更方便的使用,下面演示示例,你能夠把其當成正常的接口使用,由用戶使用 Lambda 傳入。
// hello world 示例 Function<String,String> function = (x) -> {return x+"Function";}; System.out.println(function.apply("hello world")); // hello world Function UnaryOperator<String> unaryOperator = x -> x + 2; System.out.println(unaryOperator.apply("9420-")); // 9420-2 // 判斷輸入值是否爲偶數示例 Predicate<Integer> predicate = (x) ->{return x % 2 == 0 ;}; System.out.println(predicate.test(1)); // false // 這個沒有返回值 Consumer<String> consumer = (x) -> {System.out.println(x);}; consumer.accept("hello world "); // hello world // 這個沒有輸入 Supplier<String> supplier = () -> {return "Supplier";}; System.out.println(supplier.get()); // Supplier // 找出大數 BinaryOperator<Integer> bina = (x, y) ->{return x > y ? x : y;}; bina.apply(1,2); // 2
方法引用使得開發者能夠直接引用現存的方法、Java類的構造方法或者實例對象。方法引用和Lambda表達式配合使用,使得java類的構造方法看起來緊湊而簡潔,沒有不少複雜的模板代碼。
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 );
等價於
Car car = Car.create(() -> new Car());
第二種方法引用的類型是靜態方法引用,語法是Class::static_method。注意:這個方法接受一個Car類型的參數。
cars.forEach( Car::collide );
forEach
原型爲 forEach(Consumer<? super T> action)
使用的是 Consumer 只有參數,沒有返回值;這個參數 T 就是 car 類型,由於是 cars.forEach
嘛,因此上面的方法引用等價於
cars.forEach(car -> Car.collide(car));
第三種方法引用的類型是某個類的成員方法的引用,語法是Class::method,注意,這個方法沒有定義入參:
cars.forEach( Car::repair );
它等價於
cars.forEach(car -> car.repair());
自從Java 5中引入註解以來,這個特性開始變得很是流行,並在各個框架和項目中被普遍使用。不過,註解有一個很大的限制是:在同一個地方不能屢次使用同一個註解。Java 8打破了這個限制,引入了重複註解的概念,容許在同一個地方屢次使用同一個註解。
在Java 8中使用 @Repeatable 註解定義重複註解,實際上,這並非語言層面的改進,而是編譯器作的一個trick,底層的技術仍然相同。能夠利用下面的代碼說明:
@Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) @Repeatable( Filters.class ) public @interface Filter { String value(); }; @Filter( "filter1" ) @Filter( "filter2" ) public interface Filterable { } public static void main(String[] args) { for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { System.out.println( filter.value() ); } }
正如咱們所見,這裏的Filter類使用 @Repeatable(Filters.class)
註解修飾,而Filters是存放Filter註解的容器,編譯器儘可能對開發者屏蔽這些細節。這樣,Filterable接口能夠用兩個Filter註解註釋(這裏並無提到任何關於Filters的信息)。
另外,反射API提供了一個新的方法:getAnnotationsByType(),能夠返回某個類型的重複註解,例如Filterable.class.getAnnoation(Filters.class)
將返回兩個Filter實例。
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() ); } }
參數 Value.defaultValue() 的類型由編譯器推導得出,不須要顯式指明。在Java 7中這段代碼會有編譯錯誤,除非使用Value.<String>defaultValue()
Java 8拓寬了註解的應用場景。如今,註解幾乎可使用在任何元素上:局部變量、接口類型、超類和接口實現類,甚至能夠用在函數的異常定義上。下面是一些例子:
package com.javacodegeeks.java8.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; 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_USER 和 ElementType.TYPE_PARAMETER 是Java 8新增的兩個註解,用於描述註解的使用場景。Java 語言也作了對應的改變,以識別這些新增的註解。
Java 8 開始正式支持參數名稱,終於不須要讀 class 字節碼來獲取參數名稱了,這對於常用反射的人特別有用。
在 Java8 這個特性默認是關閉的,須要開啓參數才能獲取參數名稱:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerArgument>-parameters</compilerArgument> <source>1.8</source> <target>1.8</target> </configuration> </plugin>
使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM參數方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原來的-XX:PermSize和-XX:MaxPermSize。
Java 8增長了不少新的工具類(date/time類),並擴展了現存的工具類,以支持現代的併發編程、函數式編程等,本章節參考原文,並提取出經常使用功能。
Streams 操做分爲中間操做和晚期操做,中間操做會返回一個新的 Stream ,只是把要作的操做記錄起來而已,並不會真的執行,晚期操做纔會真的遍歷列表並執行全部操做
Stream 的另外一個價值就是支持了並行處理 parallel
方法。
Stream API 簡化了集合的操做,並擴展了集合的分組,求和,mapReduce,flatMap ,排序等功能,下面列出項目中常常用到的功能,會以使用頻率排序。
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Vehicle { //車架號 private String vin; // 車主手機號 private String phone; // 車主姓名 private String name; // 所屬車租車公司 private Integer companyId; // 我的評分 private Double score; //安裝的設備列表imei,使用逗號分隔 private String deviceNos; }
static List<Vehicle> vehicles = new ArrayList<>(); @Before public void init(){ List<String> imeis = new ArrayList<>(); for (int i = 0; i <5 ; i++) { List<String> singleVehicleDevices = new ArrayList<>(); for (int j = 0; j < 3; j++) { String imei = RandomStringUtils.randomAlphanumeric(15); singleVehicleDevices.add(imei); } imeis.add(StringUtils.join(singleVehicleDevices,',')); } vehicles.add(new Vehicle("KPTSOA1K67P081452","17620411498","9420",1,4.5,imeis.get(0))); vehicles.add(new Vehicle("KPTCOB1K18P057071","15073030945","張玲",2,1.4,imeis.get(1))); vehicles.add(new Vehicle("KPTS0A1K87P080237","19645871598","sanri1993",1,3.0,imeis.get(2))); vehicles.add(new Vehicle("KNAJC526975740490","15879146974","李種",1,3.9,imeis.get(3))); vehicles.add(new Vehicle("KNAJC521395884849","13520184976","袁紹",2,4.9,imeis.get(4))); }
vehicles.forEach(vehicle -> System.out.println(vehicle)); //這樣就能夠遍歷打印 vehicles.forEach(System.out::println);
Map<String,Integer> map = new HashMap<>(); map.put("a",1);map.put("b",2);map.put("c",3); map.forEach((k,v) -> System.out.println("key:"+k+",value:"+v));
// 去掉評分爲 3 分如下的車 List<Vehicle> collect = vehicles.stream().filter(vehicle -> vehicle.getScore() >= 3).collect(Collectors.toList());
對一個 List<Object>
大部分狀況下,咱們只須要列表中的某一列,或者須要把裏面的每個對象轉換成其它的對象,這時候可使用 map 映射,示例:
// 取出全部的車架號列表 List<String> vins = vehicles.stream().map(Vehicle::getVin).collect(Collectors.toList());
// 按照公司 Id 進行分組 Map<Integer, List<Vehicle>> companyVehicles = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getCompanyId)); // 按照公司分組求司機打分和 Map<Integer, Double> collect = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getCompanyId, Collectors.summingDouble(Vehicle::getScore)));
// 單列排序 vehicles.sort((v1,v2) -> v2.getScore().compareTo(v1.getScore())); // 或使用 Comparator 類來構建比較器,流處理不會改變原列表,須要接收返回值才能獲得預期結果 List<Vehicle> collect = vehicles.stream().sorted(Comparator.comparing(Vehicle::getScore).reversed()).collect(Collectors.toList()); // 多列排序,score 降序,companyId 升序排列 List<Vehicle> collect = vehicles.stream().sorted(Comparator.comparing(Vehicle::getScore).reversed() .thenComparing(Comparator.comparing(Vehicle::getCompanyId))) .collect(Collectors.toList());
// 查出全部車綁定的全部設備 List<String> collect = vehicles.stream().map(vehicle -> { String deviceNos = vehicle.getDeviceNos(); return StringUtils.split(deviceNos,','); }).flatMap(Arrays::stream).collect(Collectors.toList());
flatMap 很適合 List<List>
或 List<object []>
這種結構,能夠當成一個列表來處理;像上面的設備列表,在數據庫中存儲的結構就是以逗號分隔的數據,而車輛列表又是一個列表數據。
// 對全部司機的總分求和 Double reduce = vehicles.stream().parallel().map(Vehicle::getScore).reduce(0d, Double::sum);
// 總的分值 Double totalScore = vehicles.stream().parallel().map(Vehicle::getScore).reduce(0d, Double::sum); // 查看每個司機佔的分值比重 List<String> collect = vehicles.stream() .mapToDouble(vehicle -> vehicle.getScore() / totalScore) .mapToLong(weight -> (long) (weight * 100)) .mapToObj(percentage -> percentage + "%") .collect(Collectors.toList());
原文的 boxed 不知道是什麼意思,但願有大神能幫忙解答下,不用 boxed 也是能夠的
Optional 用來解決 Java 中常常出現的 NullPointerException ,從而避免源碼被各類空檢查污染,使源碼更加簡潔和更加容易閱讀
// 假設有一個對象 obj ,你不知道它是否是爲空的,可是你想用它的方法,能夠這麼玩 Optional<T> canUseObj = Optional.ofNullable(obj); canUseObj.ifPresent(System.out::println); //若是 obj 不爲空,則可使用 obj 的方法,這裏作個簡單輸出
新的日期時間工具所有都在 java.time
及其子包中。
Java 8日期/時間API是 JSR-310 規範的實現,它的目標是克服舊的日期/時間API實現中全部的缺陷,新的日期/時間API的一些設計原則以下:
時間大體能夠分爲三個部分:日期、時間、時區
其中日期又細分爲年、月、日;時間又細分爲時、分、秒
通常機器時間用從 1970-01-01T00:00 到如今的秒數來表示時間; 這裏糾正大部分人犯的一個錯誤概念,時間戳指的是秒數,而不是毫秒數。
幾乎全部的時間對象都實現了 Temporal
接口,因此接口參數通常都是 Temporal
Instant
表示時間線上的一個點(瞬時)// 測試執行一個 new 操做使用的時間(納秒值) Instant begin = Instant.now(); StreamMain streamMain = new StreamMain(); Instant end = Instant.now(); System.out.println(Duration.between(begin,end).toNanos());
LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
能夠規爲一組,用於表示時間的// 可使用 of 方法構建它們的實例,以下面建立了一個 2019-9-22 21:42:59 東八區 的時間對象 LocalDate localDate = LocalDate.of(2019, Month.SEPTEMBER, 22); LocalTime localTime = LocalTime.of(21, 42, 59); LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.systemDefault()); // 獲取如今的時間,這是一個靜態方法 LocalDate now = LocalDate.now(); // 每一個實例能夠獲取它們的 part 信息,如獲取年 int year = localDate.getYear(); // 能夠修改 part 信息,這將返回一個新對象,如增長一年 LocalDate localDatePlus = localDate.plusYears(1); // 設置 part 信息,也會返回新的對象,如設置爲 2017 年 LocalDate localDateWithYear = localDate.withYear(2017); // 比較兩個日期 isAfter,isBefore boolean after = localDate.isAfter(LocalDate.now()); // 格式化日期時間 // yyyy-MM-dd System.out.println(now.format(DateTimeFormatter.ISO_DATE)); // yyyy-MM-ddTHH:mm:ss System.out.println(now.format(DateTimeFormatter.ISO_DATE_TIME)); // yyyy-MM-dd HH:mm:ss System.out.println(now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM))); // 日期解析 System.out.println(LocalDate.parse("2019-09-22")); System.out.println(LocalDateTime.parse("2019-09-22T21:05:22")); System.out.println(LocalDateTime.parse("2019-09-22 21:05:22",DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
ZoneId
用來操做時區,它提供了獲取全部時區和本地時區的方法ZoneId zoneId = ZoneId.systemDefault(); Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
Period
,Duration
能夠視爲一組,用於計算時間間隔// 建立一個兩週的間隔 Period periodWeeks = Period.ofWeeks(2); // 一年三個月零二天的間隔 Period custom = Period.of(1, 3, 2); // 一天的時長 Duration duration = Duration.ofDays(1); // 計算2015/6/16 號到如今 2019/09/22 過了多久,它這個把間隔分到每一個 part 了 LocalDate now = LocalDate.now(); LocalDate customDate = LocalDate.of(2015, 6, 16); Period between = Period.between(customDate, now); // 結果爲 4:3:6 即過去了 4年3個月6天了 System.out.println(between.getYears()+":"+between.getMonths()+":"+between.getDays()); // 比較兩個瞬時的時間間隔 Instant begin = Instant.now(); Instant end = Instant.now(); Duration.between(begin,end); // 一樣能夠修改 part 信息和設置 part 信息,都是返回新的對象來表示設置過的值,原來的對象不變 Period plusDays = between.plusDays(1); Period withDays = between.withDays(4);
雖說,這個新的時間工具很好用,但若是不能與之前的舊的 api 兼容的話,同樣是沒有用的;還好新的工具類能很好的與之前的工具類進行相互轉換。
經過 Instant
作中間轉換實現Date
,Calendar
與 LocalDateTime
,ZonedDateTime
,LocalDate
的互相轉換
// LocalDateTime 轉 Date Date localDateTimeDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); // LocalDateTime 轉 Calendar Calendar localDateTimeCalendar = GregorianCalendar.from(ZonedDateTime.of(localDateTime, ZoneId.systemDefault())); // Date 轉 LocalDateTime LocalDateTime dateLocalDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); // Calendar 轉 LocalDateTime LocalDateTime calendarLocalDateTime = LocalDateTime.ofInstant(calendar.toInstant(), ZoneOffset.systemDefault());
對於 Base64 終於不用引用第三方包了,使用 java 庫就能夠完成
// 編碼 final String encoded = Base64.getEncoder().encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); // 解碼 final String decoded = new String( Base64.getDecoder().decode( encoded ),StandardCharsets.UTF_8 );
基於新增的lambda表達式和steam特性,爲Java 8中爲java.util.concurrent.ConcurrentHashMap類添加了新的方法來支持聚焦操做;另外,也爲java.util.concurrentForkJoinPool類添加了新的方法來支持通用線程池操做(更多內容能夠參考咱們的併發編程課程)。
Java 8還添加了新的java.util.concurrent.locks.StampedLock類,用於支持基於容量的鎖——該鎖有三個模型用於支持讀寫操做(能夠把這個鎖當作是java.util.concurrent.locks.ReadWriteLock的替代者)。
在java.util.concurrent.atomic包中也新增了很多工具類,列舉以下:
Java 8提供了一些新的命令行工具,這部分會講解一些對開發者最有用的工具。
deps是一個至關棒的命令行工具,它能夠展現包層級和類層級的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
https://gitee.com/sanri/example/tree/master/testjava8
創做不易,但願能夠支持下個人開源軟件,及個人小工具,歡迎來 gitee 點星,fork ,提 bug 。
Excel 通用導入導出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代碼 ,從數據庫生成代碼 ,及一些項目中常常能夠用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven