函數式接口, Collection等

Lambda

函數式接口

lambda 表達式的使用須要藉助於 函數式接口, 也就是說只有函數式接口才能夠將其用 lambda 表達式進行簡化. 函數式接口定義爲僅含有一個抽象方法的接口. 按照這個定義, 一個接口若是聲明瞭兩個或兩個以上的方法就不叫函數式接口.java

JDK1.8爲接口的定義引入了默認方法, 能夠用default關鍵字在接口中直接定義方法的實現. 若是一個接口存在多個默認方法, 可是僅含有一個抽象方法, 這個接口也符合函數式接口的定義.數組

@FunctionalInterface註解用於標記該接口是一個函數式接口, 這也是JDK1.8以後新增的. 添加了該註解以後, 編譯器會限制接口只容許有一個抽象方法, 不然報錯. 建議爲函數式接口添加該註解. 在JDK中添加了這個註解的典型接口有 Function, Consumer, Predicate等.app

  • 該註解只能標記在」有且僅有一個抽象方法」的接口上
  • JDK8接口中的靜態方法和默認方法,都不算是抽象方法
  • 接口默認繼承java.lang.Object,因此若是接口顯示聲明覆蓋了Object中方法,那麼也不算抽象方法
  • 該註解不是必須的,若是一個接口符合」函數式接口」定義,那麼加不加該註解都沒有影響。加上該註解可以更好地讓編譯器進行檢查。若是編寫的不是函數式接口,可是加上了@FunctionInterface,那麼編譯器會報錯
  • 在一個接口中定義兩個自定義的方法,就會產生Invalid ‘@FunctionalInterface’ annotation; FunctionalInterfaceTest is not a functional interface錯誤.

Consumer Consumer<T> 接收T對象,不返回值框架

Predicate Predicate<T> 接收T對象並返回booleanide

Function Function<T, R> 接收T對象,返回R對象函數

Supplier Supplier<T> 提供T對象(例如工廠),不接收值優化

UnaryOperator UnaryOperator 接收T類型參數, 並返回同一類型的結果. 例如ui

public static void main(String[] args) {
       List<Integer> list = Arrays.asList(10,20,30,40,50);
       UnaryOperator<Integer> unaryOpt = i->i*i; 
       unaryOperatorFun(unaryOpt, list).forEach(x->System.out.println(x));       
    }
    private static List<Integer> unaryOperatorFun(UnaryOperator<Integer> unaryOpt, List<Integer> list){
       List<Integer> uniList = new ArrayList<>();
       list.forEach(i->uniList.add(unaryOpt.apply(i))); 
       return uniList;
    }

BinaryOperator BinaryOperator 接收兩個T類型的參數, 並返回同一類型的結果, 例如this

public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        map.put("X", "A");
        map.put("Y", "B");
        map.put("Z", "C");
        BinaryOperator<String> binaryOpt = (s1,s2)-> s1+"-"+s2;
        binaryOperatorFun(binaryOpt, map).forEach(x->System.out.println(x));
    }
    private static List<String> binaryOperatorFun(BinaryOperator<String> binaryOpt, Map<String,String> map){
        List<String> biList = new ArrayList<>();
        map.forEach((s1,s2)->biList.add(binaryOpt.apply(s1,s2)));
        return biList;
    }

Lambda

行爲參數化 行爲參數化簡單的說就是將方法的邏輯以參數的形式傳遞到方法中, 方法主體僅包含模板類通用代碼, 而一些會隨着業務場景而變化的邏輯則以參數的形式傳遞到方法之中, 採用行爲參數化可讓程序更加的通用, 以應對頻繁變動的需求. 例如對於一個Apple對象線程

public class Apple {
    private Color color;
    private Float weight;
    
    public Apple() {}
    public Apple(Color color, Float weight) {
        this.color = color;
        this.weight = weight;
    }
    ...
}

最初是須要篩選顏色, 可使用顏色做爲參數

public static List<Apple> filterApplesByCOlor(Lis<Apple> apples, Color color) {
    List<Apple> filtered = new ArrayList<>();
    for (final Apple apple : apples) {
        if (color.equals(apple.getColor())) {
            filtered.add(apple);
        }
    }
    return filtered;
}

若是以重量爲參數, 也能夠仿照上門的格式再寫一個方法 若是篩選的條件不止一種, 須要靈活組合, 那就有必要將filter做爲一個參數, 將篩選行爲抽象化

public interface AppleFilter {
    boolean accept(Apple apple);
}

public static class List<Apple> filterApplesByFilter(List<Apple> apples, AppleFilter filter) {
    List<Apple> filtered = new ArrayList<Apple>();
    for (final Apple apple : apples) {
        if (filter.accept(apple)) {
            filtered.add(apple);
        }
    }
    return filtered;
}

public static void main(String[] args) {
    //...
    AppleFilter filter = new AppleFilter() {
        @Override
        public boolean accept(Apple apple) {
            //...
        }
    }
    //...
}

上面的行爲參數化方式採用匿名類實現, 能夠在具體調用的地方用匿名類指定函數的具體執行邏輯, 可是還不夠簡潔, 在 JDK1.8中能夠經過 lambda 表達式進行簡化

List<Apple> filtered = filterApplesByFilter(apples, 
        (apple) -> Color.RED.equals(apple.getColor()));

Lambda 表達式的定義與形式 Lambda 表達式

  • 本質上是一個函數, 雖然它不屬於某個特定的類, 但具有參數列表、函數主體、返回類型, 甚至可以拋出異常
  • 其次它是匿名的, lambda 表達式沒有具體的函數名稱

Lambda 表達式能夠像參數同樣進行傳遞, 從而簡化代碼的編寫,其格式定義以下

參數列表 -> 表達式參數列表 -> {表達式集合}

注意

  • lambda 表達式隱含了 return 關鍵字, 因此在單個的表達式中, 咱們無需顯式的寫 return語句, 可是當表達式是一個語句集合的時候則須要顯式添加 return語句並用花括號{ } 將多個表達式包圍起來.
  • lambda中, this不是指向lambda表達式產生的那個SAM對象, 而是聲明它的外部對象

方法引用

  • objectName::instanceMethod
  • ClassName::staticMethod
  • ClassName::instanceMethod

前兩種方式相似, 等同於把lambda表達式的參數直接當成instanceMethod|staticMethod的參數來調用. 好比System.out::println等同於x->System.out.println(x), Math::max等同於(x, y)->Math.max(x,y), 例如 execStrs.forEach(System.out::println).

最後一種方式等同於把lambda表達式的第一個參數當成instanceMethod的目標對象, 其餘剩餘參數當成該方法的參數. 好比String::toLowerCase等同於x->x.toLowerCase(). 能夠這麼理解,前兩種是將傳入對象當參數執行方法, 後一種是調用傳入對象的方法.

List<BigDecimal> bdList = new ArrayList<>();
BigDecimal result = bdList.stream()
        .reduce(BigDecimal.ZERO, BigDecimal::add);

上面的代碼,

  • 建立一個BigDecimal列表

  • 轉換爲 Stream<BigDecimal>

  • 調用 reduce 方法

    • 提供了一個定義好的加數, 這裏是BigDecimal.ZERO
    • 使用BinaryOperator<BigDecimal>, 經過方法BigDecimal::add將兩個BigDecimal的值相加
List<Invoice> invoiceList = new ArrayList<>();
//populate
Function<Invoice, BigDecimal> totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity());
BigDecimal result = invoiceList.stream()
        .map(totalMapper)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

上面的代碼, 使用了一個Function作stream對象的map.

構造器引用 構造器引用語法: ClassName::new 把lambda表達式的參數當成ClassName構造器的參數. 例如BigDecimal::new等同於x->new BigDecimal(x).

Iterable

JDK1.8中, Iterable接口增長了兩個帶default實現的方法, 一個是

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

另外一個是

default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }

Spliterator

Spliterator這個類型是JDK1.8新增的接口方法

boolean tryAdvance(Consumer<? super T> action);

Spliterator<T> trySplit();

Spliterator用於對一個數據源進行遍歷和分區, 這個數據源能夠是一個數組, 一個Collection,一個IO通道, 或者一個生成函數. Spliterator能夠單個或成批地處理元素, 也能夠將部分元素劃分爲單獨的Spliterator, 不能分區的Spliterator將不能從並行處理中獲益. Spliterator用characteristics()方法彙報結構特性. 調用trySplit()的線程能夠將返回的Spliterator傳遞給另外一個線程, 這個線程能夠遍歷或進一步拆分這個Spliterator.

Consumer

Consumer是一個操做, 用於接受單個輸入而且不返回結果. 在stream裏主要是用於forEach內部迭代的時候, 對傳入的參數作一系列的業務操做. Consumer有相關的原始類型實現: IntConsumer,LongConsumer,DoubleConsumer, 是Consumer的特例

void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

JDK8中有雙冒號的用法,就是把方法當作參數傳到stream內部,使stream的每一個元素都傳入到該方法裏面執行一下

public class MyTest {
    public static void  printValur(String str){
        System.out.println("print value : "+str);
    }
 
    public static void main(String[] args) {
        List<String> al = Arrays.asList("a", "b", "c", "d");
        al.forEach(AcceptMethod::printValur);
        //下面的方法和上面等價的
        Consumer<String> methodParam = AcceptMethod::printValur;
        al.forEach(x -> methodParam.accept(x));//方法執行accept
    }
}

Collection

Collection是集合類型對象的根接口. collection表明了一組對象集合, 這些對象就是集合的元素. 一些集合容許重複的元素, 另外一些則不容許. 一些集合是有序的, 另外一些則是無序的. JDK並無提供這個接口的直接實現, 可是提供了其派生接口的實現, 例如Set和List. 這個接口用於傳遞和操做集合類型的數據並保持最大的通用特性.

打包類型或多集合類型(包含重複對象的無序集合)應該直接實現這個接口.

全部通用的Collection實現類(一般是實現某個派生接口)都應該提升兩個標準的構造方法: 一個 void (無參數) 構造方法和一個單參數構造方法, 前者建立一個空集合, 後者建立一個包含一個元素的集合. 實際上, 後者容許用戶用任何集合建立包含相同元素的新類型集合. 雖然不能對這種便利進行強制(由於接口不能包含構造方法)可是Java平臺上全部的通用Collection實現都遵照這種約定.

在操做集合時, 若是調用了此集合類型不支持的方法, 應當拋出UnsupportedOperationException. 在特殊狀況下, 例如調用對集合並不產生影響時, 能夠拋出UnsupportedOperationException, 但這不是強制的. 例如在一個不可修改的collection上調用addAll(Collection)方法時, 若是參數的集合爲空時, 不強制拋出異常.

有些集合實現會對元素有限制. 例如一些實現禁止null元素, 另外一些則對元素的類型有要求. 添加不合法的元素時會拋出異常, 例如NullPointerException 或 ClassCastException. 而查詢一個不合法元素時可能會拋出異常, 也可能會返回false; 這些異常在接口的規範中是可選的.

同步的策略由各個集合實現本身來決定. 因爲不是強約束, 被其餘線程轉換的集合上調用任何接口方法均可能致使未定義的行爲, 包含直接調用, 傳參後調用, 或者用迭代器對集合進行操做.

Collections框架中的許多方法是根據equals方法定義的. 例如, contains(Object o)方法的定義是: "僅當這個集合包含至少一個元素e知足(o==null ? e==null : o.equals(e))時, 返回true." 這個定義不該該被解釋爲說明使用非null參數調用Collection.contains時將致使o.equals(e)對每個e元素進行調用. Collection的實現能夠自行優化避免使用equals, 例如首先比較兩個元素的hash code. (Object.hashCode() 的定義保證了hash code不相等的兩個對象確定不相等.) 更進一步, Collections 框架的不一樣實現能夠自行利用對象方法的特定行爲進行優化..

一些操做中, 對直接或間接包含本身的集合進行遞歸遍歷可能會拋出異常, 例如clone(), equals(), hashCode() 和 toString() 方法. 具體的實現類可能會對這種狀況進行處理, 不過大多數如今的實現並不會這樣作.

相關文章
相關標籤/搜索