JAVA8新特性學習筆記
一、Lamda表示式
在JAVA8新特性中主要一點就是支援了Lamda表示式,這為我們開發帶來了一些便利。
傳統中我們實現匿名內部類是這樣的:
package cn.com; interface Message {// 這適宜個一個介面 public void print(String str) ;// 裡面只有一個抽象方法 } public class TestDemo { public static void main(String[] args) { Message msg= new Message() { @Override public void print(String str) { System.out.println(str); } } ;// 匿名內部類定義完成了 msg.print("Hello word"); } }
使用Lamda表示式後
package cn.com.java;
@FunctionalInterface
interface Message {// 這適宜個一個介面
public void print(String str);// 裡面只有一個抽象方法
}
public class TestDemo {
public static void main(String[] args) {
Message msg = (s) -> System.out.println(s);
msg.print("Hello word");
}
}
其中(s)要與介面中print方法的引數數量相同,這種使用的話就只允許介面中只有一個抽象方法,所以用到了@FunctionalInterface註解。
1、在使用Lamda表示式中有三種語法:
1.1、使用單行語句就是上面的例子;
(params) -> 單行語句;
1.2、使用表示式
(params) -> 表示式;
package cn.com.java; @FunctionalInterface interface Message {// 這適宜個一個介面 public int print(int a, int b);// 裡面只有一個抽象方法 } public class TestDemo { public static void main(String[] args) { Message msg = (a, b) -> a + b; // 單行語句中可以不用使用return宣告返回值,如果只有一個引數的話(a,b)中括號也可以不需要 msg.print(1, 2); } }
1.3、多行語句
(params) -> {多行語句};
package cn.com.java;
@FunctionalInterface
interface Message {// 這適宜個一個介面
public int print(int a, int b);// 裡面只有一個抽象方法
}
public class TestDemo {
public static void main(String[] args) {
Message msg = (a, b) ->{
int sum = a+b;
return sum;
};
msg.print(1, 2);
}
}
2、JAVA8中介面可以擴充普通方法和靜態方法
增加普通方法的話使用default宣告
package cn.com.java;
@FunctionalInterface
interface Message {
public int print(int a, int b);
default String getInfo(String str){
return str;
}
static String fun(String str){
return str;
}
}
public class TestDemo {
public static void main(String[] args) {
Message msg = (a, b) ->{
int sum = a+b;
return sum;
};
msg.print(1, 2);
msg.getInfo("Hello Word");//普通方法由物件呼叫
Message.fun("Hello Word");//靜態方法由類名稱呼叫
}
}
二、方法引用
所謂的方法引用實際上指的是將一個特定類的方法功能對映過來,而後通過介面中的方法,利用lamda表示式實現方法體的定義,當然這種定義的形式一共分為四種
1、類之中構造方法的引用:類名稱:: new
package cn.com.java;
class Book{
private String name;
private int price;
public Book(String name,int price){
this.name = name;
this.price = price;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "書名:"+this.name+";價格:"+this.price;
}
}
@FunctionalInterface
interface Message<T extends Book> {
public T print(String name,int price);// 如果想引用Book類之中的構造方法,需引數向Book中的保持一致
}
public class TestDemo {
public static void main(String[] args) {
Message<Book> mes = Book::new;
System.out.println(mes.print("Java開發", 30));
}
}
2、類中靜態方法的引用:類名稱:: 靜態方法名稱
package cn.com.java;
interface Message {
public Integer print(String msg);
}
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Message mes = Integer::parseInt;
System.out.println(mes.print("123456"));
}
}
3、類中普通方法的引用:例項化物件名稱:: 普通方法
package cn.com.java;
interface Message {
public Integer print(String msg);
}
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
String a = "a";
Message mes = a::compareTo;
System.out.println(mes.print("b"));
}
}
4、特定型別的任意方法引用:類名稱:: 方法名稱。
在String類裡面有一個不區分大小寫判斷內容大小的方法:public int compareToIgnoreCase(String str);
package cn.com.java;
import java.util.Arrays;
import java.util.Comparator;
public class TestDemo {
public static void main(String[] args) {
String[] data = new String[] { "test", "hello", "eclipse", "java",
"Oracle" };
Comparator<String> cmp = String::compareToIgnoreCase;// 特定的型別的方法
Arrays.sort(data, cmp);
System.out.println(Arrays.toString(data));
}
}
三、系統提供的函式式介面
從JDK 1.8開始為了方便使用者開發專門提供了一個新的包:java.util.function,在這個包裡面針對於使用者有可能出現的函式式介面做了一個公共定義。
在java.util.function包之中最為核心的只有四個介面:
·功能型介面:Function;
·消費型介面:Consumer;
·供給型介面:Supplier;
·斷言型介面:Predicate。
1、功能型介面 Function
@FunctionalInterface
public interface Function<T, R> {
publicR apply(T t) ;// 接收資料而後返回處理結果
}
範例:實現功能型介面的引用
·本次引用Integer類的parseInt()方法,這個方法要求接收String型資料,而後返回int型資料。
package cn.com.java;
import java.util.function.Function;
public class TestDemo {
public static void main(String[] args) {
Function<String, Integer> fun= Integer::parseInt; // parseInt()方法為static型
int num= fun.apply("100");
System.out.println(num* 2);
}
}
也就是說這種既能接收資料,又能返回結果的方法,都用Function介面定義
2、消費型介面 Consumer
@FunctionalInterface
public interface Consumer<T> {
publicvoidaccept(T t); // 只是接收資料,並沒有返回值存在
}
範例:使用消費型介面,引用System.out.println()這個方法,只接收資料但是沒有返回值
package cn.com.java;
import java.util.function.Consumer;
public class TestDemo {
public static void main(String[] args) {
Consumer<String> con= System.out::println;con.accept("Hello World !");
}
}
3、供給型介面 Supplier
@FunctionalInterface
public interface Supplier<T> {
publicT get();
}
本介面的方法沒有引數,但是卻可以返回資料。
範例:設定供給型方法引用,本次引用System.currentTimeMillis();
package cn.com.java;
import java.util.function.Supplier;
public class TestDemo {
public static void main(String[] args) {
Supplier<Long> sup= System :: currentTimeMillis;
System.out.println(sup.get());
}
}
4、·斷言型介面 Predicate
@FunctionalInterface
public interfacePredicate<T> {
public boolean test(T t);
}
現在是一個判斷操作,那麼如果是判斷操作,就使用正則驗證。
範例:引用String類中的matches()方法
package cn.com.java;
import java.util.function.Predicate;
public class TestDemo {
public static void main(String[] args) {
String str= "100";// String類物件
Predicate<String> pre= str::matches;
System.out.println(pre.test("\\d+"));
}
}
以上是四個核心介面,實際上這四個介面會了,那麼整個java.util.function包之中的介面就明白怎麼使了。
例如,隨便找一個BiFunction介面,這個介面與Function相似的,此介面定義如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
publicR apply(T t, U u);
}
雖然與Function介面不同,但是這裡面可以設定兩個引數。
範例:利用BiFunction介面引用一個方法
·引用方法,String類的replaceAll()方法。
package cn.com.java;
import java.util.function.BiFunction;
public class TestDemo {
public static void main(String[] args) {
String str= "hello";
BiFunction<String, String, String> bf= str::replaceAll;
System.out.println(bf.apply("l", "_"));
}
}
整個包之中的介面的功能都是類似的,實際上四個會了,所有的也就都會了。
之所以系統會提供內建的函式式介面,那麼就會在大量的系統類庫之中使用它。在Collection接口裡面新定義了一個forEach()方法:default void forEach(Consumer<? super T> action)
此方法是一個default方法可以直接利用介面物件呼叫,同時這個方法裡面接收有一個消費型介面。
範例:List遍歷輸出
package cn.com.java;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
public static void main(String[] args) {
List<String> pro= new ArrayList<String>();
pro.add("java");
pro.add("android");
pro.add("pl/sql");
pro.add("ios");
pro.add("python");
pro.add("node.js");
// pro.forEach((s) -> System.out.println(s));// 如果只有一個引數,直接編寫也可以,不用寫()了
pro.forEach(s-> System.out.println(s));
}
}
在以後學習系統類的時候會大量的使用到在java.util.function包之中定義的函式式介面,所以掌握這四個介面就是掌握了整個包的使用。
四、資料流
類集自從JDK 1.2開始引入之後,一開始給予我們的特徵是動態物件陣列,後來有了泛型(JDK 1.5),讓類集操作更加的安全,再後來到了JDK 1.8的時代,類集又變為了重要的批量資料處理的容器,而這個容器的操作實現就是利用資料流完成的。
所以在JDK 1.8版本之後專門提供了一個數據流的工具包:java.util.stream。而在這個包裡面最需要關注的是Stream介面,此介面是BaseStream的子介面。
在BaseStream接口裡面會包含有:DoubleStream、IntStream、LongStream、Stream<T>。
類集自從 JDK 1.2 開始引入之後,一開始給予我們的特徵是動態物件陣列,後來有了泛型( JDK 1.5 ),讓類集操作更 加的安全,再後來到了 JDK 1.8 的時代,類集又變為了重要的批量資料處理的容器,而這個容器的操作實現就是利用資料 流完成的 類集自從 JDK 1.2 開始引入之後,一開始給予我們的特徵是動態物件陣列,後來有了泛型( JDK 1.5 ),讓類集操作更 加的安全,再後來到了 JDK 1.8 的時代,類集又變為了重要的批量資料處理的容器,而這個容器的操作實現就是利用資料 流完成的 如果要想關注Stream介面使用,那麼首先需要來觀察Collection介面。在Collection接口裡面定義瞭如下一個方法,可以取得Stream介面的例項化物件:default Stream<E> stream()。 類集自從 JDK 1.2 開始引入之後,一開始給予我們的特徵是動態物件陣列,後來有了泛型( JDK 1.5 ),讓類集操作更 加的安全,再後來到了 JDK 1.8 的時代,類集又變為了重要的批量資料處理的容器,而這個容器的操作實現就是利用資料 流完成的。 類集自從 JDK 1.2 開始引入之後,一開始給予我們的特徵是動態物件陣列,後來有了泛型( JDK 1.5 ),讓類集操作更 加的安全,再後來到了 JDK 1.8 的時代,類集又變為了重要的批量資料處理的容器,而這個容器的操作實現就是利用資料 流完成的。範例:操作Stream
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class TestDemo3 {
public static void main(String[] args) {
List<String> all= new ArrayList<String>();
all.add("hello");
all.add("word");
all.add("test");
Stream<String> stream= all.stream(); // 將集合變為了資料流的形式
System.out.println(stream.count());// 取得資料流的長度}}
}
}
通過本程式應該清楚一點:Collection集合裡面已經支援了Stream介面物件的取得。這一點可以為隨後要講解的集合的資料分析帶來幫助。除了Collection集合可以為Stream介面例項化之外,那麼實際上也可以利用Stream接口裡面提供的方法完成:
·取得Stream集合:static <T> Stream<T> of(T... values);
·支援forEach輸出:public void forEach(Consumer<? super T> action)。
import java.util.stream.Stream;
public class TestDemo3 {
public static void main(String[] args) {
Stream<String> stream= Stream.of("yootk", "mldn", "mldnjava"); // 準備好資料
stream.forEach(System.out::println); // 方法引用
}
}
現在可以利用Collection介面例項化Stream介面,也可以利用Stream接口裡面的of()方法,利用static方法取得Stream介面物件,但是折騰半天,感覺它就是一個集合個數(Collection介面的size())取得以及集合的輸出(Iterator)。Java8之後最大的特徵是支援了資料的分析操作,所以有了Stream介面物件最大的好處是在於可以進行集合的處理。在函式式接口裡面有一個Predicate,這個介面可以負責斷言操作。 現在可以利用 Collection 介面例項化 Stream 介面,也可以利用 Stream 接口裡面的 of() 方法,利用 static 方法取得 Stream 介面物件,但是折騰半天,感覺它就是一個集合個數( Collection 介面的 size() )取得以及集合的輸出( Iterator )。 Java8 之後最大的特徵是支援了資料的分析操作,所以有了 Stream 介面物件最大的好處是在於可以進行集合的處理。 在函式式接口裡面有一個 Predicate ,這個介面可以負責斷言操作。
範例:新的判斷模式
public static void main(String[] args) {
List<String> pros= new ArrayList<String>();
pros.add("java");
pros.add("android");
pros.add("ios");
pros.add("python");
pros.add("node.js");
filter(pros, (str) -> str.contains("a"));
}
public static void filter(List<String> temp, Predicate<String> pre) {
temp.forEach((s) -> {if(pre.test(s)) {
System.out.println("data = "+ s);
}
});
}
感覺上和輸出沒有任何的區別,但是在這個時候是屬於Java8的輸出模式。但是現在有人覺得,這種程式碼看起來很亂,相當於結合兩個函式式介面。 範例: 執行集合資料 範例:執行集合資料過濾
·集合過濾操作:public Stream<T> filter(Predicate<? super T> predicate);
import java.util.ArrayList;
import java.util.List;
public class TestDemo3 {
public static void main(String[] args) {
List<String> pros= new ArrayList<String>();
pros.add("java");
pros.add("android");
pros.add("ios");
pros.add("python");
pros.add("node.js");
pros.stream().filter((x) -> x.contains("a")).forEach(System.out::println);
}
}
此時雖然簡化了過濾的操作,但是感覺到,這種操作還是完全可以利用Iterator輸出實現。還是覺得沒用。範例:取得過濾後的子集合
在Stream接口裡面有一個收集器:public <R,A> R collect(Collector<? super T,A,R> collector);
範例: 取得過濾後的子集合 在 Stream 接口裡面有一個收集器: public <R,A> R collect(Collector<? super T,A,R> collector) ;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestDemo3 {
public static void main(String[] args) {
List<String> pros= new ArrayList<String>();
pros.add("java");
pros.add("android");
pros.add("ios");
pros.add("python");
pros.add("node.js");
List<String> subList= pros.stream().filter((x) -> x.contains("a")).collect(Collectors.toList()); // 將滿足條件的集合變為了一個新的集合
subList.forEach(System.out::println);
}
}
除了可以進行資料的判斷之外,那麼也可以進行資料的處理,逐個進行處理。
範例:將包含的字串資料進行小寫變大寫
·如果要想針對於每個資料處理:public <R> Stream<R> map(Function<? super T,? extends R> mapper);
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestDemo3 {
public static void main(String[] args) {
List<String> pros= new ArrayList<String>();
pros.add("java");
pros.add("android");
pros.add("android");
pros.add("ios");
pros.add("ios");
pros.add("python");
pros.add("python");
pros.add("python");
pros.add("node.js");
List<String> subList= pros.stream().map((x) -> x.toUpperCase()).collect(Collectors.toList()); // 將滿足條件的集合變為了一個新的集合
subList.forEach(System.out::println);
}
}
也就是說這個時候可以發現,map()方法可以將每一條資料分別進行處理,而後裡面會包含原始的內容。
範例:消除重複資料
·在Stream接口裡面提供了重複資料清除的方法:public Stream<T> distinct()。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TestDemo3 {
public static void main(String[] args) {
List<String> pros= new ArrayList<String>();
pros.add("java");
pros.add("android");
pros.add("android");
pros.add("ios");
pros.add("ios");
pros.add("python");
pros.add("python");
pros.add("python");
pros.add("node.js");
List<String> subList= pros.stream().map((x) -> x.toUpperCase()).distinct().collect(Collectors.toList()); // 將滿足條件的集合變為了一個新的集合
subList.forEach(System.out::println);
}
}
這個時候已經可以成功的消除掉了重複的資料內容。在Stream接口裡面也提供了一些資料的處理方法:
·判斷集合中的全部資料:public boolean allMatch(Predicate<? super T> predicate);
·判斷集合中的任意一個數據:public boolean anyMatch(Predicate<? super T> predicate);
·不匹配:public boolean noneMatch(Predicate<? super T> predicate)。
· 判斷集合中的全部資料: public boolean allMatch(Predicate<? super T> predicate) ; · 判斷集合中的任意一個數據: public boolean anyMatch(Predicate<? super T> predic ate) ; · 不匹配: public boolean noneMatch(Predicate<? super T> predicate) 。if(pros.stream().allMatch((s) -> s.contains("a"))) {
System.out.println("集合中的全部內容都包含有字母a!");
}
if(pros.stream().anyMatch((s) -> s.contains("a"))) {
System.out.println("集合中的全部內容都包含有字母a!");
}
在JDK 1.8之前,如果匿名內部類要想訪問方法中的引數,則引數前必須加上final關鍵字,但是從JDK 1.8開始提供的新特性,匿名內部類訪問方法引數的時候可以不加上final關鍵字了。
以上的判斷你判斷的全部都是單個條件,如果現在有多個條件呢?如果是判斷,則一定使用斷言式函式介面:Predicate,在這個接口裡面提供有一些連線的操作方法:
·與操作:default Predicate<T> and(Predicate<? super T> other);
·或操作:default Predicate<T> or(Predicate<? super T> other);
Predicate<String> condA= (str) -> str.contains("a");
Predicate<String> condB= (str) -> str.length() == 3;
pros.stream().filter(condA.or(condB)).forEach(System.out::println);
實際上資料流本身支援兩類處理方式,一類是並行處理,另一類序列處理(預設),可以利用如下的方法改變處理:
·設定為並行處理:public S parallel();
·設定為序列處理:public S sequential()。
範例:採用並行處理
pros.stream().filter(condA.or(condB)).parallel().forEach(System.out::println);
幸運的是在Java之中,資料流的處理裡面所有的併發操作完全不需要使用者來關心,都可以自己處理。並且最有意思的是可以並行和序列互相切換。
通過以上的講解,實際上就應該清楚一點:所有的操作像map()、filter()這樣操作都屬於中間操作,而像collectors()、forEach()一定都屬於結尾操作。
在BaseStream接口裡面一共定義了四個子介面,以IntStream為例。
範例:使用IntStream介面
·生成整型的資料流:static IntStream range(int startInclusive, int endExclusive)。
IntStream stream= IntStream.range(0, 30);stream.forEach(System.out::println);
Stream介面可以儲存各種型別,而IntStream裡面只能夠儲存int型資料。
從JDK 1.8開始一些類裡面也增加了改變,例如:java.util.Random類,在這個類裡面增加了新的方法:
·返回一個整型資料流:public IntStream ints()。
newRandom().ints().limit(10).forEach(System.out::println);
隨著大資料的發展,基本上Java的開發也向大資料靠攏,所有的概念也都是相通的。map和reduce函式的使用
範例:定義一個購物車的類
import java.util.ArrayList;
import java.util.List;
class Car {
private String pname;
private Integer amount;
private Double price;
public Car() {
super();
}
public Car(String pname, Integer amount, Double price) {
super();
this.pname= pname;
this.amount= amount;
this.price= price;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname= pname;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount= amount;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price= price;
}
}
public class TestDemo3 {
public static void main(String[] args) {
List<Car> all= new ArrayList<Car>();
all.add(new Car("Java開發", 200, 79.8));
all.add(new Car("Java WEB開發", 500, 69.8));
all.add(new Car("Android開發", 700, 89.8));
all.add(new Car("Oracle開發", 300, 88.8));
all.add(new Car("MongoDB開發", 610, 98.8));
all.stream().map((myCar) -> {
System.out.print("書名:"+ myCar.getPname() + ",購買總價:");
return myCar.getAmount() * myCar.getPrice();
}).forEachOrdered(System.out::println); // 統計每本書的各自花費
}
}
現在可以發現map()方法的功能就是針對於集合的每個資料進行了處理。
如果說使用map()方法實現了資料的重新組合,那麼reduce()就是將集合中的所有的資料變為一個結果,例如:類似於SQL中的sum()、avg()、count()函式的功能。
·reduce()方法:public T reduce(T identity, BinaryOperator<T> accumulator)。
double result= all.stream().map((myCar) -> {
// System.out.print("書名:" + myCar.getPname() + ",購買總價:");
return myCar.getAmount() * myCar.getPrice();
}).reduce((sum, carPrice) -> sum+ carPrice).get();
System.out.println("購買總金額:"+ result);
如果要進行統計,可能會包含:總和、最大值、最小值、平均值、數量。
在Stream接口裡面提供了相應的操作:
·處理double資料:public DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
·處理int資料:public IntStream mapToInt(ToIntFunction<? super T> mapper);
·處理long資料:public LongStream mapToLong(ToLongFunction<? super T> mapper)。
在每個返回的接口裡面都提供瞭如下的統計操作方法:
·double資料統計(DoubleStream):public Double SummaryStatistics summaryStatistics()·
int資料的統計(IntStream):public IntSummaryStatistics summaryStatistics()
·long資料的統計(LongStream):public LongSummaryStatistics summaryStatistics()
這些類裡面都提供有一系列的getXxx()方法用於統計相關資訊。
範例:進行reduce功能實現
DoubleSummaryStatistics result= all.stream().mapToDouble((myCar) -> {
return myCar.getAmount() * myCar.getPrice();
}).summaryStatistics();
System.out.println("統計量:"+ result.getCount());
System.out.println("最大值:"+ result.getMax());
System.out.println("最小值:"+ result.getMin());
System.out.println("總和:"+ result.getSum());
System.out.println("平均值:"+ result.getAverage());