欧美成人午夜免费全部完,亚洲午夜福利精品久久,а√最新版在线天堂,另类亚洲综合区图片小说区,亚洲欧美日韩精品色xxx

扣丁學堂Java培訓之Java 8新特性詳解

2018-05-22 14:25:27 1355瀏覽

今天扣丁學堂給大家介紹一下關于Java8新特性,并將使用簡單的代碼示例來指導你如何使用默認接口方法,lambda表達式,方法引用以及多重Annotation,之后你將會學到最新的API上的改進,比如流,函數式接口,Map以及全新的日期API等內容,下面我們一起來看一下吧。

一、接口的默認方法

Java8允許我們給接口添加一個非抽象的方法實現,只需要使用default關鍵字即可,這個特征又叫做擴展方法,示例如下:

interfaceFormula{
doublecalculate(inta);
defaultdoublesqrt(inta){
returnMath.sqrt(a);
}
}

Formula接口在擁有calculate方法之外同時還定義了sqrt方法,實現了Formula接口的子類只需要實現一個calculate方法,默認方法sqrt將在子類上可以直接使用。

代碼如下:

Formulaformula=newFormula(){
@Override
publicdoublecalculate(inta){
returnsqrt(a*100);
}
};
formula.calculate(100);//100.0
formula.sqrt(16);//4.0

文中的formula被實現為一個匿名類的實例,該代碼非常容易理解,6行代碼實現了計算sqrt(a*100)。在下一節(jié)中,我們將會看到實現單方法接口的更簡單的做法。

譯者注:在Java中只有單繼承,如果要讓一個類賦予新的特性,通常是使用接口來實現,在C++中支持多繼承,允許一個子類同時具有多個父類的接口與功能,在其他語言中,讓一個類同時具有其他的可復用代碼的方法叫做mixin。新的Java8的這個特新在編譯器實現的角度上來說更加接近Scala的trait。在C#中也有名為擴展方法的概念,允許給已存在的類型擴展方法,和Java8的這個在語義上有差別。

二、Lambda表達式

首先看看在老版本的Java中是如何排列字符串的:

代碼如下:

List<String>names=Arrays.asList("peter","anna","mike","xenia");
Collections.sort(names,newComparator<String>(){
@Override
publicintcompare(Stringa,Stringb){
returnb.compareTo(a);
}
});

只需要給靜態(tài)方法Collections.sort傳入一個List對象以及一個比較器來按指定順序排列。通常做法都是創(chuàng)建一個匿名的比較器對象然后將其傳遞給sort方法。

在Java8中你就沒必要使用這種傳統(tǒng)的匿名對象的方式了,Java8提供了更簡潔的語法,lambda表達式:

代碼如下:

Collections.sort(names,(Stringa,Stringb)->{
returnb.compareTo(a);
});

看到了吧,代碼變得更段且更具有可讀性,但是實際上還可以寫得更短:

代碼如下:

Collections.sort(names,(Stringa,Stringb)->b.compareTo(a));

對于函數體只有一行代碼的,你可以去掉大括號{}以及return關鍵字,但是你還可以寫得更短點:

代碼如下:
Collections.sort(names,(a,b)->b.compareTo(a));

Java編譯器可以自動推導出參數類型,所以你可以不用再寫一次類型。接下來我們看看lambda表達式還能作出什么更方便的東西來:

三、函數式接口

Lambda表達式是如何在java的類型系統(tǒng)中表示的呢?每一個lambda表達式都對應一個類型,通常是接口類型。而“函數式接口”是指僅僅只包含一個抽象方法的接口,每一個該類型的lambda表達式都會被匹配到這個抽象方法。因為默認方法不算抽象方法,所以你也可以給你的函數式接口添加默認方法。

我們可以將lambda表達式當作任意只包含一個抽象方法的接口類型,確保你的接口一定達到這個要求,你只需要給你的接口添加@FunctionalInterface注解,編譯器如果發(fā)現你標注了這個注解的接口有多于一個抽象方法的時候會報錯的。

示例如下:

@FunctionalInterface
interfaceConverter<F,T>{
Tconvert(Ffrom);
}
Converter<String,Integer>converter=(from)->Integer.valueOf(from);
Integerconverted=converter.convert("123");
System.out.println(converted);//123

需要注意如果@FunctionalInterface如果沒有指定,上面的代碼也是對的。

譯者注將lambda表達式映射到一個單方法的接口上,這種做法在Java8之前就有別的語言實現,比如RhinoJavaScript解釋器,如果一個函數參數接收一個單方法的接口而你傳遞的是一個function,Rhino解釋器會自動做一個單接口的實例到function的適配器,典型的應用場景有org.w3c.dom.events.EventTarget的addEventListener第二個參數EventListener。

四、方法與構造函數引用

前一節(jié)中的代碼還可以通過靜態(tài)方法引用來表示:

代碼如下:

Converter<String,Integer>converter=Integer::valueOf;
Integerconverted=converter.convert("123");
System.out.println(converted);//123

Java8允許你使用::關鍵字來傳遞方法或者構造函數引用,上面的代碼展示了如何引用一個靜態(tài)方法,我們也可以引用一個對象的方法:

代碼如下:
converter=something::startsWith
Stringconverted=converter.convert("Java");
System.out.println(converted);//"J"

接下來看看構造函數是如何使用::關鍵字來引用的,首先我們定義一個包含多個構造函數的簡單類:

代碼如下:

classPerson{
StringfirstName;
StringlastName;
Person(){}
Person(StringfirstName,StringlastName){
this.firstName=firstName;
this.lastName=lastName;
}
}

接下來我們指定一個用來創(chuàng)建Person對象的對象工廠接口:

代碼如下:

interfacePersonFactory<PextendsPerson>{
Pcreate(StringfirstName,StringlastName);
}

這里我們使用構造函數引用來將他們關聯起來,而不是實現一個完整的工廠:

代碼如下:
PersonFactory<Person>personFactory=Person::new;
Personperson=personFactory.create("Peter","Parker");

我們只需要使用Person::new來獲取Person類構造函數的引用,Java編譯器會自動根據PersonFactory.create方法的簽名來選擇合適的構造函數。

五、Lambda作用域

在lambda表達式中訪問外層作用域和老版本的匿名對象中的方式很相似。你可以直接訪問標記了final的外層局部變量,或者實例的字段以及靜態(tài)變量。

六、訪問局部變量

我們可以直接在lambda表達式中訪問外層的局部變量:

代碼如下:

finalintnum=1;
Converter<Integer,String>stringConverter=
(from)->String.valueOf(from+num);
stringConverter.convert(2);//3

但是和匿名對象不同的是,這里的變量num可以不用聲明為final,該代碼同樣正確:

代碼如下:

intnum=1;
Converter<Integer,String>stringConverter=
(from)->String.valueOf(from+num);
stringConverter.convert(2);//3

不過這里的num必須不可被后面的代碼修改(即隱性的具有final的語義),例如下面的就無法編譯:

復制代碼代碼如下:

intnum=1;
Converter<Integer,String>stringConverter=
(from)->String.valueOf(from+num);
num=3;

在lambda表達式中試圖修改num同樣是不允許的。

七、訪問對象字段與靜態(tài)變量

和本地變量不同的是,lambda內部對于實例的字段以及靜態(tài)變量是即可讀又可寫。該行為和匿名對象是一致的:

代碼如下:

  classLambda4{
  staticintouterStaticNum;
  intouterNum;
  voidtestScopes(){
  Converter<Integer,String>stringConverter1=(from)->{
  outerNum=23;
  returnString.valueOf(from);
  };
  Converter<Integer,String>stringConverter2=(from)->{
  outerStaticNum=72;
  returnString.valueOf(from);
  };
  }
  }

八、訪問接口的默認方法

還記得第一節(jié)中的formula例子么,接口Formula定義了一個默認方法sqrt可以直接被formula的實例包括匿名對象訪問到,但是在lambda表達式中這個是不行的。

Lambda表達式中是無法訪問到默認方法的,以下代碼將無法編譯:

代碼如下:

Formulaformula=(a)->sqrt(a*100);
Built-inFunctionalInterfaces

JDK1.8API包含了很多內建的函數式接口,在老Java中常用到的比如Comparator或者Runnable接口,這些接口都增加了@FunctionalInterface注解以便能用在lambda上。

Java8API同樣還提供了很多全新的函數式接口來讓工作更加方便,有一些接口是來自GoogleGuava庫里的,即便你對這些很熟悉了,還是有必要看看這些是如何擴展到lambda上使用的。

Predicate接口

Predicate接口只有一個參數,返回boolean類型。該接口包含多種默認方法來將Predicate組合成其他復雜的邏輯(比如:與,或,非):

代碼如下:

  Predicate<String>predicate=(s)->s.length()>0;
  predicate.test("foo");//true
  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();

Function接口

Function接口有一個參數并且返回一個結果,并附帶了一些可以和其他函數組合的默認方法(compose,andThen):

代碼如下:

Function<String,Integer>toInteger=Integer::valueOf;
Function<String,String>backToString=toInteger.andThen(String::valueOf);
backToString.apply("123");//"123"

Supplier接口

Supplier接口返回一個任意范型的值,和Function接口不同的是該接口沒有任何參數

復制代碼代碼如下:

Supplier<Person>personSupplier=Person::new;
personSupplier.get();//newPerson

Consumer接口

Consumer接口表示執(zhí)行在單個參數上的操作。

復制代碼代碼如下:

Consumer<Person>greeter=(p)->System.out.println("Hello,"+p.firstName);
greeter.accept(newPerson("Luke","Skywalker"));

Comparator接口

Comparator是老Java中的經典接口,Java8在此之上添加了多種默認方法:

復制代碼代碼如下:

Comparator<Person>comparator=(p1,p2)->p1.firstName.compareTo(p2.firstName);
Personp1=newPerson("John","Doe");
Personp2=newPerson("Alice","Wonderland");
comparator.compare(p1,p2);//>0
comparator.reversed().compare(p1,p2);//<0

Optional接口

Optional不是函數是接口,這是個用來防止NullPointerException異常的輔助類型,這是下一屆中將要用到的重要概念,現在先簡單的看看這個接口能干什么:

Optional被定義為一個簡單的容器,其值可能是null或者不是null。在Java8之前一般某個函數應該返回非空對象但是偶爾卻可能返回了null,而在Java8中,不推薦你返回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接口

java.util.Stream表示能應用在一組元素上一次執(zhí)行的操作序列。Stream操作分為中間操作或者最終操作兩種,最終操作返回一特定類型的計算結果,而中間操作返回Stream本身,這樣你就可以將多個操作依次串起來。Stream的創(chuàng)建需要指定一個數據源,比如java.util.Collection的子類,List或者Set,Map不支持。Stream的操作可以串行執(zhí)行或者并行執(zhí)行。

首先看看Stream是怎么用,首先創(chuàng)建實例代碼的用到的數據List:

復制代碼代碼如下:

List<String>stringCollection=newArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Java8擴展了集合類,可以通過Collection.stream()或者Collection.parallelStream()來創(chuàng)建一個Stream。下面幾節(jié)將詳細解釋常用的Stream操作:

Filter過濾

過濾通過一個predicate接口來過濾并只保留符合條件的元素,該操作屬于中間操作,所以我們可以在過濾后的結果來應用其他Stream操作(比如forEach)。forEach需要一個函數來對過濾后的元素依次執(zhí)行。forEach是一個最終操作,所以我們不能在forEach之后來執(zhí)行其他Stream操作。

復制代碼代碼如下:

stringCollection
.stream()
.filter((s)->s.startsWith("a"))
.forEach(System.out::println);
//"aaa2","aaa1"

Sort排序

排序是一個中間操作,返回的是排序好后的Stream。如果你不指定一個自定義的Comparator則會使用默認排序。

復制代碼代碼如下:
stringCollection
.stream()
.sorted()
.filter((s)->s.startsWith("a"))
.forEach(System.out::println);
//"aaa1","aaa2"

需要注意的是,排序只創(chuàng)建了一個排列好后的Stream,而不會影響原有的數據源,排序之后原數據stringCollection是不會被修改的:

復制代碼代碼如下:

System.out.println(stringCollection);
//ddd2,aaa2,bbb1,aaa1,bbb3,ccc,bbb2,ddd1

Map映射

中間操作map會將元素根據指定的Function接口來依次將元素轉成另外的對象,下面的示例展示了將字符串轉換為大寫字符串。你也可以通過map來講對象轉換成其他類型,map返回的Stream類型是根據你map傳遞進去的函數的返回值決定的。

復制代碼代碼如下:

stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a,b)->b.compareTo(a))
.forEach(System.out::println);
//"DDD2","DDD1","CCC","BBB3","BBB2","AAA2","AAA1"

Match匹配

Stream提供了多種匹配操作,允許檢測指定的Predicate是否匹配整個Stream。所有的匹配操作都是最終操作,并返回一個boolean類型的值。

復制代碼代碼如下:

  booleananyStartsWithA=
  stringCollection
  .stream()
  .anyMatch((s)->s.startsWith("a"));
  System.out.println(anyStartsWithA);//true
  booleanallStartsWithA=
  stringCollection
  .stream()
  .allMatch((s)->s.startsWith("a"));
  System.out.println(allStartsWithA);//false
  booleannoneStartsWithZ=
  stringCollection
  .stream()
  .noneMatch((s)->s.startsWith("z"));
  System.out.println(noneStartsWithZ);//true

Count計數

計數是一個最終操作,返回Stream中元素的個數,返回值類型是long。

復制代碼代碼如下:

longstartsWithB=
stringCollection
.stream()
.filter((s)->s.startsWith("b"))
.count();
System.out.println(startsWithB);//3
Reduce規(guī)約

這是一個最終操作,允許通過指定的函數來講stream中的多個元素規(guī)約為一個元素,規(guī)越后的結果是通過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"

并行Streams

前面提到過Stream有串行和并行兩種,串行Stream上的操作是在一個線程中依次完成,而并行Stream則是在多個線程上同時執(zhí)行。

下面的例子展示了是如何通過并行Stream來提升性能:

首先我們創(chuàng)建一個沒有重復元素的大表:

復制代碼代碼如下:

intmax=1000000;
List<String>values=newArrayList<>(max);
for(inti=0;i<max;i++){
UUIDuuid=UUID.randomUUID();
values.add(uuid.toString());
}

然后我們計算一下排序這個Stream要耗時多久,

串行排序:

復制代碼代碼如下:

longt0=System.nanoTime();
longcount=values.stream().sorted().count();
System.out.println(count);
longt1=System.nanoTime();
longmillis=TimeUnit.NANOSECONDS.toMillis(t1-t0);
System.out.println(String.format("sequentialsorttook:%dms",millis));
//串行耗時:899ms

并行排序:

復制代碼代碼如下:

  longt0=System.nanoTime();
  longcount=values.parallelStream().sorted().count();
  System.out.println(count);
  longt1=System.nanoTime();
  longmillis=TimeUnit.NANOSECONDS.toMillis(t1-t0);
  System.out.println(String.format("parallelsorttook:%dms",millis));
  //并行排序耗時:472ms

上面兩個代碼幾乎是一樣的,但是并行版的快了50%之多,唯一需要做的改動就是將stream()改為parallelStream()。

Map

前面提到過,Map類型不支持stream,不過Map提供了一些新的有用的方法來處理一些日常任務。

復制代碼代碼如下:

Map<Integer,String>map=newHashMap<>();
for(inti=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

以上就是關于Java8的新特性的詳細介紹,希望能給大家一個參考。想要了解更多內容的小伙伴可以登錄扣丁學堂官網了解更多內容??鄱W堂是專業(yè)的Java培訓機構,不僅有專業(yè)的老師和與時俱進的課程體系,還有大量的Java視頻教程供學員觀看學習,想要學習Java的小伙伴快快行動吧。Java技術交流群:670348138。


扣丁學堂微信公眾號


【關注微信公眾號獲取更多學習資料】



查看更多關于“Java開發(fā)資訊的相關文章>>

標簽: Java培訓 Java開發(fā)程序員 Java視頻教程

熱門專區(qū)

暫無熱門資訊

課程推薦

微信
微博
15311698296

全國免費咨詢熱線

郵箱:codingke@1000phone.com

官方群:148715490

北京千鋒互聯科技有限公司版權所有   北京市海淀區(qū)寶盛北里西區(qū)28號中關村智誠科創(chuàng)大廈4層
京ICP備2021002079號-2   Copyright ? 2017 - 2022
返回頂部 返回頂部