1. 程式人生 > 其它 >Redis 利用 incr 和 expire 來限流, 併發導致過期時間失效問題

Redis 利用 incr 和 expire 來限流, 併發導致過期時間失效問題

Java8 新特性

1 lambda表示式

1.1 概述

lambda是JDK8中的一個語法糖。他可以對某些匿名內部類的寫法進行簡化。它是函數語言程式設計思想的一個重要體現。讓我們不用關注是什麼物件。而是更關注我們對資料進行了什麼操作。

1.2 核心原則

可推導可省略

1.3 函式式介面

函式式介面(functional interface 也叫功能性介面,其實是同一個東西)。簡單來說,函式式介面是隻包含一個抽象方法的介面。比如Java標準庫中的java.lang.Runnable和 java.util.Comparator都是典型的函式式介面。
java 8提供 @FunctionalInterface作為註解,這個註解是非必須的,只要介面符合函式式介面的標準(即只包含一個抽象方法的介面),虛擬機器會自動判斷, 但 最好在介面上使用註解@FunctionalInterface進行宣告,以免團隊的其他人員錯誤地往介面中新增新的方法。

Java中的lambda無法單獨出現,它需要一個函式式介面來盛放,lambda表示式方法體其實就是函式介面的實現.

只要介面只定義了一個抽象方法,那它就是一個函式式介面,還有在上述Java Api中都有個@FunctionalInterface註解,這表示著該介面會設計成一個函式式介面,不符合規範的話,就會編譯報錯。

jdk1.8新特性 : 介面中可以有普通方法(非靜態方法)和靜態方法 , jdk1.7和1.7之前 介面中只能有共有常量和抽象方法的概念。

1.3 基本格式

(引數列表)->{程式碼}
public class Lambda_01 {
    public static void main(String[] args) {
        //一個介面Runnable,一個方法run
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新執行緒中方法被執行了");
            }
        }).start();
    }

}

**例1 ** 我們在建立執行緒並啟動時可以使用匿名內部類的寫法:

簡化後(不關注類,不關注方法名,只關注引數列表,方法體)

public class Lambda_01 {
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("新執行緒中方法被執行了");
        }).start();
    }

}

只有函式式接口才能用到這種寫法

一個lambda表示式就是一個函式式介面的例項

什麼是函式式介面,只有一個抽象方法的介面

點選回車,自動優化為lambda

例2

現有方法定義如下,其中IntPredicate是一個介面。先使用匿名內部類的寫法呼叫該方法。

public class Lambda_01 {
    public static void main(String[] args) {
        printNum(new IntPredicate() {
            @Override
            public boolean test(int value) {
                return value%2==0;
            }
        });
    }

    public static void printNum(IntPredicate predicate){
        int []arr = {1,2,3,4,5,6,7,8,9,10};
        for (int i : arr) {
            if(predicate.test(i)){
                System.out.print(i+" ");
            }
        }
    }

}

簡化後

public class Lambda_01 {
    public static void main(String[] args) {
        printNum((int value) -> {
            return value%2==0;
        });
    }

    public static void printNum(IntPredicate predicate){
        int []arr = {1,2,3,4,5,6,7,8,9,10};
        for (int i : arr) {
            if(predicate.test(i)){
                System.out.print(i+" ");
            }
        }
    }

}

1.4 省略規則

  • 引數型別可以省略
  • 方法體只有一句程式碼時,大括號,return和唯一一句程式碼的分號可以省略
  • 方法只有一個引數時,小括號可以省略
  • 以上這些規則都記不住也可以省略不記
public static void main(String[] args) {
    printNum((int value) -> {
        return value%2==0;
    });
}

省略後:
public static void main(String[] args) {
printNum(value -> value%2==0);
}

2 Stream流

2.1 概述

Java8的Stream使用的是函數語言程式設計模式,如同它的名字一樣,它可以被用來對集合或陣列進行鏈式流式的操作。可以更方便的讓我們對集合或陣列操作。

2.2 案例

專案結構

Author類

public class Author {
    private Long id;
    private String name;
    private Integer age;
    private String intro;
    private List<Book> books;
    
        @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Author author = (Author) o;
        return Objects.equals(id, author.id) &&
                Objects.equals(name, author.name) &&
                Objects.equals(age, author.age) &&
                Objects.equals(intro, author.intro) &&
                Objects.equals(books, author.books);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, intro, books);
    }
}

Book類

public class Book {
    private Long id;
    private String name;
    private String category;
    private Integer score;
    private String intro;
        @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return Objects.equals(id, book.id) &&
                Objects.equals(name, book.name) &&
                Objects.equals(category, book.category) &&
                Objects.equals(score, book.score) &&
                Objects.equals(intro, book.intro);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, category, score, intro);
    }
}

StreamDemo測試類

public class StreamDemo {

    public static void main(String[] args) {

        List<Author> authors = getAuthors();
        System.out.println(authors);
    }



    private static List<Author> getAuthors(){
        //資料初始化
        Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
        Author author2 = new Author(2L, "亞拉索", 15, "狂風追不上思考速度", null);
        Author author3 = new Author(3L, "易", 14, "是這個世界在限制他的思維", null);
        Author author4 = new Author(3L, "易", 14, "是這個世界在限制他的思維", null);

        //書籍列表
        ArrayList<Book> books1 = new ArrayList<>();
        ArrayList<Book> books2 = new ArrayList<>();
        ArrayList<Book> books3 = new ArrayList<>();

        books1.add(new Book(1L,"刀的兩側","哲學,愛情",88,"用一把刀劃分了愛恨"));
        books1.add(new Book(2L,"一個人不能死在","個人成長,愛情",99,"講述瞭如何從失敗"));

        books2.add(new Book(3L,"那風吹不到","哲學",85,"帶你用思維去"));
        books2.add(new Book(3L,"那風吹不到","哲學",85,"帶你用思維去"));
        books2.add(new Book(4L,"吹或不吹","愛情,個人傳記",56,"一個哲學家的戀愛"));

        books3.add(new Book(5L,"你的劍就是","愛情",56,"無法想象一個武者"));
        books3.add(new Book(6L,"風與劍","個人傳記",100,"兩個哲學家的靈魂"));
        books3.add(new Book(6L,"風與劍","個人傳記",100,"兩個哲學家的靈魂"));

        author.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
        author4.setBooks(books3);

        List<Author> authorList = new ArrayList<>(Arrays.asList(author, author2, author3, author4));
        return authorList;

    }
}
public static void main(String[] args) {

    List<Author> authors = getAuthors();
    /*現在需要列印所有年齡小於18的作家的名字,並且要注意去重。*/
    authors.stream()//把集合轉換成流
           .distinct()
            .filter(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getAge()<18; //把返回true的元素留在流中
                }
            })
            .forEach(new Consumer<Author>() {
                @Override
                public void accept(Author author) {
                    System.out.println(author.getName());
                }
            });
}

lambda簡化

public static void main(String[] args) {

    List<Author> authors = getAuthors();
    /*現在需要列印所有年齡小於18的作家的名字,並且要注意去重。*/
    authors.stream()//把集合轉換成流
           .distinct()
            .filter(author -> {
                return author.getAge()<18; //把返回true的元素留在流中
            })
            .forEach(author -> System.out.println(author.getName()));
}

斷點除錯

3 常用操作

3.1 建立流

單列集合:集合物件.stream()

List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();

陣列:Arrays.stream(陣列)或者使用Stream.of來建立

Integer []arr = {1,2,3,4,5}
Stream<Integer> stream1 = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);

雙列集合:轉換成單列集合後再建立

Map<String, Integer> map = new HashMap<>();
map.put("蠟筆小新",19);
map.put("黑子",17);
map.put("日向翔陽",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
stream.filter(entry -> entry.getValue()>16)
      .forEach(entry -> System.out.println(entry.getKey()+"->"+entry.getValue()));

3.2 中間操作

filter

可以對流中的元素進行條件過濾,符合過濾條件的才能留在流中。

/*現在需要列印所有年齡小於18的作家的名字,並且要注意去重。*/
authors.stream()//把集合轉換成流
       .distinct()
        .filter(author -> {
            return author.getAge()<18; //把返回true的元素留在流中
        })
        .forEach(author -> System.out.println(author.getName()));

map

可以把對流中的元素進行計算或轉換。

例如:列印所有作家的姓名

private static void test02() {
    List<Author> authors = getAuthors();
    authors.stream()
            .map(new Function<Author, String>() {
                @Override
                public String apply(Author author) {
                    return author.getName();
                }
            })
            .forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            });
}

優化後

private static void test02() {
    List<Author> authors = getAuthors();
    authors.stream()
            .map(author -> author.getName())
            .forEach(s -> System.out.println(s));
}
//年齡加10
private static void test02() {
    List<Author> authors = getAuthors();
    authors.stream()
        .map(author -> author.getAge())
        .map(age -> age+10)
        .forEach(age -> System.out.println(age));
}

distinct

可以去除流中的重複元素。

注意:distinct方法是依賴Object的equals方法來判斷是否是相同物件的。所以需要注意重寫equals方法。

private static void test02() {
    List<Author> authors = getAuthors();
    authors.stream()
        .distinct()
        .forEach(author -> System.out.println(author.getName()));
}

sorted

可以對流中的元素進行排序。

注意:如果呼叫空參的sorted()方法,需要流中的元素是實現了Comparable。

private static void test02() {
    List<Author> authors = getAuthors();
    authors.stream()
            .distinct()
            .sorted()
            .forEach(author -> System.out.println(author.getAge()));
}
public class Author implements Comparable<Author>{
    private Long id;
    private String name;
    private Integer age;
    private String intro;
    private List<Book> books;
    
    @Override
    public int compareTo(Author o) {
        return this.getAge()-o.getAge();
    }
}

sorted有引數

private static void test02() {
    List<Author> authors = getAuthors();
    //對流中的元素按照年齡進行降序排序,並且要求不能有重複的元素。
    authors.stream()
            .distinct()
            .sorted(new Comparator<Author>() {
                @Override
                public int compare(Author o1, Author o2) {
                    return o1.getAge() - o2.getAge();
                }
            })
            .forEach(author -> System.out.println(author.getAge()));
}

簡化

private static void test02() {
    List<Author> authors = getAuthors();
    //對流中的元素按照年齡進行升序排序,並且要求不能有重複的元素。
    authors.stream()
            .distinct()
            .sorted((o1, o2) -> o1.getAge() - o2.getAge())
            .forEach(author -> System.out.println(author.getAge()));
}

limit

可以設定流的最大長度,超出的部分將被拋棄。

private static void test02() {
    List<Author> authors = getAuthors();
    //對流中的元素按照年齡進行升序排序,並且要求不能有重複的元素,然後列印其中年齡最小的兩個作家的姓名。
    authors.stream()
            .distinct()
            .sorted((o1, o2) -> o1.getAge() - o2.getAge())
            .limit(2)
            .forEach(author -> System.out.println(author.getName()));
}

skip

跳過流中的前n個元素,返回剩下的元素

private static void test02() {
    List<Author> authors = getAuthors();
    //列印除了年齡最小的作家外的其他作家,要求不能有重複元素,並且按照年齡升序排序。
    authors.stream()
            .distinct()
            .sorted((o1, o2) -> o1.getAge() - o2.getAge())
            .skip(1)
            .forEach(author -> System.out.println(author.getName()));
}

flatMap

map只能把一個物件轉換成另一個物件來作為流中的元素。而flatMap可以把一個物件轉換成多個物件作為流中的元素。

private static void test02() {
    //列印所有書籍的名字。要求對重複的元素進行去重。
    List<Author> authors = getAuthors();
    authors.stream()
        .flatMap(new Function<Author, Stream<Book>>() {
            @Override
            public Stream<Book> apply(Author author) {
                //自動對所有作家返回的所有書籍進行拼接
                return author.getBooks().stream();
            }
        })
        .distinct()
        .forEach(new Consumer<Book>() {
            @Override
            public void accept(Book book) {
                System.out.println(book.getName());
            }
        });
}

優化

private static void test02() {
    //列印所有書籍的名字。要求對重複的元素進行去重。
    List<Author> authors = getAuthors();
    authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .forEach(book -> System.out.println(book.getName()));
}

深刻理解

private static void test02() {
    //列印現有資料的所有分類。要求對分類進行去重。不能出現這個格式:哲學,愛情 (就是把兩個這種轉換成單個,分隔開來)
    List<Author> authors = getAuthors();
    authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
            .distinct()
            .forEach(category -> System.out.println(category));
}

3.3 終結操作

foreach

對流中的元素進行遍歷操作,我們通過傳入的引數去指定對遍歷的元素進行什麼具體操作。

private static void test02() {
    //輸出所有作家的名字
    List<Author> authors = getAuthors();
    authors.stream()
            .map(author -> author.getName())
            .distinct()
            .forEach(name -> System.out.println(name));
}

count

可以用來獲取當前流中元素的個數。

private static void test02() {
    //列印這些作家的所出書籍的數目,注意刪除重複元素。
    List<Author> authors = getAuthors();
    long count = authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .count();
    System.out.println(count);

}

max&min

可以用來獲取流中的最值。

private static void test02() {
    //分別獲取這些作家的所出書籍的最高分和最低分並列印
    List<Author> authors = getAuthors();
    Optional<Integer> max = authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .map(book -> book.getScore())
            .max(((o1, o2) -> o1 - o2));
    System.out.println(max.get());
    
    Optional<Integer> min = authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .map(book -> book.getScore())
            .min(((o1, o2) -> o1 - o2));
    System.out.println(min.get());

}

collect

把當前流轉換成一個集合。

private static void test02() {
    //獲取一個存放所有作者名字的List集合。
    List<Author> authors = getAuthors();
    List<String> nameList = authors.stream()
            .map(author -> author.getName())
            .collect(Collectors.toList());
    System.out.println(nameList);

}
private static void test02() {
    //獲取一個所有書名的Set集合
    List<Author> authors = getAuthors();
    Set<String> bookNames = authors.stream()
        .flatMap(author -> author.getBooks().stream())
        .map(book -> book.getName())
        .collect(Collectors.toSet());
    System.out.println(bookNames);

}
private static void test02() {
    //獲取一個Map集合,map的key為作者名,value為List<Book>
    List<Author> authors = getAuthors();
    Map<String, List<Book>> map = authors.stream()
            .distinct()
            .collect(Collectors.toMap(new Function<Author, String>( ) {
                @Override
                public String apply(Author author) {
                    return author.getName();
                }
            }, new Function<Author, List<Book>>() {
                @Override
                public List<Book> apply(Author author) {
                    return author.getBooks();
                }
            }));
}

優化

private static void test02() {
    //獲取一個Map集合,map的key為作者名,value為List<Book>
    List<Author> authors = getAuthors();
    Map<String, List<Book>> map = authors.stream()
        	.distinct()
            .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
    System.out.println(map);
}

查詢與匹配:

anyMatch

可以用來判斷是否有任意符合匹配條件的元素,結果為boolean型別。(只要有一個滿足條件就會返回true)

private static void test02() {
    //判斷是否有年齡29以上的作家
    List<Author> authors = getAuthors();
    boolean flag = authors.stream()
            .anyMatch(author -> author.getAge() > 29);
    System.out.println(flag);
}

allMatch

可以用來判斷是否都符合匹配條件,結果為boolean型別。如果都符合結果為true,否則結果為false。

private static void test02() {
    //判斷是否所有的作家都是成年人
    List<Author> authors = getAuthors();
    boolean flag = authors.stream()
            .allMatch(author -> author.getAge() >= 18);
    System.out.println(flag);
}

noneMatch

可以判斷流中的元素是否都不符合匹配條件。如果都不符合結果為true,否則結果為false

private static void test02() {
    //判斷作家是否都沒有超過100歲的。
    List<Author> authors = getAuthors();
    boolean flag = authors.stream()
            .noneMatch(author -> author.getAge() >100);
    System.out.println(flag);
}

findAny

獲取流中的任意一個元素。該方法沒有辦法保證獲取的一定是流中的第一個元素。

private static void test02() {
    //獲取任意一個大於18的作家,如果存在就輸出他的名字
    List<Author> authors = getAuthors();
    Optional<Author> optionalAuthor = authors.stream()
            .filter(author -> author.getAge() > 18)
            .findAny();

    optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
}

findFirst

獲取流中的第一個元素。

private static void test02() {
    //獲取一個年齡最小的作家,並輸出他的姓名。
    List<Author> authors = getAuthors();
    Optional<Author> first = authors.stream()
            .sorted((o1, o2) -> o1.getAge() - o2.getAge())
            .findFirst();
    first.ifPresent(author -> System.out.println(author.getName()));

}

reduce歸併

對流中的資料按照你指定的計算方式計算出一個結果。(縮減操作)

reduce的作用是把stream中的元素給組合起來,我們可以傳入一個初始值,它會按照我們的計算方式依次拿流中的元素和初始化值進行計算,計算結果再和後面的元素計算。

reduce兩個引數內部的計算方式如下:

T result = identity
for(T element : this stream)
    result = accumulator.apply(result,element)
return result;

其中identity就是我們可以通過方法引數傳入的初始值,accumulator的apply具體進行什麼計算也是我們通過方法引數來確定的。

private static void test02() {
    //使用reduce求所有作者年齡的和
    List<Author> authors = getAuthors();
    Integer sum = authors.stream()
            .map(author -> author.getAge())
            .reduce(0, new BinaryOperator<Integer>() {
                @Override
                public Integer apply(Integer result, Integer element) {
                    return result + element;
                }
            });
    System.out.println(sum);
}

優化

private static void test02() {
    //使用reduce求所有作者年齡的和
    List<Author> authors = getAuthors();
    Integer sum = authors.stream()
            .map(author -> author.getAge())
            .reduce(0, (result, element) -> result + element);
    System.out.println(sum);
}
private static void test02() {
    //使用reduce求所有作者中年齡的最大值
    List<Author> authors = getAuthors();
    Integer max = authors.stream()
            .map(author -> author.getAge())
            .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
    System.out.println(max);

}
private static void test02() {
    //使用reduce求所有作者中年齡的最小值
    List<Author> authors = getAuthors();
    Integer min = authors.stream()
            .map(author -> author.getAge())
            .reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
    System.out.println(min);
}

reduce一個引數的過載形式內部的計算

boolean foundAny = false;
     T result = null;
     for (T element : this stream) {
         if (!foundAny) {
             foundAny = true;
             result = element;
         }
         else
             result = accumulator.apply(result, element);
     }
     return foundAny ? Optional.of(result) : Optional.empty();
private static void test02() {
    //使用reduce求所有作者中年齡的最小值
    List<Author> authors = getAuthors();
    Optional<Integer> minOptional = authors.stream()
            .map(author -> author.getAge())
            .reduce(new BinaryOperator<Integer>() {
                @Override
                public Integer apply(Integer result, Integer element) {
                    return result > element ? element : result;
                }
            });
    minOptional.ifPresent(age -> System.out.println(age));
}

優化後

private static void test02() {
    //使用reduce求所有作者中年齡的最小值
    List<Author> authors = getAuthors();
    Optional<Integer> minOptional = authors.stream()
            .map(author -> author.getAge())
            .reduce((result, element) -> result > element ? element : result);
    minOptional.ifPresent(age -> System.out.println(age));
}

3.4 注意事項

  • 惰性求值(如果沒有終結操作,只有中間操作是不會得到執行的)
  • 流是一次性的(一旦一個流物件經過一個終結操作後。這個流就不能再被使用)
private static void test02() {
    List<Author> authors = getAuthors();
    Stream<Author> stream = authors.stream();

    stream.map(author -> author.getName())
           .forEach(name -> System.out.println(name));

    stream.map(author -> author.getName())
            .forEach(name -> System.out.println(name));
}
  • 不會影響原資料(我們在流中可以多資料做很多處理。但是正常情況下是不會影響原來集合中的元素的。這往往也是我們期望的)

4 Optional

4.1 概述

我們在編寫程式碼的時候出現最多的就是空指標異常。所以在很多情況下我們需要做各種非空的判斷。

例如:

Author author = getAuthor();
if(author != null){
    System.out.println(author.getName());
}

尤其是物件中的屬性還是一個物件的情況下。這種判斷會更多。

而過多的判斷語句會讓我們的程式碼顯得臃腫不堪。

所以在JDK8中引入了Optional,養成使用Optional的習慣後你可以寫出更優雅的程式碼來避免空指標異常。

並且在很多函數語言程式設計相關的API中也都用到了Optional,如果不會使用Optional也會對函數語言程式設計的學習造成影響。

4.2 使用

建立物件

Optional就好像是包裝類,可以把我們的具體資料封裝Optional物件內部。然後我們去使用Optional中封裝好的方法操作封裝進去的資料就可以非常優雅的避免空指標異常。

我們一般使用Optional的靜態方式ofNullable來把資料封裝成一個Optional物件。無論傳入的引數是否為null都不會出現問題。

Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullabel(author);

你可能會覺得還要加一行程式碼來封裝資料比較麻煩。但是如果改造下getAuthor方法,讓其的返回值就是封裝好的Optional的話,我們在使用時就會方便很多。

而且在實際開發中我們的資料很多是從資料庫獲取的。Mybatis從3.5版本也已經支援Optional了。我們可以直接把dao方法的返回值型別定義成Optional型別,Mybatis會自己把資料封裝成Optional物件返回。封裝的過程也不需要我們自己操作。

public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();
    authorOptional.ifPresent(author -> System.out.println(author.getName()));

}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
    return Optional.ofNullable(author);
}

如果你確定一個物件不是空的則可以使用Optional的靜態方法of來把資料封裝成Optional物件。

Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);

但是一定要注意,如果使用of的時候傳入的引數必須不為null。

如果一個方法的返回值型別是Optional型別。而如果我們經判斷髮現某次計算得到的返回值為null,這個時候就需要把null封裝成Optional物件返回。這時則可以使用Optional的靜態方法empty來進行封裝。

Optional.empty()

安全消費值

使用ifPresent方法消費其中的值。

這個方法判斷封裝的資料是否為空,不為空時才會執行具體的消費程式碼。

Optional<Author> authorOptional = getAuthorOptional();
authorOptional.ifPresent(author -> System.out.println(author.getName()));

獲取值

如果我們想獲取值自己進行處理可以使用get方法獲取,但是不推薦。因為當Optional內部的資料為空的時候會出現異常。

安全獲取值

如果我們期望安全的獲取值。我們不推薦使用get方法,而是使用Optional提供的以下方法。

  • ofElseGet

獲取資料並且設定資料為空時的預設值。如果資料不為空就能獲取到該資料。如果為空則根據你傳入的引數來建立物件作為預設值返回。

public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();
    //如果沒有值就返回new Author()
    Author author = authorOptional.orElseGet(() -> new Author(1L, "feng", 33, "", null));
    System.out.println(author.getName());
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
    return Optional.ofNullable(null);
}
  • orElseThrow

獲取資料,如果資料不為空就能獲取到該資料。如果為空則根據你傳入的引數來建立異常丟擲。

public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();
    Author author = authorOptional.orElseThrow(() -> new RuntimeException("資料為null"));
    System.out.println(author);
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
    return Optional.ofNullable(null);
}

過濾

我們可以使用filter方法對資料進行過濾。如果原本是有資料的,但是不符合判斷,也會變成一個無資料的Optional物件。

private static void testFilter() {
    Optional<Author> authorOptional = getAuthorOptional();
    authorOptional.filter(author -> author.getAge() > 88)
        .ifPresent(author -> System.out.println(author.getName()));
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
    return Optional.ofNullable(author);
}

判斷

我們可以使用isPresent方法進行是否存在資料的判斷。如果為空返回值為false,如果不為空,返回值為true。但是這種方式並不能體現Optional的好處,更推薦使用ifPresent方法。

private static void testFilter() {
    Optional<Author> authorOptional = getAuthorOptional();
    if (authorOptional.isPresent()) {
        System.out.println(authorOptional.get().getName());
    }
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
    return Optional.ofNullable(author);
}

資料轉換

Optional還提供了map可以讓我們對資料進行轉換,並且轉換得到的資料也還是被Optional包裝好的,保證了我們的使用安全。

例如我們想獲取作家的書籍集合。

private static void testFilter() {
    Optional<Author> authorOptional = getAuthorOptional();
    authorOptional.map(author -> author.getBooks())//返回的是List<Book>
                  .ifPresent(books -> System.out.println(books));
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一個從菜刀明悟祖安人", null);
    return Optional.ofNullable(author);
}

5 函式式介面

5.1 概述

只有一個抽象方法的介面我們稱之為函式介面。

JDK的函式式介面都加上了@FunctionalInterface註釋進行標識。但是無論是否加上該註解只要介面中只有一個抽象方法,都是函式式介面。

5.2 常見的函式式介面

  • Consumer消費介面

根據其中抽象方法的引數列表和返回值型別知道,我們可以在方法中對傳入的引數進行消費

  • Function計算轉換介面

根據其中抽象方法的引數列表和返回值型別知道,我們可以在方法中對傳入的引數計算或轉換,把結果返回

  • Predicate 判斷介面

根據其中抽象方法的引數列表和返回值型別知道,我們可以在方法中對傳入的引數條件判斷,返回判斷結果

  • Supplier生產型介面

根據其中抽象方法的引數列表和返回值型別知道,我們可以在方法中建立物件,把建立好的物件返回

5.3 常用的預設方法

  • and

我們在使用Predicate介面時候可能需要進行判斷條件的拼接。而and方法相當於是使用&&來拼接兩個判斷條件

例如:

列印作家中年齡大於17並且姓名的長度大於1的作家。

private static void testFilter() {
   //列印作家中年齡大於17並且姓名的長度大於1的作家
    List<Author> authors = getAuthors();
    authors.stream()
            .filter(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getAge()>17;
                }
            }.and(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getName().length()>1;
                }
            })).forEach(author -> System.out.println(author.getName()+"-"+author.getAge()));
}
  • or

我們在使用Predicate介面時候可能需要進行判斷條件的拼接。而aor方法相當於是使用||來拼接兩個判斷條件

例如:

列印作家中年齡大於17或者姓名的長度小於2的作家。

private static void testFilter() {
   //列印作家中年齡大於17或者姓名的長度小於2的作家。
    List<Author> authors = getAuthors();
    authors.stream()
            .filter(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getAge()>17;
                }
            }.or(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getName().length()<2;
                }
            }))
            .forEach(author -> System.out.println(author.getName()));
}
  • negate

Predicate介面中的方法。negate方法相當於是在判斷新增前面加了個!表示取反

例如:

列印作家中年齡不大於17的作家

private static void testFilter() {
   //列印作家中年齡不大於17的作家
    List<Author> authors = getAuthors();
    authors.stream()
            .filter(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getAge()>17;
                }
            }.negate())
            .forEach(author -> System.out.println(author.getAge()));

}

6 方法引用

我們在使用lambda時,如果方法體中只有一個方法的呼叫的話(包括構造方法),我們可以用方法引用進一步簡化程式碼。

6.1 推薦用法

我們在使用lambda時不需要考慮什麼時候用方法引用,用哪種方法引用,方法引用的格式是什麼。我們只需要在寫完lambda方法發現方法體只有一行程式碼,並且是方法的呼叫時使用快捷鍵嘗試是否能夠轉換成方法引用即可。

當我們方法引用使用的多了慢慢的也可以直接寫出方法引用。

6.2 基本格式

類名或者物件名::方法名

6.3 語法詳解(瞭解)

引用靜態方法

其實就是引用類的靜態方法

格式

類名::方法名

使用前提

如果我們在重寫方法的時候,方法體中只有一行程式碼,並且這行程式碼是呼叫了某個類的靜態方法,並且我們把要重寫的抽象方法中所有的引數都按照順序傳入了這個靜態方法中,這個時候我們就可以引用類的靜態方法。

private static void testFilter() {
    List<Author> authors = getAuthors();
    Stream<Author> authorStream = authors.stream();
    authorStream.map(author -> author.getAge())
            .map(age -> String.valueOf(age));

}

注意,如果我們所重寫的方法是沒有引數的,呼叫的方法也是沒有引數的也相當於符合以上規則。

優化後如下:

private static void testFilter() {
    List<Author> authors = getAuthors();
    Stream<Author> authorStream = authors.stream();
    authorStream.map(author -> author.getAge())
            .map(String::valueOf);

}

引用物件的例項方法

格式

物件名::方法名

使用前提

如果我們在重寫方法的時候,方法體中只有一行程式碼,並且這行程式碼是呼叫了某個物件的成員方法,並且我們把要重寫的抽象方法中所有的引數都按照順序傳入了這個成員方法中,這個時候我們就可以引用物件的例項方法。

private static void testFilter() {
    List<Author> authors = getAuthors();
    Stream<Author> authorStream = authors.stream();
    StringBuilder sb = new StringBuilder();
    authorStream.map(author -> author.getName())
                .forEach(name -> sb.append(name));
}

優化後

private static void testFilter() {
    List<Author> authors = getAuthors();
    Stream<Author> authorStream = authors.stream();
    StringBuilder sb = new StringBuilder();
    authorStream.map(author -> author.getName())
                .forEach(sb::append);
}

引用類的例項方法

格式

類名::方法名

使用前提

如果我們在重寫方法的時候,方法體中只有一行程式碼,並且這行程式碼是呼叫了第一個引數的成員方法,並且我們把要重寫的抽象方法中剩餘的所有的引數都按照順序傳入了這個成員方法中,這個時候我們就可以引用類的例項方法。

public static void main(String[] args) {
    subAuthorName("fengpeng", new UseString() {
        @Override
        public String use(String str, int start, int length) {
            return str.substring(start,length);
        }
    });
}
interface UseString{
    String use(String str,int start,int length);
}
public static String subAuthorName(String str,UseString useString){
    int start = 0;
    int length = 1;
    return useString.use(str,start,length);
}

優化後

public static void main(String[] args) {
    subAuthorName("fengpeng", String::substring);
}

構造器引用

如果方法體中的一行程式碼是構造器的話就可以使用構造器引用。

格式

類名::new

使用前提

如果我們在重寫方法的時候,方法體中只有一行程式碼,並且這行程式碼是呼叫了某個類的構造方法,並且我們把要重寫的抽象方法中的所有的引數都按照順序傳入了這個構造方法中,這個時候我們就可以引用構造器。

例如:

private static void testFilter() {
    List<Author> authors = getAuthors();
    authors.stream()
            .map(author -> author.getName())
            .map(name -> new StringBuilder(name))
            .map(sb -> sb.append("-feng").toString())
            .forEach(str -> System.out.println(str));
}

優化後

private static void testFilter() {
    List<Author> authors = getAuthors();
    authors.stream()
            .map(author -> author.getName())
            .map(StringBuilder::new)
            .map(sb -> sb.append("-feng").toString())
            .forEach(str -> System.out.println(str));
}

7 高階用法

7.1 基本資料型別優化

我們之前用到的很多Stream的方法由於都使用了泛型。所以涉及到的引數和返回值都是引用資料型別。

即使我們操作的是整數小數,但是實際用的都是他們的包裝類。JDK5中引入的自動裝箱和自動拆箱讓我們在使用對應的包裝類時就好像使用基本資料型別一樣方便。但是你一定要知道裝箱和拆箱肯定是要消耗時間的。雖然這個時間消耗很小。但是在大量的資料不斷的重複裝箱拆箱的時候,你就不能無視這個時間損耗了。

所以為了讓我們能夠對這部分的時間消耗進行優化。Stream還提供了很多專門針對基本資料型別的方法。

例如:mapToInt, mapToLong, mapToDouble, flatMapToInt, flatMapToDouble等。

private static void testFilter() {
    List<Author> authors = getAuthors();
    authors.stream()
           .map(author -> author.getAge())
            .map(age -> age+10)
            .filter(age -> age>18)
            .map(age -> age+2)
            .forEach(System.out::println);

    authors.stream()
            .mapToInt(author -> author.getAge())
            .map(age -> age+10)
            .filter(age -> age>18)
            .map(age -> age+2)
            .forEach(System.out::println);
}

7.2 並行流

​ 當流中有大量元素時,我們可以使用並行流去提高操作的效率。其實並行流就是把任務分配給多個執行緒去完全。如果我們自己去用程式碼實現的話其實會非常的複雜,並且要求你對並愛程式設計有足夠的理解和認識。而如果我們使用Stream的話,我們只需要修改一個方法的呼叫就可以使用並行流來幫我們實現,從而提高效率。

​ parallel方法可以把序列流轉換成並行流。

private static void testFilter() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = stream.filter(num -> num > 5)
            .reduce((result, ele) -> result + ele)
            .get();
    System.out.println(sum);
}
private static void testFilter() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = stream.parallel()//轉換為並行流,多個執行緒執行,適合於資料量大的場景
            .filter(num -> num > 5)
            .reduce((result, ele) -> result + ele)
            .get();
    System.out.println(sum);
}
private static void testFilter() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = stream.parallel()//轉換為並行流,多個執行緒執行,適合於資料量大的場景
            .peek(num -> System.out.println(num+Thread.currentThread().getName()))
            .filter(num -> num > 5)
            .reduce((result, ele) -> result + ele)
            .get();
    System.out.println(sum);
}

也可以通過parallelStream直接獲取並行流物件。

private static void testFilter() {
    List<Author> authors = getAuthors();
    authors.parallelStream()
            .map(author -> author.getAge())
            .map(age -> age+10)
            .filter(age -> age>18)
            .map(age -> age+2)
            .forEach(System.out::println);
}