java8 Lambda表示式和Stream Api
兩個用於測試的類
員工:
public class Employee { private int id; private String name; private int age; private double salary; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public Employee(int id, String name, int age, double salary) { this.id = id; this.name = name; this.age = age; this.salary = salary; } public Employee() { } @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}'; } public Employee(int id, String name) { this.id = id; this.name = name; } public Employee(int id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; if (id != employee.id) return false; if (age != employee.age) return false; if (Double.compare(employee.salary, salary) != 0) return false; return name != null ? name.equals(employee.name) : employee.name == null; } @Override public int hashCode() { int result; long temp; result = id; result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + age; temp = Double.doubleToLongBits(salary); result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } }
/** * 提供用於測試的資料 * */ public class EmployeeData { public static List<Employee> getEmployees(){ List<Employee> list = new ArrayList<>(); list.add(new Employee(1001, "馬化騰", 34, 6000.38)); list.add(new Employee(1002, "馬雲馬", 12, 9876.12)); list.add(new Employee(1003, "劉強東", 33, 3000.82)); list.add(new Employee(1004, "雷軍", 26, 7657.37)); list.add(new Employee(1005, "李彥巨集", 65, 5555.32)); list.add(new Employee(1006, "比爾蓋茨", 42, 9500.43)); list.add(new Employee(1007, "任正非", 26, 4333.32)); list.add(new Employee(1008, "爾扎克伯格", 35, 2500.32)); return list; } }
一、Lambda表示式的基本語法。
1.格式: lambda形參列表 -> lambda體 2.說明: -> : lambda操作符 或箭頭操作符 ->左邊 :lambda表示式的形參列表 ->右邊:lambda表示式的執行語句,稱為lambda體
3.如何使用:分為六種情況
public class LambdaTest { //情況六:lambda體如果只有一條執行語句,可以省略這一對{}. //特別的,如果此唯一的一條執行語句是return,則除了省略一對{}之外,return關鍵字也可以省略 @Test public void test7(){ Comparator<Integer> com1 = (o1,o2) -> o1.compareTo(o2); int value = com1.compare(12, 34); System.out.println(value); } //情況五:lambda表示式的形參列表有兩個或兩個以上的變數,lambda體有多條執行語句,甚至有返回值 @Test public void test6(){ Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); // return Integer.compare(o1,o2); return o1.compareTo(o2); } }; int value = com1.compare(12, 34); System.out.println(value); System.out.println("******************"); Comparator<Integer> com2 = (o1,o2) -> { System.out.println(o1); System.out.println(o2); // return Integer.compare(o1,o2); return o1.compareTo(o2); }; int value1 = com2.compare(32, 12); System.out.println(value1); } //情況四:針對於情況三進行迭代。 //如果lambda表示式的形參列表只有一個變數,則可以省略一對() @Test public void test5(){ Consumer<String> con1 = s -> { System.out.println(s); }; con1.accept("你好我也好!"); } //以前在java程式中出現的型別推斷 @Test public void test4(){ //舉例1: int[] arr = new int[]{1,2,3,4}; //型別推斷 int[] arr1 = {1,2,3,4}; int[] arr2 ; arr2 = new int[]{1,2,3,4}; //舉例2: List<String> list = new ArrayList<String>(); //型別推斷 List<String> list1 = new ArrayList<>(); //舉例3 method(new HashMap<>()); } public void method(HashMap<String,Employee> map){ } //情況三:針對於情況二進行迭代。lambda形參列表的變數的資料型別可以省略 //說明:java編譯器可以根據上下文推斷出變數的資料型別,故可以省略:型別推斷 @Test public void test3(){ Consumer<String> con1 = (s) -> { System.out.println(s); }; con1.accept("你好我也好!"); } //情況二:lambda形參列表有一個引數,無返回值 @Test public void test2(){ Consumer<String> con = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con.accept("昨天過的挺好!"); System.out.println("*************"); Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("你好我也好!"); } //情況一:無形參,無返回值 @Test public void test1(){ Runnable r = new Runnable() { @Override public void run() { System.out.println("昨天是七夕情人節!"); } }; Thread t1 = new Thread(r); t1.start(); System.out.println("*************"); Runnable r1 = () -> { System.out.println("你們有沒有因為愛情而鼓掌呢?"); }; Thread t2 = new Thread(r1); t2.start(); } }
總結: 1)lambda形參列表:如果有形參的話,都可以省略變數的資料型別 ---型別推斷 如果形參列表只有一個形參,還可以省略一對() 2)lambda體:正常情況下,需要使用一對{}包起來所有的執行語句。 特別的,①lambda體如果只有一條執行語句,可以省略這一對{}. ②如果此唯一的一條執行語句是return,則除了省略一對{}之外,return關鍵字也可以省略
二、什麼是函式式介面
1.特點:如果一個介面中只有唯一的一個抽象方法,則此介面稱為函式式介面 2.可以在介面的宣告上使用@FunctionalInterface註解去校驗一個介面是否是函式式介面 3.lambda表示式的使用依賴於函式式介面 4.lambda表示式即為函式式介面的例項。
@FunctionalInterface
public interface MyFunction {
public String getValue(String str);
}
只要一個物件是函式式介面的例項,那麼該物件就可以用Lambda表示式來表示。 所以以前用匿名類表示的現在都可以用Lambda表示式來寫。
java8中關於Lambda表示式提供的4個基本的函式式介面
1.Consumer<T> :消費型介面 void accept(T t) 2.Supplier<T> : 供給型介面 T get() 3. Function<T,R> : 函式型介面 R apply(T t) 4. Predicate<T> : 斷定型介面 boolean test(T t)
public class LambdaTest1 {
//4. Predicate<T> : 斷定型介面
//boolean test(T t)
@Test
public void test4(){
List<String> list = Arrays.asList("北京","南京","東京","西京","普京","上海","深圳");
List<String> data = getStrings(list, s -> s.contains("京"));
System.out.println(data);
}
public List<String> getStrings(List<String> list, Predicate<String> pre){
List<String> data = new ArrayList<>();
for (String s : list) {
if(pre.test(s)){
data.add(s);
}
}
return data;
}
//3. Function<T,R> : 函式型介面
//R apply(T t)
@Test
public void test3(){
strHandler(" hel lo ",str -> str.trim());
strHandler("世界那麼大,我想去看看",str -> str.substring(2,5));
}
public void strHandler(String str, Function<String,String> func){
String s = func.apply(str);
System.out.println(s);
}
// 2.Supplier<T> : 供給型介面
// T get()
@Test
public void test2(){
List<Double> list = getRandomValue(10, () -> Math.random() * 100);
for (Double d : list) {
System.out.println(d);
}
}
public List<Double> getRandomValue(int num, Supplier<Double> sup){
List<Double> list = new ArrayList<>();
for(int i = 0;i < num;i++){
list.add(sup.get());
}
return list;
}
//1.Consumer<T> :消費型介面
//void accept(T t)
@Test
public void test1(){
happyNight(500, s -> {
System.out.println("學習很辛苦,累了的話,可以去正規的足浴店放鬆一下。花費:" + s);
});
}
}
總結:從方法的角度來說: 1.方法在呼叫時,如果發現方法的形參是一個函式型介面,那麼我們呼叫此方法時,可以使用lambda表示式 作為實參傳遞給此介面形參 2.方法定義時,如果需要一個定義介面,且此介面只有一個抽象方法,(說明此介面就是函式式介面),那麼 考慮是有有現成的函式式介面可用。如果有,則不需要我們再去定義。比如:FilterData 替換為Predicate
構造器引用
一、構造器引用 1.格式: 類名 :: new 2.要求:函式式介面中抽象方法的形參列表與構造器形參列表一致(型別相同,個數相同), 同時,抽象方法的返回值型別即為構造器所屬的類的型別。
public class ConstructorRefTest {
@Test
public void test4(){
Function<Integer,String[]> func1 = (length) -> new String[length];
String[] arr = func1.apply(10);
System.out.println(arr.length);
System.out.println("**********");
Function<Integer,String[]> func2 = String[]::new;
String[] arr1 = func2.apply(20);
System.out.println(arr1.length);
}
@Test
public void test3(){
BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
Employee emp = func1.apply(10, "Jim");
System.out.println(emp);
System.out.println("**********");
BiFunction<Integer,String,Employee> func2 = Employee::new;
Employee emp1 = func2.apply(20, "Jim");
System.out.println(emp1);
}
@Test
public void test2(){
Function<Integer,Employee> func1 = (id) -> new Employee(id);
Employee emp1 = func1.apply(12);
System.out.println(emp1);
System.out.println("**********");
Function<Integer,Employee> func2 = Employee::new;
Employee emp2 = func2.apply(12);
System.out.println(emp2);
}
@Test
public void test1(){
Supplier<Employee> sup = () -> new Employee();
Employee emp = sup.get();
System.out.println(emp);
System.out.println("**********");
Supplier<Employee> sup1 = Employee::new;
Employee emp1 = sup1.get();
System.out.println(emp1);
}
}
方法引用的使用
1.方法引用可以看做是Lambda表示式深層次的表達,或者可以理解為:方法引用就是Lambda表示式。 又因為Lambda表示式本身就是函式式介面的例項,進而方法引用也可以看做函式式介面的例項。 2.使用情境: 當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用! 3.要求:函式式介面中抽象方法的形參列表和返回值型別 要與 方法引用對應的方法的形參列表和返回值型別一致! 4.格式:類(或 物件) :: 方法名 5.分為如下的三種情況: ① 物件 :: 例項方法 ② 類 :: 靜態方法 ③ 類 :: 例項方法 (有難度) 6.在滿足如上的第2條d的情況下,使用方法引用替換Lambda表示式。如果方法引用不熟悉,可以使用Lambda表示式。
public class MethodRefTest {
//情況三:類 :: 例項方法 (有難度)
//注意:當函式式介面方法的第一個引數是需要引用方法的呼叫者,
//並且第二個引數是需要引用方法的引數(或無引數)時:ClassName::methodName
@Test
public void test7(){
Employee emp = new Employee(1001, "Jerry", 32, 23430);
Function<Employee,String> func1 = (e) -> e.getName();
String name = func1.apply(emp);
System.out.println(name);
System.out.println("***************");
Function<Employee,String> func2 = Employee::getName;
String name1 = func2.apply(emp);
System.out.println(name1);
}
@Test
public void test6(){
BiPredicate<String,String> bi = (s1,s2) -> s1.equals(s2);
boolean b = bi.test("abc", "abc");
System.out.println(b);
System.out.println("***************");
BiPredicate<String,String> bi1 = String::equals;
boolean b1 = bi1.test("abc", "abc");
System.out.println(b1);
}
@Test
public void test5(){
Comparator<String> com = (s1,s2) -> s1.compareTo(s2);
int value = com.compare("abad", "abdd");
System.out.println(value);
System.out.println("***************");
Comparator<String> com1 = String::compareTo;
int value1 = com1.compare("aaaaaa", "aa");
System.out.println(value1);
}
//情況二:類 :: 靜態方法
@Test
public void test4(){
Function<Double,Long> func1 = d -> Math.round(d);
Long value = func1.apply(12.3);
System.out.println(value);
System.out.println("***************");
Function<Double,Long> func2 = Math::round;
Long value1 = func2.apply(12.6);
System.out.println(value1);
Function<Long,Long> func3 = Math::abs;
Long value2 = func3.apply(-1234L);
System.out.println(value2);
}
@Test
public void test3(){
Comparator<Integer> com = (num1,num2) -> Integer.compare(num1,num2);
int value = com.compare(12, 32);
System.out.println(value);
System.out.println("***************");
Comparator<Integer> com1 = Integer::compare;
int value1 = com1.compare(43, 12);
System.out.println(value1);
}
//情況一:物件 :: 例項方法
@Test
public void test2(){
Employee emp = new Employee(1001, "Tom", 23, 4534);
Supplier<String> sup = () -> emp.getName();
String name = sup.get();
System.out.println(name);
System.out.println("***************");
Supplier<String> sup1 = emp::getName;
String name1 = sup1.get();
System.out.println(name1);
}
@Test
public void test1(){
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("beijing");
System.out.println("***************");
PrintStream ps = System.out;
Consumer<String> con2 = ps :: println;
con2.accept("shanghai");
}
}
Stream API
1.Stream API: 可以理解為java提供的一套api,使用這套api可以實現對集合、陣列中的資料進行過濾、對映、歸約、查詢等操作 2.注意點: ①Stream 自己不會儲存元素。 ②Stream 不會改變源物件。相反,他們會返回一個持有結果的新Stream。 ③Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。 3.Stream的使用流程: 步驟一:Stream的例項化 步驟二:一系列的中間操作 步驟三:終止操作 注意:①步驟二中的中間操作可以有多個 ②如果沒有終止操作,那麼一系列的中間操作是不會執行的。只有執行了步驟三的終止操作,步驟二才會執行:惰性求值 ③終止操作一旦執行,就不可以再執行中間操作或其他的終止操作。
步驟一:Stream的例項化
public class StreamAPITest {
//方式四:建立無限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);
}
//方式三:Stream的靜態方法of()
@Test
public void test3(){
//public static<T> Stream<T> of(T... values) : 返回一個流
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
}
//方式二:通過陣列
@Test
public void test2(){
//呼叫Arrays的static <T> Stream<T> stream(T[] array): 返回一個流
String[] arr = new String[]{"MM","GG","JJ","DD"};
Stream<String> stream = Arrays.stream(arr);
}
//方式一:通過集合
@Test
public void test1(){
// default Stream<E> stream() : 返回一個順序流
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> stream = list.stream();
// default Stream<E> parallelStream() : 返回一個並行流
Stream<Employee> stream1 = list.parallelStream();
}
}
步驟二:測試中間操作
1.可以通過Stream的例項,執行多次中間操作 2.中間操作,只有在執行了終止操作以後才會執行。
public class StreamAPITest1 {
//3-排序
@Test
public void test4(){
// sorted()——自然排序
List<Integer> list = Arrays.asList(23,43,454,32,1,2,5,5,-8);
list.stream().sorted().forEach(System.out::println);
//此時針對Employees進行排序:失敗。原因:Employee類沒有實現Comparable介面
// List<Employee> list1 = EmployeeData.getEmployees();
// list1.stream().sorted().forEach(System.out::println);
// sorted(Comparator com)——定製排序
List<Employee> list1 = EmployeeData.getEmployees();
list1.stream().sorted((e1,e2) -> {
if(e1.getAge() != e2.getAge()){
return e1.getAge() - e2.getAge();
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println);
}
@Test
public void test3(){
ArrayList list1 = new ArrayList();
list1.add(1);
list1.add(2);
list1.add(3);
ArrayList list2 = new ArrayList();
list2.add(4);
list2.add(5);
list2.add(6);
// list1.add(list2); ---map()
// System.out.println(list1);//[1, 2, 3, [4, 5, 6]]
// list1.addAll(list2); -- flatMap()
// System.out.println(list1);//[1, 2, 3, 4, 5, 6]
}
//2-對映
@Test
public void test2(){
// map(Function f)——接收一個函式作為引數,將元素轉換成其他形式或提取資訊,該函式會被應用到每個元素上,並將其對映成一個新的元素。
List<String> list = Arrays.asList("aa","bb","cc","dd");
list.stream().map(String::toUpperCase).forEach(System.out::println);
// 練習:獲取員工姓名長度大於3的員工的姓名。
Stream<Employee> stream = EmployeeData.getEmployees().stream();
Stream<String> stream1 = stream.map(Employee::getName);
stream1.filter(name -> name.length() > 3).forEach(System.out::println);
Stream<Stream<Character>> stream2 = list.stream().map(StreamAPITest1::fromStringToChar);
stream2.forEach(
x ->{
x.forEach(System.out::println);
}
);
System.out.println();
// flatMap(Function f)——接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。
Stream<Character> stream3 = list.stream().flatMap(StreamAPITest1::fromStringToChar);
stream3.forEach(System.out::println);
}
//將str中的字元存在集合中,返回集合的Stream
public static Stream<Character> fromStringToChar(String str){
ArrayList<Character> list = new ArrayList<>();
// for(Character c : str.toCharArray()){
// list.add(c);
// }
for(int i = 0;i < str.length();i++){
list.add(str.charAt(i));
}
return list.stream();
}
//1-篩選與切片
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
//體會方法鏈的呼叫方式。比如:StringBuffer s = new StringBuffer(); s.append("A").append("B");
// filter(Predicate p)——接收 Lambda , 從流中排除某些元素。
Stream<Employee> stream = list.stream().filter(e -> e.getAge() > 30);
stream.forEach(System.out::println);
System.out.println();
// limit(n)——截斷流,使其元素不超過給定數量。
list.stream().filter(e -> e.getAge() > 30).limit(3).forEach(System.out::println);
System.out.println();
// skip(n) —— 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補
list.stream().filter(e -> e.getAge() > 30).skip(3).forEach(System.out::println);
System.out.println();
// distinct()——篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素
list.add(new Employee(1009,"劉強東",30,6000));
list.add(new Employee(1009,"劉強東",30,6000));
list.add(new Employee(1009,"劉強東",30,6000));
list.add(new Employee(1009,"劉強東",30,6000));
list.add(new Employee(1009,"劉強東",30,6000));
list.stream().distinct().forEach(System.out::println);
}
}
步驟三:終止操作
public class StreamAPITest2 {
//3-收集:將集合--->Stream --->集合
@Test
public void test5(){
// collect(Collector c)——將流轉換為其他形式。接收一個 Collector介面的實現,
// 用於給Stream中元素做彙總的方法
List<Employee> list = EmployeeData.getEmployees();
List<Employee> list1 = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toList());
//遍歷
list1.forEach(System.out::println);
System.out.println();
Set<Employee> set = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println();
ArrayList<Employee> list2 = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toCollection(ArrayList::new));
for (Employee employee : list2) {
System.out.println(employee);
}
}
//2-歸約
@Test
public void test4(){
// reduce(T identity, BinaryOperator)——可以將流中元素反覆結合起來,得到一個值。返回 T
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
// Integer sum = list.stream().reduce(0, (x1, x2) -> x1 + x2);
Integer sum = list.stream().reduce(10, Integer::sum);
System.out.println(sum);
// reduce(BinaryOperator) ——可以將流中元素反覆結合起來,得到一個值。返回 Optional<T>
// 練習1:計算公司所有員工工資的總和
List<Employee> emps = EmployeeData.getEmployees();
Stream<Double> moneyStream = emps.stream().map(Employee::getSalary);
Optional<Double> moneyOptional = moneyStream.reduce(Double::sum);
System.out.println(moneyOptional.get());
// 練習2:員工姓名中包含“馬”字的個數
Stream<String> nameStream = emps.stream().map(Employee::getName);
Stream<Character> charStream = nameStream.flatMap(StreamAPITest1::fromStringToChar);
//方式一:
// long count = charStream.filter(c -> c.equals('馬')).count();
// System.out.println(count);
//方式二:
Optional<Integer> op = charStream.map(c -> {
if (c.equals('馬')) {
return 1;
} else {
return 0;
}
}).reduce(Integer::sum);
System.out.println(op.get());
//練習3:員工姓名中包含“馬”的員工個數
long count = emps.stream().map(Employee::getName).filter(name -> name.contains("馬")).count();
System.out.println(count);
// 練習4:員工姓名中包含“馬”的員工的姓名
emps.stream().map(Employee::getName).filter(name -> name.contains("馬")).forEach(System.out::println);
}
//1-匹配與查詢
@Test
public void test2(){
List<Employee> list = EmployeeData.getEmployees();
// max(Comparator c)——返回流中最大值
// 練習:返回最高的工資:
Stream<Employee> stream = list.stream();
Stream<Double> stream1 = stream.map(Employee::getSalary);
Optional<Double> max = stream1.max(Double::compare);
System.out.println(max.get());
// min(Comparator c)——返回流中最小值
// 練習:返回最低工資的員工
Stream<Employee> stream2 = list.stream();
Optional<Employee> min = stream2.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get());
// forEach(Consumer c)——內部迭代
list.stream().forEach(System.out::println);
}
//外部迭代
@Test
public void test3(){
List<Employee> list = EmployeeData.getEmployees();
Iterator<Employee> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
// allMatch(Predicate p)——檢查是否匹配所有元素
//是否所有的員工的年齡都大於18
boolean b = list.stream().allMatch(e -> e.getAge() > 18);
System.out.println(b);
// anyMatch(Predicate p)——檢查是否至少匹配一個元素
//是否存在員工的工資大於 10000
boolean b1 = list.stream().anyMatch(e -> e.getSalary() > 9900);
System.out.println(b1);
// noneMatch(Predicate p)——檢查是否沒有匹配的元素
//是否存在員工姓“雷”
boolean b2 = list.stream().noneMatch(e -> e.getName().contains("雷"));
System.out.println(b2);
// findFirst——返回第一個元素
Optional<Employee> emp = list.stream().sorted((e1,e2) -> {
if(e1.getAge() != e2.getAge()){
return e1.getAge() - e2.getAge();
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).findFirst();
System.out.println(emp.get());
// findAny——返回當前流中的任意元素
Optional<Employee> emp1 = list.parallelStream().findAny();
System.out.println(emp1.get());
// count——返回流中元素的總個數
long count = list.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
}
}