### Java 8 的改進 - 速度更快 - 代碼更少(**Lambda表達式**) - 引入強大的**Stream API** - 便於並行 - 最大化減少空指針異常(**Optional**) - **Nashorn**引擎,允許在JVM上運行**js**應用 - **並行流**就是把一個內容 ...
Java 8 的改進
- 速度更快
- 代碼更少(Lambda表達式)
- 引入強大的Stream API
- 便於並行
- 最大化減少空指針異常(Optional)
- Nashorn引擎,允許在JVM上運行js應用
- 並行流就是把一個內容分成多個數據塊,並用不同的線程分別處理每個數據塊的流。相比較串列的流,並行的流可以很大程度上提高程式的執行效率。
一、Lambda 表達式
- Lambda表達式的重要特征
- 可選用類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
- 可選用的參數圓括弧:一個參數無需定義圓括弧,但多個參數需要定義圓括弧。
- 可選的大括弧:如果主體包含了一個語句,就不需要使用大括弧。
- 可選的返回關鍵字:如果主體只有一個表達式,返回值則編譯器會自動返回值,大括弧需要指定表達式返回了一個數值。(即return…)
- Lambda表達式是對象,而不是函數。
- Lambda表達式基本語法
舉例:
(o1,o2) -> Integer.compare(o1,o2);
格式:
->
:lambda 操作符 或 箭頭操作符->
左邊:lambda 形參列表 (其實就是介面中的抽象方法的形參列表)->
右邊:lambda 體(其實就是重寫的抽象方法的方法體)
- 舉例
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 類型聲明
MathOperation addition = (int a, int b) -> a + b;
// 不用類型聲明
MathOperation subtraction = (a, b) -> a - b;
// 大括弧中的返回語句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 沒有大括弧及返回語句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));//10 + 5 = 15
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));//10 - 5 = 5
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));//10 x 5 = 50
System.out.println("10 / 5 = " + tester.operate(10, 5, division));//10 / 5 = 2
// 不用括弧
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括弧
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");//Hello Runoob
greetService2.sayMessage("Google");//Hello Google
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
- 變數作用域
- lambda 表達式只能引用標記了 final 的外層局部變數,即不能在 lambda 內部修改定義在域外的局部變數,否則會編譯錯誤。
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));//訪問外層的局部變數num
s.convert(2); // 輸出結果為 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
- lambda 表達式的局部變數可以不用聲明為 final,但是必須不可被後面的代碼修改(即隱性的具有 final 的語義)
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//報錯信息:Local variable num defined in an enclosing scope must be final or effectively final
-
在 Lambda 表達式當中不允許聲明一個與局部變數同名的參數或者局部變數。
String first = ""; Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //編譯會出錯
- Lambda表達式的本質:作為函數式介面的實例(關於函數式介面的概念在下一點)
二、函數式介面
- 什麼是函數式介面
如果一個介面中,只聲明瞭一個抽象方法,則此介面就稱為函數式介面。在介面中加上@FunctionalInterface註解可以用於檢驗該介面是否為一個函數式介面。
- 四大核心函數式介面
public class LambdaTest3 {
//消費型介面 Consumer<T> void accept(T t)
@Test
public void test1() {
//未使用Lambda表達式
Learn("java", new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("學習什麼? " + s);
}
});
System.out.println("====================");
//使用Lambda表達
Learn("html", s -> System.out.println("學習什麼? " + s));
}
private void Learn(String s, Consumer<String> stringConsumer) {
stringConsumer.accept(s);
}
//供給型介面 Supplier<T> T get()
@Test
public void test2() {
//未使用Lambda表達式
Supplier<String> sp = new Supplier<String>() {
@Override
public String get() {
return new String("我能提供東西");
}
};
System.out.println(sp.get());
System.out.println("====================");
//使用Lambda表達
Supplier<String> sp1 = () -> new String("我能通過lambda提供東西");
System.out.println(sp1.get());
}
//函數型介面 Function<T,R> R apply(T t)
@Test
public void test3() {
//使用Lambda表達式
Employee employee = new Employee(1001, "Tom", 45, 10000);
Function<Employee, String> func1 =e->e.getName();
System.out.println(func1.apply(employee));
System.out.println("====================");
//使用方法引用
Function<Employee,String> func2 = Employee::getName;
System.out.println(func2.apply(employee));
}
//斷定型介面 Predicate<T> boolean test(T t)
@Test
public void test4() {
//使用匿名內部類
Function<Double, Long> func = new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
System.out.println(func.apply(10.5));
System.out.println("====================");
//使用Lambda表達式
Function<Double, Long> func1 = d -> Math.round(d);
System.out.println(func1.apply(12.3));
System.out.println("====================");
//使用方法引用
Function<Double,Long>func2 = Math::round;
System.out.println(func2.apply(12.6));
}
}
- 其他函數式介面
三、方法引用
-
方法引用就是Lambda表達式,也就是函數式介面的一個實例,通過方法的名字來指向一個方法。(Lambda表達式深層次的表達)
-
當要傳遞給Lambda體的操作,已經有實現的方法了,就可以使用方法引用。
-
4種不同方法的引用
現有一個Car類如下:
@FunctionalInterface public interface Supplier<T> { T get(); } class Car { //Supplier是jdk1.8的介面,這裡和lamda一起使用了 public static Car create(final Supplier<Car> supplier) { //靜態方法 return supplier.get(); } public static void collide(final Car car) { //靜態方法 System.out.println("Collided " + car.toString()); } public void follow(final Car another) { //非靜態方法 System.out.println("Following the " + another.toString()); } public void repair() { //非靜態方法 System.out.println("Repaired " + this.toString()); } }
構造器引用:Class::new
final Car car = Car.create( Car::new ); final List< Car > cars = Arrays.asList( car );
靜態方法引用:Class::static_method
cars.forEach( Car::collide );
特定類的任意對象的方法引用:Class::method
cars.forEach( Car::repair );
特定對象的方法引用:instance::method
final Car police = Car.create( Car::new ); cars.forEach( police::follow );
-
數組引用
public void test4() { Function<Integer, String[]> func1 = length -> new String[length]; String[] arr1 = func1.apply(5); System.out.println(Arrays.toString(arr1)); System.out.println("===================="); //使用方法引用 Function<Integer,String[]> func2 = String[]::new; //數組引用 String[] arr2 = func2.apply(10); System.out.println(Arrays.toString(arr2)); }
三、Stream API
-
Java8中有兩大最為重要的改變。第一個是Lambda 表達式;另外一個則是Stream API。
-
Stream API ( java.util.stream)把真正的函數式編程風格引入到Java中。這是目前為止對Java類庫最好的補充,因為Stream API可以極大提供Java程式員的生產力,讓程式員寫出高效率、乾凈、簡潔的代碼。
-
Stream 是Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射數據等操作。使用Stream API 對集合數據進行操作,就類似於使用SQL 執行的資料庫查詢。也可以使用Stream API 來並行執行操作。簡言之,Stream API 提供了一種高效且易於使用的處理數據的方式。
-
為什麼要使用Stream API:實際開發中,項目中多數數據源都來自於Mysql,Oracle等。但現在數據源可以更多了,有MongDB,Radis等,而這些NoSQL的數據就需要Java層面去處理。
-
Stream 和Collection 集合的區別:Collection 是一種靜態的記憶體數據結構,而Stream 是有關計算的。前者是主要面向記憶體,存儲在記憶體中,後者主要是面向CPU,通過CPU 實現計算。
-
註意
- Stream自己不會存儲元素
- Stream不會改變源對象。相反,他們會返回一個持有結果的新Stream。
- Stream操作是延遲執行的。
-
Stream的操作三個步驟
Stream的實例化:一個數據源(如集合、數組),獲取一個流
//創建Stream的方式一:通過集合 public void test1(){ List<Employee> employees = EmployeeData.getEmployees(); //default Stream<E> stream():返回一個順序流 Stream<Employee> stream = employees.stream(); //default Stream<E> parallelStream():返回一個並行流 Stream<Employee> parallelStream = employees.parallelStream(); } //創建Stream方式二:通過數組 public void test2(){ int[] arr = new int[]{1, 2, 3, 4, 5, 6}; //調用Arrays類的static<T> Stream<T> stream(T[] array):返回一個流 IntStream stream = Arrays.stream(arr);//int數組 Employee e1 = new Employee(1001, "Tom"); Employee e2 = new Employee(1002, "Jerry"); Employee[] arr1 = new Employee[]{e1, e2}; Stream<Employee> stream2 = Arrays.stream(arr1);//Employee類數組 } //創建Stream方式三:通過Stream的of()函數 public void test3(){ Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);//這裡的1~6不是int類型數據,而是一個包裝類 } //創建Stream方式四:創建無限流 public void test4(){ //①迭代 //public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) //遍歷前10個偶數 Stream.iterate(0, t->t+2).limit(10).forEach(System.out::println); //②生成 //public static<T> Stream<T> generate(Supplier<T> s) //輸出10個隨機數 Stream.generate(Math::random).limit(10).forEach(System.out::println); }
- 順序流與並行流的區別:順序流的集合元素時按存入順序儲存的,取出時按順序取出;並行流的集合元素是並行存儲的,取出時是隨機的。
一系列的中間操作:一個中間操作鏈,對數據源的數據進行處理。多個中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為“惰性求值”。
2.1 篩選與切片
public void test1(){ List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> employeeStream = employees.stream(); //filter(Predicate p)——接收 Lambda , 從流中排除某些元素。 employeeStream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);//查詢員工表中薪資大於7000的員工信息 System.out.println(); //limit(n)——截斷流,使其元素不超過給定數量。 employees.stream().limit(3).forEach(System.out::println);//因為上一條代碼中的Stream已經執行了終止操作,所以不能使用【employeeStream.limit(3).forEach(System.out::println)】,而是要重新創建一個Stream System.out.println(); //skip(n) —— 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補 employees.stream().skip(3).forEach(System.out::println); System.out.println(); //distinct()——篩選,通過流所生成元素的 hashCode() 和 equals() 去除重覆元素 employees.add(new Employee(1010,"劉慶東",56,8000)); employees.add(new Employee(1010,"劉慶東",56,8000)); employees.add(new Employee(1010,"劉慶東",56,8000)); employees.add(new Employee(1010,"劉慶東",56,8000)); employees.stream().distinct().forEach(System.out::println); }
2.2 映射
public void test2(){ List<String> list = Arrays.asList("aa", "bb", "cc", "dd"); //map(Function f)——接收一個函數作為參數,將元素轉換成其他形式或提取信息,該函數會被應用到每個元素上,並將其映射成一個新的元素。 list.stream().map(str -> str.toUpperCase()).forEach(System.out::println); //練習1:獲取員工姓名長度大於3的員工的姓名。 List<Employee> employees = EmployeeData.getEmployees(); Stream<String> nameStream = employees.stream().map(Employee::getName); nameStream.filter(name -> name.length() >3).forEach(System.out::println); System.out.println(); //練習2:使用map()中間操作實現flatMap()中間操作方法 Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream); streamStream.forEach(s ->{ s.forEach(System.out::println); }); System.out.println(); //flatMap(Function f)——接收一個函數作為參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流。 Stream<Character> characterStream = list.stream().flatMap(StreamAPITest2::fromStringToStream); characterStream.forEach(System.out::println); //