Java 8是Java的一個重大版本,是目前企業中使用最廣泛的一個版本。 它支持函數式編程,新的Stream API 、新的日期 API等一系列新特性。 掌握Java8的新特性已經是java程式員的標配,掌握了它,就可以看懂公司里的代碼、高效率地處理大量集合數據以及消滅“嵌套地獄”等等。 ...
【進階】Java8新特性的理解與應用
前言
Java 8是Java的一個重大版本,是目前企業中使用最廣泛的一個版本。
它支持函數式編程,新的Stream API 、新的日期 API等一系列新特性。
掌握Java8的新特性已經是java程式員的標配,掌握了它,就可以看懂公司里的代碼、高效率地處理大量集合數據以及消滅“嵌套地獄”等等。
目錄一、Lambda表達式
Lambda表達式是java8最重要的新特性之一,與Stream API一起成為JDK1.8最主要的更新內容。
Lambda是一個匿名函數(表達式),可以將Lambda表達式理解為是一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。
這樣可以寫出更簡潔、更靈活的代碼。同時作為一種更緊湊的代碼風格,使java的語言表達能力得到了提升。
9.1基礎概念
-
首先,lambda表達式需要函數式介面的支持,lambda表達式的實現是基於函數式介面的。
-
lambda表達式的底層思維還是執行方法(函數),但lambda表達式會使得代碼更簡潔,利於程式員編寫。
-
Java8中引入了一個新的操作符“->”,該操作符成為箭頭操作符或者lambda操作符。該操作符將lambda表達式分為了左側和右側兩部分。
-
操作符左側:lambda表達式所需的參數列表,具體就是lambda表達式中介面抽象方法的參數列表;
-
操作符右側:lambda表達式所需執行的功能,即lambda體,也就是介面中抽象方法具體要實現的功能。
9.2語法格式
9.2.1格式一:抽象方法無參數、無返回值
/**
*語法格式一:抽象方法無參數、無返回值
* */
@Test
public void test_1(){
//Runnable介面無參數、無返回值,無參數直接使用()
Runnable r = () -> System.out.println("Hello,Lambda!");
r.run();
}
9.2.2格式二:抽象方法有1個參數,無返回值
/**
*語法格式二:抽象方法有1個參數,且無返回值
* */
@Test
public void test_2(){
//Consumer介面是JDK中一個有參、無返回的介面,作用是消費介面傳進來的泛型參數
Consumer<String> con = (t) -> System.out.println(t);
//accept()是該介面的抽象方法
con.accept("Hello Lambda!");
}
註:如該抽象方法的參數只有1個,則"->"的左側可以省略()不寫。
9.2.3格式三:抽象方法中有多個參數、有返回值,且lambda體中有多條語句
/**
* 語法格式三:抽象方法中有多個參數、有返回值,且lambda體中有多條語句
* */
@Test
public void test_3(){
//如果lambda體中有多條語句,則必須將語句寫在{};中
Comparator<Integer> com = (x,y) -> {
System.out.println("函數式介面");
return Integer.compare(x,y);
};
}
註:如該lambda體中只有一條語句,則{};和return都可以省略不寫。
9.2.4lambda表達式中參數列表的數據類型可以省略
/**
*語法格式四:lambda表達式中參數列表的數據類型可以省略,JVM可以根絕上下文進行推斷,這個過程稱為”類型推斷“。
* 本質上來說,由於lambda表達式基於函數式介面來實現,函數式介面中的抽象方法(T t)已經指定了泛型的數據類型。
**/
@Test
public void test_4(){
//參數列表中的Integer可以省略不寫
Comparator<Integer> c = (Integer x,Integer y) -> Integer.compare(x,y);
}
9.3lambda表達式的應用
9.3.1需求1
調用Collections.sort()方法,定製化排序比較兩個員工對象信息(第一比較年齡,年齡相同比較姓名),參數傳遞方式使用lambda表達式的形式。
//定義員工信息list
List<User> user_list = Arrays.asList(
new User("張三",21,"[email protected]"),
new User("李四",35,"[email protected]")
);
@Test
public void test_1(){
//Collections介面,使用lambda表達式
Collections.sort(user_list,(u1,u2) -> {
//lambda體中有多條語句
if (u1.getAge() == u2.getAge()){
return u1.getName().compareTo(u2.getName());
}else {
return -Integer.compare(u1.getAge(), u2.getAge());
}
});
for (User u : user_list){
System.out.println(u);
}
}
9.3.2需求2
a.聲明一個函數式介面,同時在該介面中聲明一個抽象方法 String getValue(String str);
b.聲明一個類TestLambda_3,類中編寫成員方法test_2,使用a中定義的介面作為該方法的參數,將一個字元串"lambda"轉換為大寫,並作為方法的返回值;
c.再將該字元串的第2和第4個索引位置的的字元進行字串截取。
/**
* 該成員方法的形參1表示的是需要被操作的字元串,形參2表示的是介面中的操作實現。
* */
public String transform(String str, MyPractice mp){
return mp.getValue(str);
}
@Test
public void test_2(){
String s = transform("lambda",(str) -> str.toUpperCase());
System.out.println(s);
//subString()方法截取規則:從數組下標的方式進行計算,含頭不含尾。
String ss = transform("lambda",(str -> str.substring(2,5)));
System.out.println(ss);
}
9.3.3需求3
a.聲明一個帶2個泛型的函數式介面,其中泛型類型為<T,R>且T為參數,R為返回值,同時在該介面中聲明對應的抽象方法;
b.在類TestLambda_3中聲明一個成員方法calculate()並使用a中的介面作為參數,輸出員工信息。
/**
* 該成員方法中形參1表示的是需要被操作的字元串,形參2表示的是介面中的操作實現。
* */
public List<User> calculate(MyPractice_2<List<User>> mp){
mp.calculateValues();
return user_list;
}
@Test
public List<User> test_3(){
List<User> list = calculate(() -> {
System.out.println(user_list);
return null;
});
return list;
}
二、函數式編程
在java中(尤其從java8開始),函數式介面的應用是函數式編程的一個典型實現。
基於以上對lambda表達式的認識,我們可以清楚地知道:lambda表達式的實現需要函數式介面的支持。
2.1函數式介面
函數式介面指的是:介面中只有一個抽象方法的介面,稱之為函數式介面。並且可以使用@FunctionnalInterface註解修飾,以此來判斷該介面是否是函數式介面。
在Java8以後,函數式介面中允許存在普通方法(即非抽象方法),使用default進行修飾。
2.2內置4大核心函數式介面
-
Cosumer
消費型介面; //抽象方法 void accept(T t);
-
Supploer< T> 供給型介面;
//抽象方法 T get();
-
Function< T> 函數型介面;
//抽象方法 R apply(T t);
-
Predicate< T>斷言式介面;
//抽象方法 boolean test(T t);
三、Stream流 API
Java8最為主要的更新內容是lambda表達式和Stream流API,關於lambda表達式在上面已經介紹過了,下麵就來看看今天的主角——Stream流 API(java.util.stream.*)。
3.1基本概念
- Stream API是java8中處理集合的關鍵抽象概念,它可以對指定的集合進行操作,如執行非常複雜的查找、過濾和映射數據等操作;
- 使用Stream API對集合數據進行操作,類似於使用SQL執行資料庫查詢的操作;
- Stream API在對數據源(集合或數組)進行一系列流水線式的操作後,最終會產生一個新的流。
註意:
- Stream本身不會存儲元素;
- Stream不會改變源對象,相反,Stream流執行完後會返回一個有結果的新Stream;
- Stream流的執行具有延遲性,只有當執行流的終止操作時(或者需要某些結果時),Stream才會執行。
簡而言之,Stream API提供了一種高效且易於使用的處理數據的方式。
3.2實現步驟
Stream流的操作可分為3個步驟:創建Stream、中間操作以及終止操作(結果)。
3.2.1步驟一:創建Stream
//可以通過Collection系列集合提供的stream(),或者parallelStream()
List<String> list_1 = new ArrayList<>();
Stream<String> stream_1 = list_1.stream();
//可以通過of()創建Stream的
Stream<String> stream_2 = Stream.of("aa","bb","cc");
//創建無限流
Stream<Integer> stream_3 = Stream.iterate(3,(x) -> x+2);
stream_3.limit(10).forEach(System.out::println);
3.2.2步驟二:中間操作
中間操作主要包括:篩選與切片、映射,查找和排序等。
- 篩選與切片、映射
/**
* 篩選與切片
* filter:接收Lambda,從流中排除某些元素;
* map:接收Lambda,將元素轉換為其它形式或者提取數據源的具體信息;(接收一個函數作為參數,該函數會應用到每個元素上,並將其映射成一個新的元素)
* limit(n):截斷流,使其元素不超過指定數量,即只取前n個元素;
* skip(n):跳過元素,返回n個後的元素;
* distinct:通過流生成元素的hashCode()和equals()去除重覆元素。
* */
@Test
public void test_1(){
List<Integer> a = Arrays.asList(1,2,3,4,5,6,7,8,9,9,9);
//創建Stream
a.stream()
//中間操作
.filter(i -> i%2 ==0 || i%3 ==0)
.limit(7)
.map(i -> i * i)
.distinct()
//終止操作
.forEach(System.out::println);
}
打開IDEA的調試模式,並點擊橫排圖標最右側的“Trace Current Stream Chain”就可追蹤到Stream流的一些中間操作,具體如圖3-1所示:
- 查找
/**
* 查找
* findFirst:查找流中的第一個元素
* findAny:查找流中的任意一個元素
* count:返迴流中元素的總個數
* */
@Test
public void testMatch(){
//比較過後獲取流中第一個元素,並放入Optional容器中
Optional<User> op = user_list.stream()
.sorted((e1,e2) -> -Double.compare(e1.getAge(), e2.getAge()))
.findFirst();
System.out.println(op.get());
//從流中取出任意一個符合條件的元素,並放入Optional容器中
Optional<User> op_2 = user_list.parallelStream()
.filter((e) -> e.getStatus().equals(User.Status.STATUS_0))
.findAny();
System.out.println(op_2.get());
//獲取流中元素的個數
Long count = user_list.stream()
//filter加以條件限制
.filter((e) -> e.getAge() > 30)
.count();
System.out.println(count);
//獲取最小的年齡(而不是最小年齡員工的信息)
Optional<Integer> op_3 = user_list.stream()
.map(User::getAge)
.min(Integer::compare);
System.out.println(op_3.get());
}
對於獲取源數據(如集合)中的具體某個元素,可以使用map()將所需信息提取出來,然後再進行比較操作。過程分析如下圖3-2所示:
- 排序
/**
* 排序
* 1、sorted:自然排序(Comparable);
* 2、sorted(Comparator com):定製排序。
* */
@Test
public void test_1(){
List<Integer> list = Arrays.asList(564,457,54,43,1,4,65);
list.stream()
.sorted()
.forEach(System.out::println);
user_list.stream()
.sorted((e1,e2) -> {
//年齡是否相同
if (e1.getAge().equals(e2.getAge())){
//年齡相同按照姓名比
return e1.getName().compareTo(e2.getName());
}else
return e1.getAge().compareTo(e2.getAge());
})
.forEach(System.out::println);
}
四、時間日期 API
Java8中更新了時間日期相關的API,主要包括時間日期轉換、時間校正器等。
4.1時間日期轉換
在實際開發中的時間日期轉換主要包括Date類型與String的互相轉換、Long類型時間轉換為String、Long類型時間轉換為Date。
4.1.1Date與String的互轉
- DateTimeFormatter類
/**
* 時間格式轉化:
* 1、獲取當前Date類型時間;
* 2、將該時間轉化為String類型;
* 3、返回(輸出)String類型的時間。
* */
@Test
public void test_1(){
//獲取當前時間
LocalDateTime ldt = LocalDateTime.now();
//指定需要轉化的格式
DateTimeFormatter simpleDateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//將當前時間按照指定格式,轉化為String類型輸出
String time = simpleDateFormat.format(ldt);
System.out.println(time);
}
- @JsonFormat註解
/**
* 該註解將String類型的數據按照指定格式返回,但其數據類型仍然還是String
* */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String activityStartTime;
4.1.2Long轉換為String(Date)
- getTime()方法、Calendar類、SimpleDateFormat類
/**
* 時間格式轉化:
* 1、獲取long類型的時間數(毫秒數);
* 2、將該時間轉化為String類型;
* 3、返回(輸出)String類型的時間。
* */
@Test
public void test_2(){
DateTest dateTest = new DateTest();
//getTime()方法獲取long類型時間數
Long ssTime = dateTest.getTestTime().getTime();
//聲明一個Calendar類的通用對象
Calendar calendar = Calendar.getInstance();
//將long類型時間Set為Date類型
calendar.setTimeInMillis(ssTime);
System.out.println(calendar);
//指定需要轉化的格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = simpleDateFormat.format(calendar.getTime());
System.out.println(time);
}