Java8的新特性(一)
主要內容
- Lambda表示式
- 函式式介面
- 方法引用和構造器引用
- Stream API
- 介面中預設的方法與靜態方法
- 新時間API
Java8新特性的簡介
- 速度更快
- 程式碼更少(增加了新的語法Lambda表示式)
- 強大的Stream API
- 便於並行
- 最大化減少空指標的異常(Optional)
– 其中最重要的就是Lambda表示式和Stream API
1-Lambda表示式
為什麼要使用Lambda表示式
Lambda是一個匿名函式,我們可以把Lambda表示式理解為一段可以傳遞的程式碼(將程式碼像資料一樣可以進行傳遞)。可以寫出靈活、更簡潔的程式碼。作為一種更緊湊的程式碼風格,使Java的語言表達能力得到提升。
Java8新特性的初體驗:利用Lambda表示式可以解決匿名內部類的問題;
- 原來的比較兩個數的大小可以這樣來寫:
/**
* 原來的匿名內部類
*/
@Test
public void test1() {
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
TreeSet<Integer> ts = new TreeSet<>(com);
}
上面的程式碼實際上最關鍵的程式碼就只有一句:
Integer.compare(o1,o2);
現在有了Lambda表示式就可以這樣來寫:
/**
* Java8的Lambda表示式就可以解決匿名內部類的問題
*/
@Test
public void test2() {
Comparator< Integer> com = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> ts = new TreeSet<>(com);
}
- 我們還可以再舉一個栗子:
- 之前我們寫上一個類去實現Runnable介面,然後重寫run方法,我們可以用匿名內部類去寫:
程式碼如下:
- 之前我們寫上一個類去實現Runnable介面,然後重寫run方法,我們可以用匿名內部類去寫:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
現在我們有了Lambda表示式了,我們就可以這樣來寫,非常的簡便:
Runnable runnable = () -> System.out.println("Hello World");
我們還可以舉一個栗子:
- 我們經常會遇到這樣的需求:獲取當前公司中員工年齡大於35的員工的資訊
原來我們可以這樣來寫:
List<Employee> employees = Arrays.asList(
new Employee("張三", 18, 9999.99),
new Employee("李四", 38, 5555.55),
new Employee("王五", 50, 6666.66),
new Employee("趙六", 16, 3333.33),
new Employee("田七", 8, 7777.77)
);
/** 需求:獲取當前公司中員工年齡大於35的員工的資訊 */
@Test
public List<Employee> filterEmployees(List<Employee> list) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : list) {
if (emp.getAge() >= 35) {
emps.add(emp);
}
}
return emps;
}
我們對上面的程式碼進行單元測試,如下圖,我們已經對當前公司中員工年齡大於35的員工的資訊進行了過濾:
假如這個時候,又來了一個需求:獲取當前公司員工工資大於5000的員工的資訊,這個時候,我又得重新寫個方法,其實我們對比一下方法,要改動的地方就一句,因為這一句又得重新寫上一個方法,非常的麻煩;
我們心裡的第一想法就是對這個方法進行優化,我們可以利用一些設計模式來對其進行優化;
- 優化方式一:利用策略設計模式來進行優化
我們可以寫上一個介面:
public interface MyPredicate<T> {
public boolean test(T t);
}
我們寫上一個類來實現這個介面,如果員工的年齡大於35的話,我們就返回true:
public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean test(Employee emp) {
return emp.getAge()>=35;
}
}
我們就可以這樣來用了:
public List<Employee> filterEmployees(List<Employee> list,MyPredicate<Employee> mp) {
List<Employee> emps = new ArrayList<>();
for (Employee emp : list) {
if (mp.test(emp)) {
emps.add(emp);
}
}
return emps;
}
然後,我們就可以對其進行測試了:
這個時候,我們對介面的實現類進行擴充套件就可以了,給它什麼策略進行過濾,就可以按照什麼方式來進行過濾;但是這種方式不好的地方就是:每次實現一個策略的時候,還要單獨的寫上一個類;
- 優化方式二:利用匿名內部類,利用匿名內部類來實現介面的方式來進行實現
這個時候,又回到了匿名內部類的程式碼冗餘的問題。 - 優化方式三:Lambda表示式
程式碼如下:
List<Employee> list = filterEmployees(this.employees, (e) -> e.getSalary() <= 5000)
list.forEach(System.out::println);
測試結果如下圖:
- 優化方式四:利用Stream API
程式碼如下:
/** 僅僅就是對上面資料進行過濾,其他的什麼之前的介面都沒有 */
@Test
public void test7() {
employees.stream()
.filter((e) -> e.getSalary()>=5000)
.forEach(System.out::println);
}
測試結果如下:
如果我們只想取這四條資料的前面兩條:我們還可以這樣來寫:
我們還可以舉一個常見的操作的栗子:把員工資訊裡面的所有的員工的名字給提取出了,我們就可以這樣來寫:
/** 把員工資訊裡面的所有的名字給提取出來 */
@Test
public void test8() {
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
效果如下:
Lambda基礎語法
一、Lambda表示式的基本語法:
Java8中引入了一個新的操作符"->"該操作符也稱為箭頭操作符或Lambda操作符;箭頭操作符將Lambda表示式拆分成兩部分;
- 左側:Lambda表示式的引數列表;
- 右側:Lambda表示式所需要執行的功能:即Lambda體;
- 語法格式一:無參,無返回值,Lambda體只需要一條語句
() -> System.out.println("Hello World!");
舉例如下:這是兩種不同的實現方式:
@Test
public void test1() {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
runnable.run();
Runnable runnable1 = () -> System.out.println("Hello World!");
runnable1.run();
}
有一個小的注意事項:在區域性內部類中,引用了一個同級別的區域性變數時,在Jdk1.7的時候是會報錯的,但在Jdk1.8是可以的,實際上它是省略了final關鍵字,預設給我們加上了:
- 語法格式二:有一個引數,並且無返回值
(x) -> System.out.println(x);
舉例,程式碼如下:
@Test
public void test2() {
Consumer<String> con = (x) -> System.out.println(x);
con.accept("我愛Java");
}
分析如下:對介面中抽象方法的實現
3. 語法格式三:若只有一個引數的話,小括號可以省略不寫,但是一般我們通常都給它寫上。
x -> System.out.println(x);
舉例程式碼如下:
@Test
public void test3() {
Consumer<String> con = x -> System.out.println(x);
con.accept("我愛Java");
}
- 語法格式四:有兩個以上的引數,有返回值,並且Lambda體中有多條語句,如果有多條語句,那麼大括號是不能省略的;
@Test
public void test4() {
Comparator<Integer> com = (x,y)->{
System.out.println("函式式介面");
return Integer.compare(x, y);
};
}
- 語法格式五:若Lambda體中只有一條語句,return和大括號都可以省略不寫
@Test
public void test5() {
Comparator<Integer> com = (x,y)->Integer.compare(x, y);
}
- 語法格式六:Lambda表示式的引數列表的資料型別可以省略不寫,因為JVM編譯器可以通過上下文推斷出資料型別,我們稱為型別推斷;
- 這裡的引數列表的資料型別是可以不寫的:
@Test
public void test6() {
Comparator<Integer> com = (Integer x,Integer y)->Integer.compare(x, y);
}
總結:
(1)左右遇一括號省:"->“左邊只有一個引數時,括號可以省;”->"右邊只有一條語句時,大括號可以省;
(2)左側推斷型別省:左側的引數列表裡面的引數可以根據上下文進行型別的推斷,可以省略資料型別不寫;
二、Lambda表示式需要函式式介面的支援:
函式式介面:介面中只有一個抽象方法的介面,稱為函式式介面。可以使用一個註解:@FunctionalInterface來進行修飾一下,可以檢查當前的介面是否為函式式介面;
簡單使用一下:
需求:對一個數進行運算
1)定義一個介面:
@FunctionalInterface
public interface MyFunction {
public Integer getValue(Integer num);
}
2)定義一個方法:傳入一個數和一個介面,介面在呼叫的時候利用Lambda進行實現:
public Integer operation(Integer num, MyFunction myFun) {
return myFun.getValue(num);
}
3)利用Lambda表示式對介面進行實現,並且重寫裡面的方法:
@Test
public void test(){
Integer num = operation(100, x -> x * x);
System.out.println(num);
}
如果我們想要進行加法的運算,我們就可以這樣來寫:
@Test
public void test9(){
System.out.println(operation(200, (y)->y+200));
}