先考慮一個問題,如何向Java中的集合庫中增長方法?例如在Java 8中向Collection接口中添加了一個forEach方法。html
若是在Java 8以前,對於接口來講,其中的方法必須都爲抽象方法,也就是說接口中不容許有接口的實現,那麼就須要對每一個實現Collection接口的類都須要實現一個forEach方法。java
但這就會形成在給接口添加新方法的同時影響了已有的實現,因此Java設計人員引入了接口默認方法,其目的是爲了解決接口的修改與已有的實現不兼容的問題,接口默認方法能夠做爲庫、框架向前兼容的一種手段。算法
默認方法就像一個普通Java方法,只是方法用default關鍵字修飾。
sql
下面來舉一個簡單的例子express
public interface Person { //默認方法 default String getName(String name) { return name; } } /////////////////////////////////////////////////////////////////////// public class Student implements Person { } ////////////////////////////////////////////////////////////////////// public class Test { public static void main(String[] args) { Person p = new Student(); String name = p.getName("小李"); System.out.println(name); } }
咱們定義了一個Person接口,其中getName是一個默認方法。接着編寫一個實現類,能夠從結果中看到,雖然Student是空的,可是仍然能夠實現getName方法。編程
顯然默認接口的出現打破了以前的一些基本規則,使用時要注意幾個問題。api
考慮若是接口中定義了一個默認方法,而另一個父類或者接口中又定義了一個同名的方法,該選擇哪一個?數組
1. 選擇父類中的接口。若是一個父類提供了具體的實現方法,那麼接口中具備相同名稱和參數的默認方法會被忽略。多線程
2. 接口衝突。若是一個父接口提供了一個默認方法,而另外一個接口也提供了具備相同名稱和參數類型的方法(無論該方法是不是默認方法),那麼必須經過覆蓋方法來解決。併發
記住一個原則,就是「類優先」,即當類和接口都有一個同名方法時,只有父類中的方法會起做用。
「類優先」原則能夠保證與Java 7的兼容性。若是你再接口中添加了一個默認方法,它對Java 8之前編寫的代碼不會產生任何影響。
下面來講說靜態方法。
靜態方法就像一個普通Java靜態方法,但方法的權限修飾只能是public或者不寫。
默認方法和靜態方法使Java的功能更加豐富。
在Java 8中Collection接口中就添加了四個默認方法,stream()、parallelStream()、forEach()和removeIf()。Comparator接口也增長了許多默認方法和靜態方法。
函數式接口(Functional Interface)是隻包含一個方法的抽象接口。
好比Java標準庫中的java.lang.Runnable,java.util.concurrent.Callable就是典型的函數式接口。
在Java 8中經過@FunctionalInterface註解,將一個接口標註爲函數式接口,該接口只能包含一個抽象方法。
@FunctionalInterface註解不是必須的,只要接口只包含一個抽象方法,虛擬機會自動判斷該接口爲函數式接口。
通常建議在接口上使用@FunctionalInterface註解進行聲明,以避免他人錯誤地往接口中添加新方法,若是在你的接口中定義了第二個抽象方法的話,編譯器會報錯。
函數式接口是爲Java 8中的lambda而設計的,lambda表達式的方法體其實就是函數接口的實現。
爲何要使用lambda表達式?
「lambda表達式」是一段能夠傳遞的代碼,由於他能夠被執行一次或屢次。咱們先回顧一下以前在Java中一直使用的類似的代碼塊。
當咱們在一個線程中執行一些邏輯時,一般會將代碼放在一個實現Runnable接口的類的run方法中,以下所示:
new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i < 10; i++) System.out.println("Without Lambda Expression"); }}).start();
而後經過建立實例來啓動一個新的線程。run方法內包含了一個新線程中須要執行的代碼。
再來看另外一個例子,若是想利用字符串長度排序而不是默認的字典順序排序,就須要自定義一個實現Comparator接口的類,而後將對象傳遞給sort方法。
class LengthComparator implements Comparator<String> { @Override public int compare(String s1, String s2) { return Integer.compare(s1.length(), s2.length()); } } Arrays.sort(strings, new LengthComparator());
按鈕回調是另外一個例子。將回調操做放在了一個實現了監聽器接口的類的一個方法中。
JButton button = new JButton("click"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Without Lambda Expression"); } });
這三個例子中,出現了相同方式,一段代碼被傳遞給其餘調用者——一個新線程、是一個排序方法或者是一個按鈕。這段代碼會在稍後被調用。
在Java中傳遞代碼並非很容易,不可能將代碼塊處處傳遞。你不得不構建一個類的對象,由他的某個方法來包含所需的代碼。
而lambda表達式實際上就是代碼塊的傳遞的實現。其語法結構以下:
(parameters) -> expression 或者 (parameters) -> {statements;}
括號裏的參數能夠省略其類型,編譯器會根據上下文來推導參數的類型,你也能夠顯式地指定參數類型,若是沒有參數,括號內能夠爲空。
方法體,若是有多行功能語句用大括號括起來,若是隻有一行功能語句則能夠省略大括號。
new Thread(() -> { for (int i = 0; i < 100; i++) System.out.println("Lambda Expression"); }).start();
Comparator<String> c = (s1, s2) -> Integer.compare(s1.length(), s2.length());
button.addActionListener(e -> System.out.println("Lambda Expression"));
能夠看到lambda表達式使代碼變得簡單,代替了匿名內部類。
下面來講一下方法引用,方法引用是lambda表達式的一種簡寫形式。 若是lambda表達式只是調用一個特定的已經存在的方法,則可使用方法引用。
使用「::」操做符將方法名和對象或類的名字分隔開來。如下是四種使用狀況:
Arrays.sort(strings, String::compareToIgnoreCase); // 等價於 Arrays.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));
上面的代碼就是第三種狀況,對lambda表達式又一次進行了簡化。
當處理集合時,一般會迭代全部元素並對其中的每個進行處理。例如,咱們但願統計一個字符串類型數組中,全部長度大於3的元素。
String[] strArr = { "Java8", "new", "feature", "Stream", "API" }; int count = 0; for (String s : strArr) { if (s.length() > 3) count++; }
一般咱們都會使用這段代碼來統計,並無什麼錯誤,只是它很難被並行計算。這也是Java8引入大量操做符的緣由,在Java8中,實現相同功能的操做符以下所示:
long count = Stream.of(strArr).filter(w -> w.length() > 3).count();
stream方法會爲字符串列表生成一個Stream。filter方法會返回只包含字符串長度大於3的一個Stream,而後經過count方法計數。
一個Stream表面上與一個集合很相似,容許你改變和獲取數據,但實際上卻有很大區別:
Stream相對於循環操做有更好的可讀性。而且能夠並行計算:
long count = Arrays.asList(strArr).parallelStream().filter(w -> w.length() > 3).count();
只須要把stream方法改爲parallelStream,就可讓Stream去並行執行過濾和統計操做。
Stream遵循「作什麼,而不是怎麼去作」的原則。只須要描述須要作什麼,而不用考慮程序是怎樣實現的。
Stream很像Iterator,單向,只能遍歷一遍。可是Stream能夠只經過一行代碼就實現多線程的並行計算。
當使用Stream時,會有三個階段:
從着三個階段來看,對應着三種類型的方法,首先是Stream的建立方法。
// 1. Individual values Stream stream = Stream.of("a", "b", "c"); // 2. Arrays String [] strArray = new String[] {"a", "b", "c"}; stream = Stream.of(strArray); stream = Arrays.stream(strArray); // 3. Collections List<String> list = Arrays.asList(strArray); stream = list.stream();
中間操做包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
終止操做包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
關於Stream的每一個方法如何使用就不展開了,更詳盡的介紹看這篇文章:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
Java8 引入了一個新的日期和時間API,位於java.time包下。
新的日期和時間API借鑑了Joda Time庫,其做者也爲同一人,但它們並非徹底同樣的,作了不少改進。
下面來講一下幾個經常使用的類。首先是Instant,一個Instant對象表示時間軸上的一個點。
Instant.now()會返回當前的瞬時點(格林威治時間)。Instant.MIN和Instant.MAX分別爲十億年前和十億年後。
以下代碼能夠計算某算法的運行時間:
Instant start = Instant.now(); runAlgorithm(); Instant end = Instant.now(); Duration timeElapsed = Duration.between(start, end); long millis = timeElapsed.toMillis();
Duration對象表示兩個瞬時點間的時間量。能夠經過不一樣的方法,換算成各類時間單位。
上面所說的絕對時間並不能應用到生活中去,因此新的Java API中提供了兩種人類時間,本地日期/時間和帶時區的時間。
LocalDate是一個帶有年份、月份和天數的日期。建立他可使用靜態方法now或者of。
LocalDate today = LocalDate.now(); LocalDate myBirthday = LocalDate.of(1994, 03, 15); // use Enum myBirthday = LocalDate.of(1994, Month.MARCH, 15); System.out.println(today); // 2017-10-23 System.out.println(myBirthday); // 1994-03-15
下面是LocalDate中的一些經常使用方法:
LocalTime表示一天中的某個時間,一樣可使用now或者of來建立實例。
LocalTime rightNow = LocalTime.now(); LocalTime bedTime = LocalTime.of(2, 0); System.out.println(rightNow); // 01:26:17.139 System.out.println(bedTime); // 02:00
LocalDateTime表示一個日期和時間,用法和上面相似。
上面幾種日期時間類都屬於本地時間,下面來講一下帶時區的時間。
ZonedDateTime經過設置時區的id來建立一個帶時區的時間。
ZonedDateTime beijingOlympicOpenning = ZonedDateTime.of(2008, 8, 8, 20, 0, 0, 0, ZoneId.of("Asia/Shanghai")); System.out.println(beijingOlympicOpenning); // 2008-08-08T20:00+08:00[Asia/Shanghai]
更新後的API一樣加入了新的格式化類DateTimeFormatter。DateTimeFormatter提供了三種格式化方法來打印日期/時間:
String formattered = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(beijingOlympicOpenning); System.out.println(formattered); // 2008-08-08T20:00:00
DateTimeFormatter類提供了多種預約義的標準格式可供使用。
標準格式主要用於機器可讀的時間戳。爲了讓人可以讀懂日期和時間,你須要使用語言環境相關的格式。
Java8提供了4種風格,SHORT、MEDIUM、LONG、FULL。
String formattered = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).format(beijingOlympicOpenning); System.out.println(formattered); //2008年8月8日 星期五 下午08時00分00秒 CST
或者你也能夠自定義日期和時間的格式。
String formattered = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm").format(beijingOlympicOpenning); System.out.println(formattered); // 星期五 2008-08-08 20:00
下圖中爲一些經常使用的模式元素。
新的API提供了從字符串解析出日期/時間的parse靜態方法和與遺留類(java.util.Date、java.sql.Time和java.txt.DateFormat等)互相轉換的方法。
Java8在String類中只添加了一個新方法,就是join,該方法實現了字符串的拼接,能夠把它看做split方法的逆操做。
String joined = String.join(".", "www", "cnblogs", "com"); System.out.println(joined); // www.cnblogs.com
數字包裝類提供了BYTES靜態方法,以byte爲單位返回長度。
全部八種包裝類都提供了靜態的hashCode方法。
Short、Integer、Long、Float和Double這5種類型分別提供了了sum、max和min,用來在流操做中做爲聚合函數使用。
集合類和接口中添加的方法:
Java8爲使用流讀取文件行及訪問目錄項提供了一些簡便的方法(Files.lines和Files.list)。同時也提供了進行Base64編碼/解碼的方法。
Java8對GUI編程(JavaFX)、併發等方面也作了改進,本文沒有一一列出。
轉載請註明原文連接:http://www.cnblogs.com/justcooooode/p/7701260.html
《寫給大忙人看的Java SE 8》
http://study.163.com/course/introduction/1003856028.htm
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/