一、新特性
1、default關鍵字
在java裏面,我們通常都是認爲接口裏面是只能有抽象方法,不能有任何方法的實現的,那麼在jdk1.8裏面打破了這個規定,引入了新的關鍵字default,通過使用default修飾方法,可以讓我們在接口裏面定義具體的方法實現,如下。
public interface NewCharacter {
public void test1();
public default void test2(){
System.out.println("我是新特性1");
}
}
那這麼定義一個方法的作用是什麼呢?爲什麼不在接口的實現類裏面再去實現方法呢?
其實這麼定義一個方法的主要意義是定義一個默認方法,也就是說這個接口的實現類實現了這個接口之後,不用管這個default修飾的方法,也可以直接調用,如下。
public class NewCharacterImpl implements NewCharacter{
@Override
public void test1() {
}
public static void main(String[] args) {
NewCharacter nca = new NewCharacterImpl();
nca.test2();
}
}
所以說這個default方法是所有的實現類都不需要去實現的就可以直接調用,那麼比如說jdk的集合List裏面增加了一個sort方法,那麼如果定義爲一個抽象方法,其所有的實現類如arrayList,LinkedList等都需要對其添加實現,那麼現在用default定義一個默認的方法之後,其實現類可以直接使用這個方法了,這樣不管是開發還是維護項目,都會大大簡化代碼量。
2、Lambda 表達式
Lambda表達式是jdk1.8裏面的一個重要的更新,這意味着java也開始承認了函數式編程,並且嘗試引入其中。
首先,什麼是函數式編程,引用廖雪峯先生的教程裏面的解釋就是說:函數式編程就是一種抽象程度很高的編程範式,純粹的函數式編程語言編寫的函數沒有變量,因此,任意一個函數,只要輸入是確定的,輸出就是確定的,這種純函數我們稱之爲沒有副作用。而允許使用變量的程序設計語言,由於函數內部的變量狀態不確定,同樣的輸入,可能得到不同的輸出,因此,這種函數是有副作用的。函數式編程的一個特點就是,允許把函數本身作爲參數傳入另一個函數,還允許返回一個函數!
簡單的來說就是,函數也是一等公民了,在java裏面一等公民有變量,對象,那麼函數式編程語言裏面函數也可以跟變量,對象一樣使用了,也就是說函數既可以作爲參數,也可以作爲返回值了,看一下下面這個例子。
//這是常規的Collections的排序的寫法,需要對接口方法重寫
public void test1(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
for (String string : list) {
System.out.println(string);
}
}
//這是帶參數類型的Lambda的寫法
public void testLamda1(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, (Comparator<? super String>) (String a,String b)->{
return b.compareTo(a);
}
);
for (String string : list) {
System.out.println(string);
}
}
//這是不帶參數的lambda的寫法
public void testLamda2(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, (a,b)->b.compareTo(a)
);
for (String string : list) {
System.out.println(string);
}
可以看到不帶參數的寫法一句話就搞定了排序的問題,所以引入lambda表達式的一個最直觀的作用就是大大的簡化了代碼的開發,像其他一些編程語言Scala,Python等都是支持函數式的寫法的。當然,不是所有的接口都可以通過這種方法來調用,只有函數式接口才行,jdk1.8裏面定義了好多個函數式接口,我們也可以自己定義一個來調用,下面說一下什麼是函數式接口。
3、函數式接口
定義:“函數式接口”是指僅僅只包含一個抽象方法的接口,每一個該類型的lambda表達式都會被匹配到這個抽象方法。jdk1.8提供了一個@FunctionalInterface註解來定義函數式接口,如果我們定義的接口不符合函數式的規範便會報錯。
@FunctionalInterface
public interface MyLamda {
public void test1(String y);
//這裏如果繼續加一個抽象方法便會報錯
// public void test1();
//default方法可以任意定義
default String test2(){
return "123";
}
default String test3(){
return "123";
}
//static方法也可以定義
static void test4(){
System.out.println("234");
}
}
看一下這個接口的調用,符合lambda表達式的調用方法。
MyLamda m = y -> System.out.println("ss"+y);
4.方法與構造函數引用
jdk1.8提供了另外一種調用方式::,當 你 需 要使用 方 法 引用時 , 目 標引用 放 在 分隔符::前 ,方法 的 名 稱放在 後 面 ,即ClassName :: methodName
。例如 ,Apple::getWeight
就是引用了Apple類中定義的方法getWeight。請記住,不需要括號,因爲你沒有實際調用這個方法。方法引用就是Lambda表達式(Apple a) -> a.getWeight()
的快捷寫法,如下示例。
//先定義一個函數式接口
@FunctionalInterface
public interface TestConverT<T, F> {
F convert(T t);
}
測試如下,可以以::形式調用。
public void test(){
TestConverT<String, Integer> t = Integer::valueOf;
Integer i = t.convert("111");
System.out.println(i);
}
此外,對於構造方法也可以這麼調用。
//實體類User和它的構造方法
public class User {
private String name;
private String sex;
public User(String name, String sex) {
super();
this.name = name;
this.sex = sex;
}
}
//User工廠
public interface UserFactory {
User get(String name, String sex);
}
//測試類
UserFactory uf = User::new;
User u = uf.get("ww", "man");
這裏的User::new就是調用了User的構造方法,Java編譯器會自動根據UserFactory.get方法的簽名來選擇合適的構造函數。
5、局部變量限制
Lambda表達式也允許使用自由變量(不是參數,而是在外層作用域中定義的變量),就像匿名類一樣。 它們被稱作捕獲Lambda。 Lambda可以沒有限制地捕獲(也就是在其主體中引用)實例變量和靜態變量。但局部變量必須顯式聲明爲final,或事實上是final。
爲什麼局部變量有這些限制?
(1)實例變量和局部變量背後的實現有一個關鍵不同。實例變量都存儲在堆中,而局部變量則保存在棧上。如果Lambda可以直接訪問局部變量,而且Lambda是在一個線程中使用的,則使用Lambda的線程,可能會在分配該變量的線程將這個變量收回之後,去訪問該變量。因此, Java在訪問自由局部變量時,實際上是在訪問它的副本,而不是訪問原始變量。如果局部變量僅僅賦值一次那就沒有什麼區別了——因此就有了這個限制。
(2)這一限制不鼓勵你使用改變外部變量的典型命令式編程模式。
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2);
6、Date Api更新
1.8之前JDK自帶的日期處理類非常不方便,我們處理的時候經常是使用的第三方工具包,比如commons-lang包等。不過1.8出現之後這個改觀了很多,比如日期時間的創建、比較、調整、格式化、時間間隔等。這些類都在java.time包下。比原來實用了很多。
6.1 LocalDate/LocalTime/LocalDateTime
LocalDate爲日期處理類、LocalTime爲時間處理類、LocalDateTime爲日期時間處理類,方法都類似,具體可以看API文檔或源碼,選取幾個代表性的方法做下介紹。
now相關的方法可以獲取當前日期或時間,of方法可以創建對應的日期或時間,parse方法可以解析日期或時間,get方法可以獲取日期或時間信息,with方法可以設置日期或時間信息,plus或minus方法可以增減日期或時間信息;
6.2TemporalAdjusters
這個類在日期調整時非常有用,比如得到當月的第一天、最後一天,當年的第一天、最後一天,下一週或前一週的某天等。
6.3DateTimeFormatter
以前日期格式化一般用SimpleDateFormat類,但是不怎麼好用,現在1.8引入了DateTimeFormatter類,默認定義了很多常量格式(ISO打頭的),在使用的時候一般配合LocalDate/LocalTime/LocalDateTime使用,比如想把當前日期格式化成yyyy-MM-dd hh:mm:ss的形式:
LocalDateTime dt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(dtf.format(dt));
7、流
定義:流是Java API的新成員,它允許我們以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)。就現在來說,我們可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地並行處理,也就是說我們不用寫多線程代碼了。
Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操作;高級版本的 Stream,用戶只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字符串”、“獲取每個字符串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換。
Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串行化操作。顧名思義,當使用串行方式去遍歷時,每個 item 讀完後再讀下一個 item。而使用並行去遍歷時,數據會被分成多個段,其中每一個都在不同的線程中處理,然後將結果一起輸出。Stream 的並行操作依賴於 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。
流的操作類型分爲兩種:
- Intermediate:一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,並沒有真正開始流的遍歷。
- Terminal:一個流只能有一個 terminal 操作,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,纔會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。
在對於一個 Stream 進行多次轉換操作 (Intermediate 操作),每次都對 Stream 的每個元素進行轉換,而且是執行多次,這樣時間複雜度就是 N(轉換次數)個 for 循環裏把所有操作都做掉的總和嗎?其實不是這樣的,轉換操作都是 lazy 的,多個轉換操作只會在 Terminal 操作的時候融合起來,一次循環完成。我們可以這樣簡單的理解,Stream 裏有個操作函數的集合,每次轉換操作就是把轉換函數放入這個集合中,在 Terminal 操作的時候循環 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();