一、接口的默認方法
在接口中新增了default方法和static方法,這兩種方法可以有方法體
1、static方法
示例代碼:
public interface DefalutTest {
static int a =5;
default void defaultMethod(){
System.out.println("DefalutTest defalut 方法");
}
int sub(int a,int b);
static void staticMethod() {
System.out.println("DefalutTest static 方法");
}
}
接口裡的靜態方法,即static修飾的有方法體的方法不會被繼承或者實現,但是靜態變量會被繼承
例如:我們添加一個接口DefalutTest的實現類DefaultTestImpl
public class DefaultTestImpl implements DefalutTest{
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
}
如下圖所示是這個實現類中所有可調用的方法:
在這些方法裡面我們無法找到staticMethod方法,則說明接口中的static方法不能被它的實現類直接使用。但是我們看到了defaultMethod,說明實現類可以直接調用接口中的default方法;
那麼如何使用接口中的static方法呢???
接口.static方法調用,如:DefalutTest.staticMethod();
public static void main(String[] args) {
DefaultTestImpl dtl = new DefaultTestImpl();
DefalutTest.staticMethod();
}
當我們試圖使用接口的子接口去調用父接口的static方法時,我們發現,無法調用,找不到方法:
結論:接口中的static方法不能被繼承,也不能被實現類調用,只能被自身調用
2、default方法
準備一個子接口繼承DefalutTest接口
public interface SubTest extends DefalutTest{
}
準備一個子接口的實現類
public class SubTestImp implements SubTest{
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
}
現在我們創建一個子接口實現類對象,並調用對象中的default方法:
public class Main {
public static void main(String[] args) {
SubTestImp stl = new SubTestImp();
stl.defaultMethod();
}
}執行結果:
DefalutTest defalut 方法
結論1:default方法可以被子接口繼承亦可被其實現類所調用
現在我們在子接口中重寫default方法,在進行調用:
public interface SubTest extends DefalutTest{
default void defaultMethod(){
System.out.println("SubTest defalut 方法");
}
}
執行結果:SubTest defalut 方法
結論2:default方法被繼承時,可以被子接口覆寫
現在,我們去除接口間的繼承關係,並使得SubTestImp同時實現父接口和子接口,我們知道此時父接口和子接口中存在同名同參數的default方法,這會怎麼樣?
如下圖所示,實現類報錯,實現類要求必須指定他要實現那個接口中的default方法
結論3:如果一個類實現了多個接口,且這些接口中無繼承關係,這些接口中若有相同的(同名,同參數)的default方法,則接口實現類會報錯,接口實現類必須通過特殊語法指定該實現類要實現那個接口的default方法
特殊語法:<接口>.super.<方法名>([參數])
示例代碼:
public class SubTestImp implements SubTest,DefalutTest{
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
@Override
public void defaultMethod() {
// TODO Auto-generated method stub
DefalutTest.super.defaultMethod();
}
}
使用示例:
//接口代碼
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
//實現
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0
二、Lambda 表達式
Lambda表達式可以看成是匿名內部類,使用Lambda表達式時,接口必須是函數式接口
基本語法:
<函數式接口> <變量名> = (參數1,參數2...) -> {
//方法體
}
說明:
(參數1,參數2…)表示參數列表;->表示連接符;{}內部是方法體
1、=右邊的類型會根據左邊的函數式接口類型自動推斷;
2、如果形參列表為空,只需保留();
3、如果形參只有1個,()可以省略,只需要參數的名稱即可;
4、如果執行語句只有1句,且無返回值,{}可以省略,若有返回值,則若想省去{},則必須同時省略return,且執行語句也保證只有1句;
5、形參列表的數據類型會自動推斷;
6、lambda不會生成一個單獨的內部類文件;
7、lambda表達式若訪問了局部變量,則局部變量必須是final的,若是局部變量沒有加final關鍵字,系統會自動添加,此後在修改該局部變量,會報錯;
示例代碼:
public interface LambdaTest {
abstract void print();
}
public interface LambdaTest2 {
abstract void print(String a);
}
public interface DefalutTest {
static int a =5;
default void defaultMethod(){
System.out.println("DefalutTest defalut 方法");
}
int sub(int a,int b);
static void staticMethod() {
System.out.println("DefalutTest static 方法");
}
}
public class Main {
public static void main(String[] args) {
//匿名內部類--java8之前的實現方式
DefalutTest dt = new DefalutTest(){
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
};
//lambda表達式--實現方式1
DefalutTest dt2 =(a,b)->{
return a-b;
};
System.out.println(dt2.sub(2, 1));
//lambda表達式--實現方式2,省略花括號
DefalutTest dt3 =(a,b)->a-b;
System.out.println(dt3.sub(5, 6));
//測試final
int c = 5;
DefalutTest dt4 =(a,b)->a-c;
System.out.println(dt4.sub(5, 6));
//無參方法,並且執行語句只有1條
LambdaTest lt = ()-> System.out.println("測試無參");
lt.print();
//只有一個參數方法
LambdaTest2 lt1 = s-> System.out.println(s);
lt1.print("有一個參數");
}
}
局部變量修改報錯如圖:
若是強行修改也無法編譯通過
Lambda表達式其他特性:
1、引用實例方法:
語法:
<函數式接口> <變量名> = <實例>::<實例方法名>
//調用
<變量名>.接口方法([實際參數...])
將調用方法時的傳遞的實際參數,全部傳遞給引用的方法,執行引用的方法;
示例代碼:
如我們引用PrintStream類中的println方法。我們知道System類中有一個PrintStream的實例為out,引用該實例方法:System.out::println:
public class Main {
public static void main(String[] args) {
LambdaTest2 lt1 = s-> System.out.println(s);
lt1.print("有一個參數");
//改寫為:
LambdaTest2 lt2 = System.out::println;
lt2.print("實例引用方式調用");
}
}
將lt2調用時的實際參數傳遞給了PrintStream類中的println方法,並調用該方法
2、引用類方法:
語法:
<函數式接口> <變量名> = <類>::<類方法名稱>
//調用
<變量名>.接口方法([實際參數...])
將調用方法時的傳遞的實際參數,全部傳遞給引用的方法,執行引用的方法;
示例代碼:
我們可以以數組排序方式為例
public interface LambdaTest3 {
abstract void sort(List<Integer> list,Comparator<Integer> c);
}
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(50);
list.add(18);
list.add(6);
list.add(99);
list.add(32);
System.out.println(list.toString()+"排序之前");
LambdaTest3 lt3 = Collections::sort;
lt3.sort(list, (a,b) -> {
return a-b;
});
System.out.println(list.toString()+"排序之後");
}
}
執行結果:
[50, 18, 6, 99, 32]排序之前
[6, 18, 32, 50, 99]排序之後
再來看Comparator接口,它屬於函數式接口,所以我們在Comparator入參時,也採取了lambda表達式寫法。
@FunctionalInterface
public interface Comparator<T> {
...
...
...
}
3、引用類的實例方法:
定義、調用接口時,需要多傳遞一個參數,並且參數的類型與引用實例的類型一致
語法:
//定義接口
interface <函數式接口>{
<返回值> <方法名>(<類><類名稱>,[其他參數...]);
}
<函數式接口> <變量名> = <類>::<類實例方法名>
//調用
<變量名>.接口方法(類的實例,[實際參數...])
將調用方法時的傳遞的實際參數,從第二個參數開始(第一個參數指定的類的實例),全部傳遞給引用的方法,執行引用的方法;
示例代碼:
public class LambdaClassTest {
public int add(int a, int b){
System.out.println("LambdaClassTest類的add方法");
return a+b;
}
}
public interface LambdaTest4 {
abstract int add(LambdaClassTest lt,int a,int b);
}
public class Main {
public static void main(String[] args) {
LambdaTest4 lt4 = LambdaClassTest::add;
LambdaClassTest lct = new LambdaClassTest();
System.out.println(lt4.add(lct, 5, 8));
}
}
4、引用構造器方法:
語法:
<函數式接口> <變量名> = <類>::<new>
//調用
<變量名>.接口方法([實際參數...])
把方法的所有參數全部傳遞給引用的構造器,根據參數類型自動推斷調用的構造器方法;
示例代碼:
public interface LambdaTest5 {
abstract String creatString(char[] c);
}
public class Main {
public static void main(String[] args) {
LambdaTest5 lt5 = String::new;
System.out.println(lt5.creatString(new char[]{'1','2','3','a'}));
}
}
根據傳入的參數類型,自動匹配構造函數
三、函數式接口
如果一個接口只有一個抽象方法,則該接口稱之為函數式接口,因為 默認方法 不算抽象方法,所以你也可以給你的函數式接口添加默認方法。
函數式接口可以使用Lambda表達式,lambda表達式會被匹配到這個抽象方法上
我們可以將lambda表達式當作任意只包含一個抽象方法的接口類型,確保你的接口一定達到這個要求,你只需要給你的接口添加 @FunctionalInterface 註解,編譯器如果發現你標註了這個註解的接口有多於一個抽象方法的時候會報錯的
示例代碼:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
四、Lambda 作用域
在lambda表達式中訪問外層作用域和老版本的匿名對象中的方式很相似。你可以直接訪問標記了final的外層局部變量,或者實例的欄位以及靜態變量。
五、訪問局部變量
我們可以直接在lambda表達式中訪問外層的局部變量,但是該局部變量必須是final的,即使沒有加final關鍵字,之後我們無論在哪(lambda表達式內部或外部)修改該變量,均報錯。
六、訪問對象欄位與靜態變量
lambda內部對於實例的欄位以及靜態變量是即可讀又可寫。該行為和匿名對象是一致的;
示例代碼:
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
七、訪問接口的默認方法
Predicate接口
Predicate 接口只有一個參數,返回boolean類型。該接口包含多種默認方法來將Predicate組合成其他複雜的邏輯(比如:與,或,非):
public static void main(String[] args) {
Predicate<String> predicate = (s) -> s.length() > 0;
System.out.println(predicate.test("foo")); // true
System.out.println(predicate.negate().test("foo")); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
System.out.println(nonNull.test(null));
System.out.println(isNull.test(null));
System.out.println(isEmpty.test("sss"));
System.out.println(isNotEmpty.test(""));
}
運行結果:
true
false
false
true
false
false
Function 接口
Function 接口有一個參數並且返回一個結果,並附帶了一些可以和其他函數組合的默認方法(compose, andThen):
Function<String, Integer> toInteger = Integer::valueOf;
System.out.println(toInteger.apply("123").getClass());
Function<String, Object> toInteger2 = toInteger.andThen(String::valueOf);
System.out.println(toInteger2.apply("123").getClass());
輸出:
class java.lang.Integer
class java.lang.String
Supplier 接口
Supplier 接口返回一個任意泛型的值,和Function接口不同的是該接口沒有任何參數
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Consumer 接口
Consumer 接口表示執行在單個參數上的操作。接口只有一個參數,且無返回值
Supplier<LambdaClassTest> personSupplier = LambdaClassTest::new;
Consumer<LambdaClassTest> greeter = (lt) -> System.out.println("Hello, " + lt.getTest());
greeter.accept(personSupplier.get());
Comparator 接口
Comparator 是老Java中的經典接口, Java 8在此之上添加了多種默認方法:
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Optional 接口
Optional 不是函數是接口,這是個用來防止NullPointerException異常的輔助類型,這是下一屆中將要用到的重要概念,現在先簡單的看看這個接口能幹什麼:
Optional 被定義為一個簡單的容器,其值可能是null或者不是null。在Java 8之前一般某個函數應該返回非空對象但是偶爾卻可能返回了null,而在Java 8中,不推薦你返回null而是返回Optional。
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Stream 接口 重要!!!
創建stream–通過of方法
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
創建stream–通過generator方法
生成一個無限長度的Stream,其元素的生成是通過給定的Supplier(這個接口可以看成一個對象的工廠,每次調用返回一個給定類型的對象)
Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三條語句的作用都是一樣的,只是使用了lambda表達式和方法引用的語法來簡化代碼。每條語句其實都是生成一個無限長度的Stream,其中值是隨機的。這個無限長度Stream是懶加載,一般這種無限長度的Stream都會配合Stream的limit()方法來用。
創建stream–通過iterate方法
也是生成無限長度的Stream,和generator不同的是,其元素的生成是重複對給定的種子值(seed)調用用戶指定函數來生成的。其中包含的元素可以認為是:seed,f(seed),f(f(seed))無限循環
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
這段代碼就是先獲取一個無限長度的正整數集合的Stream,然後取出前10個列印。千萬記住使用limit方法,不然會無限列印下去。
通過Collection子類獲取Stream
public interface Collection<E> extends Iterable<E> {
//其他方法省略
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
java.util.Stream 表示能應用在一組元素上一次執行的操作序列。Stream 操作分為中間操作或者最終操作兩種,最終操作返回一特定類型的計算結果,而中間操作返回Stream本身,這樣你就可以將多個操作依次串起來。Stream 的創建需要指定一個數據源,比如 java.util.Collection的子類,List或者Set, Map不支持。Stream的操作可以串行執行或者並行執行。
Java 8擴展了集合類,可以通過 Collection.stream() 或者 Collection.parallelStream() 來創建一個Stream。
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();
stream的其他應用:
1、count()、max()、min()方法
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> collection = new ArrayList<Integer>();
collection.add(14);
collection.add(5);
collection.add(43);
collection.add(89);
collection.add(64);
collection.add(112);
collection.add(55);
collection.add(55);
collection.add(58);
//list長度
System.out.println(collection.parallelStream().count());
//求最大值,返回Option,通過Option.get()獲取值
System.out.println(collection.parallelStream().max((a,b)->{return a-b;}).get());
//求最小值,返回Option,通過Option.get()獲取值
System.out.println(collection.parallelStream().min((a,b)->{return a-b;}).get());
}
}
2、Filter 過濾方法
過濾通過一個predicate接口來過濾並只保留符合條件的元素,該操作屬於中間操作。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> collection = new ArrayList<Integer>();
collection.add(14);
collection.add(5);
collection.add(43);
collection.add(89);
collection.add(64);
collection.add(112);
collection.add(55);
collection.add(55);
collection.add(58);
Long count =collection.stream().filter(num -> num!=null).
filter(num -> num.intValue()>50).count();
System.out.println(count);
}
}
3、distinct方法
去除重複
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> collection = new ArrayList<Integer>();
collection.add(14);
collection.add(5);
collection.add(43);
collection.add(89);
collection.add(64);
collection.add(112);
collection.add(55);
collection.add(55);
collection.add(58);
collection.stream().distinct().forEach(System.out::println);;
}
}
4、Sort 排序
排序是一個中間操作,返回的是排序好後的Stream。如果你不指定一個自定義的Comparator則會使用默認排序。
sringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
需要注意的是,排序只創建了一個排列好後的Stream,而不會影響原有的數據源,排序之後原數據stringCollection是不會被修改的:
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
5、Map 映射
對於Stream中包含的元素使用給定的轉換函數進行轉換操作,新生成的Stream只包含轉換生成的元素。這個方法有三個對於原始類型的變種方法,分別是:mapToInt,mapToLong和mapToDouble。這三個方法也比較好理解,比如mapToInt就是把原始Stream轉換成一個新的Stream,這個新生成的Stream中的元素都是int類型。之所以會有這樣三個變種方法,可以免除自動裝箱/拆箱的額外消耗;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> collection = new ArrayList<String>();
collection.add("14");
collection.add("5");
collection.add("43");
collection.add("89");
collection.add("64");
collection.add("112");
collection.add("55");
collection.add("55");
collection.add("58");
//將String轉化為Integer類型
collection.stream().mapToInt(Integer::valueOf).forEach(System.out::println);
//或
collection.stream().mapToInt(a->Integer.parseInt(a)).forEach(System.out::println);
}
}
也可以這樣用:
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println(「sum is:」+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num * 2).
peek(System.out::println).skip(2).limit(4).sum());
7、limit:
對一個Stream進行截斷操作,獲取其前N個元素,如果原Stream中包含的元素個數小於N,那就獲取其所有的元素;
8、skip:
返回一個丟棄原Stream的前N個元素後剩下元素組成的新Stream,如果原Stream中包含的元素個數小於N,那麼返回空Stream;
9、Match 匹配
Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。所有的匹配操作都是最終操作,並返回一個boolean類型的值。
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
10、Count 計數
計數是一個最終操作,返回Stream中元素的個數,返回值類型是long。
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
11、Reduce 規約
這是一個最終操作,允許通過指定的函數來講stream中的多個元素規約為一個元素,規約後的結果是通過Optional接口表示的:
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Map
前面提到過,Map類型不支持stream,不過Map提供了一些新的有用的方法來處理一些日常任務。
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
以上代碼很容易理解, putIfAbsent 不需要我們做額外的存在性檢查,而forEach則接收一個Consumer接口來對map裡的每一個鍵值對進行操作。
下面的例子展示了map上的其他有用的函數:
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
接下來展示如何在Map裡刪除一個鍵值全都匹配的項:
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
另外一個有用的方法:
map.getOrDefault(42, "not found"); // not found
對Map的元素做合併也變得很容易了:
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
Merge做的事情是如果鍵名不存在則插入,否則則對原鍵對應的值做合併操作並重新插入到map中。
steam在實際項目中使用的代碼片段:
//1、有list集合生成以productId為key值得map集合
Map<String, List<CartManager>> cartManagerGroup =
carts.stream().collect(
Collectors.groupingBy(CartManager::getProductId)
);
//2、取得購物車中數量之和
IntStream is = list.stream().mapToInt((CartManager c)->c.getQuantity());
is.sum();//數量之和
//3、所有訂單中商品數量*訂單金額求和
orderDetailsNew.parallelStream()
.mapToDouble(orderDetailMid -> orderDetailMid.getQuantity()*orderDetailMid.getFinalPrice()).sum()
//4、過濾出指定類型的訂單,並生成新的集合
orderDetails.stream().
filter(orderDetail -> StringUtil.isEmpty(orderDetail.getPromotionsType())|| !orderDetail.getPromotionsType().equals(PromotionTypeEnum.ORDERGIFTPROMOTION.getType())).collect(Collectors.toList());
//5、過濾購物車未被選中商品並生成新的list
carts.stream().filter(cart -> cart.getSelectFlag()==1).collect(Collectors.toList());
//6、將list以商品促銷委key轉化為map
Map<String,List<PromotionsGiftProduct>> map =
promotionsGiftProducts.stream().collect( Collectors.groupingBy(PromotionsGiftProduct::getPromotionId));
//7、從list<Cart>中分離出只存儲productId的列表list<String>
List<String> productIds = needUpdate.parallelStream()
.map(CartManager::getProductId)
.collect(Collectors.toList());
八、Date API
Java 8 在包java.time下包含了一組全新的時間日期API。
Clock 時鐘
Clock類提供了訪問當前日期和時間的方法,Clock是時區敏感的,可以用來取代 System.currentTimeMillis() 來獲取當前的微秒數。某一個特定的時間點也可以使用Instant類來表示,Instant類也可以用來創建老的java.util.Date對象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
Timezones 時區
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime 本地時間
LocalTime 定義了一個沒有時區信息的時間,例如 晚上10點,或者 17:30:15。下面的例子使用前面代碼創建的時區創建了兩個本地時間。之後比較時間並以小時和分鐘為單位計算兩個時間的時間差:
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
LocalTime 提供了多種工廠方法來簡化對象的創建,包括解析時間字符串。
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37
LocalDate 本地日期
LocalDate 表示了一個確切的日期,比如 2014-03-11。該對象值是不可變的,用起來和LocalTime基本一致。下面的例子展示了如何給Date對象加減天/月/年。另外要注意的是這些對象是不可變的,操作返回的總是一個新實例。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
從字符串解析一個LocalDate類型和解析LocalTime一樣簡單:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
LocalDateTime 本地日期時間
LocalDateTime 同時表示了時間和日期,相當於前兩節內容合併到一個對象上了。LocalDateTime和LocalTime還有LocalDate一樣,都是不可變的。LocalDateTime提供了一些能訪問具體欄位的方法。
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
只要附加上時區信息,就可以將其轉換為一個時間點Instant對象,Instant時間點對象可以很容易的轉換為老式的java.util.Date。
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式化LocalDateTime和格式化時間和日期一樣的,除了使用預定義好的格式外,我們也可以自己定義格式:
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
和java.text.NumberFormat不一樣的是新版的DateTimeFormatter是不可變的,所以它是線程安全的。
關於時間日期格式的詳細信息:
九、Annotation 註解
在Java 8中支持多重註解了