JAVA8 新特性詳解

2020-12-08 全棧技術資源社區

一、接口的默認方法

在接口中新增了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中支持多重註解了

相關焦點

  • Java新玩法,Java8新特性終極解析
    Java 8引入了FunctionalInterface註解來表明一個接口打算成為一個函數式接口。例如,java.lang.Runnable就是一個函數式接口。新版本向 java.util.function包中添加了很多新的函數式接口。
  • Java 15 即將到來,新特性速覽!
    在發布前夕,我們不妨先一窺新版 JDK 的新特性:第二個外部內存訪問 API(孵化階段),它將使 Java 程序安全高效地訪問 Java 堆以外的外部存儲器。該 API 應該能夠在各種外部內存(如本機、持久和託管堆)上進行操作。
  • 我的世界:java版與基巖版,相差兩歲的哥弟倆,有多少不同特性?
    2009年,notch著手開發Minecraft的java版本。2011年,mojang工作室開發了Minecraft移動版,又稱攜帶版,最後被統稱為基巖版。從開發時間來講,java版與基巖版算是一對相差2歲的兄弟倆。由於版本不同,帶來了很多遊戲特性的差異。
  • 我的世界:基巖版對比Java版,多出了這七個特性,堪稱隱藏的彩蛋
    你玩的是java版還是基巖版呢?這兩個版本是我們經常使用的版本,看似一模一樣,其實在細節上也有些一些差別。對比Java版,基巖版多出了這七個特性。今天我們就來聊一聊基巖版當中特有的七個特性,堪稱隱藏的彩蛋,如果你都沒有,那絕不是老mc!特性一:禿羊剪羊毛的事情mc玩家們都經常做,獲取羊毛以後,羊也變成了禿羊。這一點jave版與基巖版相似,而特性恰恰就是禿羊的顏色。在java版本當中,被剪掉的羊身體上的顏色與本身顏色並不相同。
  • 我的世界:只有JAVA版才有的5個特性,基巖版本只有眼饞的份
    這次阿樂給大家帶來的只有java版本才有的五個新特性。雙持功能基巖版本也有雙持功能。在java版本上副手可以放置任何東西的。但是在基巖版本上面副手是不能放置任何東西的。比如說在基巖版本上鑽石劍就不能放進去?而在java版本上面可以放置任何我的世界中的物品,這就是基巖版和java版本的區別。
  • java_線程池詳解
    java創建線程到底有幾種方式?Worker對象的多少代表著線程池的線程容量每個Worker會從BlockingQueue中獲取你的Runnable實例任務,進行執行,沒有的話,會阻塞在那裡,直到獲取到隊列中的任務新建線程任務流程1、如果HashSet中的線程數量沒有達到核心線程數量,那麼就會新創建一個
  • 我的世界基巖版還用羨慕java版嗎?這幾個特性你們沒有
    在很久以前,基巖版剛剛起步時,連紅石電路都沒有,許多基巖版玩家都很羨慕java版裡面的功能。可以說那時候基巖版的玩法總是落後於java版,但隨著基巖版的不斷更新,玩法漸漸追趕上了java版,由於它優化好,便攜性高,很受玩家們的喜愛,現在更是有一些java版沒有的玩法或特性,來看一看吧!
  • java集合詳解合集
    如果兩個對象通過compareTo(Object obj)方法比較相等,新對象將無法添加到TreeSet集合中(牢記Set是不允許重複的概念)。*;import java.io.import java.util.
  • Java15正式發布, 14個新特性,刷新你的認知!!
    現在的 JDK 真變成了「版本帝」,無力吐槽啊,版本發到 15,大部分人卻都還在用著 JDK 7/8,甚至 6。不過沒關係,多了解一下,多掌握一點新東西,對你來說沒有壞處。新特性JDK 15 新特性一覽表: ID JEP Feature
  • 你必須掌握的 21 個 Java 核心技術!
    性能調優調優:Thread Dump, 分析內存結構class 二進位字節碼結構, class loader 體系 , class加載過程 , 實例創建過程方法執行過程Java各個大版本更新提供的新特性
  • 極客學院全國首發Apple Watch 技術特性詳解
    從此,小編就可以在地鐵中,公交中,遊泳中,約會中.優雅的露出閃閃的左(右)手腕,是不是場景酷炫吊爆了,吼吼.好吧,個人YY就此結束,上乾貨嘍!就在發布會結束的7小時後,我們極客學院作為中國最大的IT職業在線教育平臺急速首發了關於Apple Watch 的新技術特性詳解,為技術開發愛好者帶來最及時、實戰的技術詳解課程。
  • centos7.2下安裝java環境(JDK1.8)
    https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html2、下載需要帳號登錄才能獲得下載連結,獲得下載連結之後,我們可以用wget命令下載,保存到root文件夾,文件為:jdk-8u271-linux-x64.tar.gz
  • JAVA工具JDK安裝配置詳解
    5.使用滑鼠右鍵點擊【此電腦】-【屬性】-【高級系統設置】開啟系統屬性的界面,在【系統屬性】的界面中切換至【高級】選項卡,點擊打開下面的【環境變量】6.在這裡我們新建一個用戶變量,如下:點擊【新建】,在新建的界面中,設置輸入變量名為JAVA_HOME,變量值指向你安裝JDK的目錄,這裡為C:\program files\java
  • 實戰系列:使用Java8 Optional類優雅解決空指針問題
    Java8 由Oracle在2014年發布,是繼Java5之後最具革命性的版本。Java8吸收其他語言的精髓帶來了函數式編程,lambda表達式,Stream流等一系列新特性,學會了這些新特性,可以讓你實現高效編碼優雅編碼。1.
  • QQ飛車手遊指揮官SPX特性是什麼 指揮官SPX六維性能詳解[多圖]
    類型:賽車競技 大小:804.98MB 評分:9.8 平臺: 標籤:競速遊戲騰訊遊戲男生精選3D 立即下載 QQ飛車手遊指揮官SPX特性怎麼樣,這款即將上線的A車,很多玩家都不知道,接下來18183小陳為大家介紹QQ飛車指揮官SPX六維性能詳解。
  • OpenJdk1.8筆記——java啟動流程
    Jdk中java的入口函數文件為openjdk\jdk\src\share\bin\main.c中的main方法(window上為WinMain),然後調用jdk8u-dev/jdk/src/share/bin/java.c的JLI_Launch方法,啟動一個jvm虛擬機;程序入口
  • Java的新未來:JVM、Kotlin和Java將在2020年後走向何方?
    開發人員很喜歡這種新函數模式,它具有更強的靈活性,並且能夠安全、輕鬆地編寫並行代碼。可以在下面的Google趨勢圖中看到這種趨勢:示例包括新的Java記錄,新的文本塊(帶三引號的多行字符串)和新的switch語句,後者或多或少是Kotlin when語句的副本。這些就是筆者所說的「Java的Kotlin化」。通過成為更強大的競爭對手,Kotlin為Java指明了前進的道路。從筆者的角度來看,Kotlin是我所見過的唯一可以超越Java成為行業領導者的語言。
  • pacebox-springboot 1.1.5 發布,java 生態框架
    inter-boot-demo 主要提供權限管理(菜單、角色、用戶),elasticsearch入參出參日誌,數據加解密,分布式追蹤(基於opentracing),阿里雲OSS,百度雲BOS,騰訊COS支持、本地存儲、FTP存儲、阿里、百度、騰訊簡訊雲合併接入等方式boot、micro版後續所需開發得模塊:    商城、監控、支付、消息、任務新特性
  • Go+iris吊打Java+SpringBoot,是Java老了嗎?且慢,Vert.x有話說
    Java界的性能擔當根本就不是Spring,只是因為國內用java的web框架主要是Spring,我們才拿它來測……本次我們就請出java界的性能擔當——Vert.x,跟Iris再比一次這次的結果會比較有說服力因為iris在官網自稱自己是
  • JAVA 基礎:JAVA開發環境搭建
    (3)點擊下一步,更改jre的安裝路徑(4)點擊下一步,等待安裝完成3.設置系統環境變量(1)參數配置示例:JAVA_HOMEE:\Java\jdk1.8.0打開Path變量,在變量值最前加入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;(方法同上)4.驗證:運行cmd,輸入java -version,顯示java版本則成功。