Java8中的 lambda 和Stream API

家裡那隻橘貓發表於2019-02-19

前言

​ 由於專案中用到了比較多有關於 Java8 中新的東西,一開始自己只是會寫,但是寫起來不太順,然後就在網上找到了一個很好的關於Java8新特性的視訊,所以就進行了學習了一下,以下是自己對 lambda 表示式和 Stream API 的筆記和相應的理解。 視訊地址,有興趣的可以自行觀看。

Java8 新特性

  1. 速度更快 更換了資料結構,記憶體結構(JVM)
  2. *程式碼更少了(Lambda表示式) *
  3. *強大的Stream API *
  4. 便於並行 fork join (序列切換並行)
  5. 最大化減少空指標異常 Optional

Lambda表示式

​ 在說 Lambda 之前,首先要說的是函式式介面。這個可以說是為了 Lambda 表示式而存在的一個東西。那麼什麼是函式式介面?

函式式介面

定義: 介面中只有一個抽象介面。 像 java.util.function 下的所有介面都是函式式介面。Java1.8提供@FunctionalInterface檢測一個介面是否是一個函式式介面。 eg: java.util.function 包下的 Consumer 介面程式碼

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

// jdk 1.8 介面可以有預設實現
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
複製程式碼

瞭解了什麼是函式式介面後,lambda 表示式就很好理解了。

​ "->" 是 lambda 表示式的符號 左側表示函式式介面中抽象方法的引數列表,右側表示你對這個方法的實現。 舉個例子eg:

public class Test{
	public static void main(String[] args){
		 Consumer consumer = x-> System.out.println(x);
         consumer.accept(1);
	}
}
複製程式碼

輸出 1;

四大函式式介面

我們一般對函式式介面的使用的時候,都會對其進行封裝。

消費性介面

​ Consumer 只有一個抽象方法名為 accept,引數列表只有一個泛型t,無返回值。引數的資料型別有類決定 eg:

/**
 * @ClassName ConsumerTest
 * @Description 消費型介面, 消費字串欄位 列印輸出
 * @Author ouyangkang
 * @Date 2019-02-18 15:46
 **/
public class ConsumerTest {

    public static void main(String[] args) {
        test("hello",x-> System.out.println(x));
    }

    public static <T> void test(T t, Consumer<T> consumer) {
        consumer.accept(t);
    }
}
複製程式碼

輸出:hello ​ 如果需要多個引數列表的話,也可以在 java.util.function 包下找到相應的函式式介面 比如 ObjLongConsumer。其他的可以自行檢視

供給型介面

Supplier 只有一個抽象方法名為 get,引數列表為空,有返回值,返回值得資料型別為T。

/**
 * @ClassName SupplerTest
 * @Description 供給型介面 字串拼接
 * @Author ouyangkang
 * @Date 2019-02-18 15:53
 **/
public class SupplerTest {

    public static void main(String[] args) {
        String hello = test("hello ", () -> "word!");
        System.out.println(hello);
    }

    public static String test(String str,Supplier<String> supplier){
        return str + supplier.get();
    }
}
複製程式碼

輸出為:hello word! ​ 如果需要返回得資料為基本資料型別,可以在 java.util.function 包下找到相應的函式式介面 比如:getAsLong 其他的可以自行檢視

函式型介面

​ Function<T, R> 只有一個抽象方法名為 apply,引數列表只有一個引數為T,有返回值,返回值的資料型別為R。

/**
 * @ClassName FunctionTest
 * @Description 函式式介面 將字串轉換成大寫的
 * @Author ouyangkang
 * @Date 2019-02-18 16:01
 **/
public class FunctionTest {


    public static void main(String[] args) {
        String test = test("hello", x -> x.toUpperCase());
        System.out.println(test);
    }


    public static String test(String str , Function<String,String> function){
        return function.apply(str);
    }
}

複製程式碼

輸出為:HELLO ​ 如果需要多個入參,然後又返回值的話,可以在 java.util.function 包下找到相應的函式式介面 比如 BiFunction。其他的可以自行檢視

斷言型介面

​ 斷言型又名判斷型。 Predicate 只有一個抽象方法名為 test,引數列表只有一個引數為 T,有返回值,返回值型別為 boolean。

/**
 * @ClassName PredicateTest
 * @Description 斷言型介面,判斷字串大小是否大於6
 * @Author ouyangkang
 * @Date 2019-02-18 16:16
 **/
public class PredicateTest {
    public static void main(String[] args) {
        boolean hello = test("hello", x -> x.length() > 6);
        System.out.println(hello);
    }
    public static boolean test(String str, Predicate<String> predicate){
        return predicate.test(str);
    }
}
複製程式碼

輸出為: false

Stream API

​ Stream 作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念。Stream中間操作,多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發了終止操作,否則中間不會執行任何處理!而終止操作時會一次性全部處理,稱為惰性處理。要進行流操作首先要獲取流。有4中方法可以獲取流。

  1. 獲取流 通過集合系列提供的stream方法和 parallelStream()(並行流)方法獲取流
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        // 常用獲取流的方式
        Stream<Integer> stream = list.stream();
    }
複製程式碼
  1. 通過Arrays.stream() 將陣列轉換成流
    public static void main(String[] args) {
        int[] a = new int[]{1,2,3,4};
        IntStream stream = Arrays.stream(a);
        
    }
複製程式碼
  1. 通過Stream.of今天方法建立流
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3);
    }
複製程式碼
  1. 建立無限流
    public static void main(String[] args) {
        Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
    }
複製程式碼

所有的對流的操作可以分為4種,分別為篩選與分片,對映,排序,終結(歸約,收集)

篩選與分片

操作有filter,distant,limit,skip。 filter : 過濾操作,方法引數為斷言型介面 eg:

 public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3);
        stream.filter(x->x != 2).forEach(x-> System.out.println(x));
    }
複製程式碼

輸出:

1
3
複製程式碼

distinct : 去重操作,方法無引數 limit : 獲取前幾條資料,方法引數為long skip : 跳過前多少條資料,然後獲取後面所有的。 方法引數為long

對映

常用操作有 map ,flatMap。 map: 對原資料進行處理,並返回處理後的資料。 方法引數為函式型介面。 eg:

  public static void main(String[] args) {
       Stream<Integer> stream = Stream.of(1, 2, 3);
       stream.map(x->x*2).forEach(System.out::println);
    }
複製程式碼

輸出:

2
4
6
複製程式碼

flatMap : 使原來流種的原有資料一個一個整合在另一個流中。方法引數為函式型介面,但是返回值為流。 eg:

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c");
        List<String> list2 = Arrays.asList("f","d");
        list.stream().flatMap(x->list2.stream().map(y-> x + y)).forEach(System.out::println);
    }
複製程式碼

排序

常用操作有sort自然排序,合sort引數為排序器的定製排序 自然排序eg:

public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3);
        stream.sorted().forEach(System.out::println);
    }
複製程式碼

輸出:

1
2
3
複製程式碼

定製排序

    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3);
        stream.sorted((x,y)->-Integer.compare(x,y)).forEach(System.out::println);
    }
複製程式碼

輸出:

3
2
1
複製程式碼

終止操作

  • allMatch 檢查是否匹配所有元素 方法引數為斷言型介面
  • anyMatch 檢查是否匹配所有元素 方法引數為斷言型介面
  • noneMatch 檢查是否沒有匹配所有元素 方法引數為斷言型介面
  • findFirst 返回第一個元素 無方法引數
  • findAny 返回當前流的任意元素 無方法引數
  • count 返回流中的元素總個數 無方法引數
  • max 返回流的最大值 無方法引數
  • min 返回流中的最小值 無方法引數

歸約

reduce : 歸約 -- 可以將流中的元素反覆結合起來,得到一個值。 eg:

 public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer reduce = list1.stream().reduce(11, (x, y) -> x + y);
        System.out.println(reduce);
    }
複製程式碼

輸出 : 66

收集

​ 這個是非常常用的一個操作。 將流裝換為其他形式。接收到一個Collector介面的實現,用於給Stream中的元素彙總的方法。用collect方法進行收集。方法引數為Collector。Collector可以由Collectors中的toList(),toSet(),toMap(Function(T,R) key,Function(T,R) value)等靜態方法實現。

  • toList() 返回一個 Collector ,它將輸入元素 List到一個新的 List 。
  • toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) 返回一個 Collector ,它將元素累加到一個 Map ,其鍵和值是將所提供的對映函式應用於輸入元素的結果。
  • toSet() 返回一個 Collector ,將輸入元素 Set到一個新的 Set 。 eg:

User類

@Data
@ToString
public class User {
    private String name;

    private Integer age;

    private Integer salary;
}
複製程式碼
   public static void main(String[] args) {
         List<User> users = Arrays.asList(new User("張三", 19, 1000),
                new User("張三", 58, 2000),
                new User("李四", 38, 3000),
                new User("趙五", 48, 4000)
        );
        List<String> collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());
        Set<String> collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());
        Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));
        System.out.println(collect);
        System.out.println(collect1);
        System.out.println(collect2);
    }
複製程式碼

輸出:

[張三, 張三, 李四, 趙五]
[李四, 張三, 趙五]
{48=趙五, 19=張三, 38=李四, 58=張三}
複製程式碼

分組

​ Collectors.groupingBy()方法是 返回 Collector “由基團”上的型別的輸入元件操作實現 T ,根據分類功能分組元素。這個是非常常用的操作。 比如你要對名字相同的進行分組。 groupingBy(Function<? super T,? extends K> classifier) eg:

    public static void main(String[] args) {
        List<User> users = Arrays.asList(new User("張三", 19, 1000),
                new User("張三", 58, 2000),
                new User("李四", 38, 3000),
                new User("趙五", 48, 4000)
        );
        Map<String, List<User>> collect3 = users.stream().collect(Collectors.groupingBy(x -> x.getName()));
        System.out.println(collect3);
    }
複製程式碼

輸出:{李四=[User{name='李四', age=38, salary=3000}], 張三=[User{name='張三', age=19, salary=1000}, User{name='張三', age=58, salary=2000}], 趙五=[User{name='趙五', age=48, salary=4000}]}

當然還有其他的一些比較複雜的分組操作,實際程式碼看業務來進行實現。

總結

​ java8中的lambda表示式可能一開始用的時候還不是很熟悉,但是隻要熟悉了,你會發現非常的好用,而且lambda表示式結合stream API可以進行編寫自己的工具類。在平常專案中可以非常的省時間,提高寫程式碼的效率。我現在給出一個List轉Map的工具類。

public class CollectionStream {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(new User("張三", 19, 1000),
                new User("張三", 58, 2000),
                new User("李四", 38, 3000),
                new User("趙五", 48, 4000)
        );
        Map<Integer, Integer> map = listToMap(users, x -> x.getAge(), x -> x.getSalary());
        System.out.println(map);
    }

    /**
     * @Author ouyangkang
     * @Description list 轉map key不能相同 ,如果相同會報錯。 方法對 源資料,key,value過濾null。
     * @Date 9:27 2019/2/19
     * @param source 源資料
     * @param key
     * @param value
     * @return java.util.Map<K,V>
    **/
    public static <DTO, K, V> Map<K, V> listToMap(List<DTO> source, Function<? super DTO, ? extends K> key, Function<? super DTO, ? extends V> value) {
        Objects.requireNonNull(source, "source not null");
        Objects.requireNonNull(key, "key not null");
        Objects.requireNonNull(value, "value not null");
        Map<K, V> map = source.stream()
                .filter(dto -> dto != null)
                .filter(dto -> key.apply(dto) != null)
                .filter(dto -> value.apply(dto) != null)
                .collect(Collectors.toMap(key, value));
        return map;
    }
}
複製程式碼

相關文章