歡迎光臨
每天分享高質量文章

Java8 函式式程式設計探秘

(點選上方公眾號,可快速關註)


來源:琴水玉 ,

www.cnblogs.com/lovesqcc/p/7965387.html

引子

將行為作為資料傳遞

怎樣在一行程式碼裡同時計算一個串列的和、最大值、最小值、平均值、元素個數、奇偶分組、指數、排序呢?

答案是思維反轉!將行為作為資料傳遞。 文藝青年的程式碼如下所示:

public class FunctionUtil {

 

   public static List multiGetResult(List, R>> functions, List list) {

     return functions.stream().map(f -> f.apply(list)).collect(Collectors.toList());

   }

 

   public static void main(String[] args) {

     System.out.println(multiGetResult(

         Arrays.asList(

             list -> list.stream().collect(Collectors.summarizingInt(x->x)),

             list -> list.stream().filter(x -> x < 50).sorted().collect(Collectors.toList()),

             list -> list.stream().collect(Collectors.groupingBy(x->(x%2==0? “even”: “odd”))),

             list -> list.stream().sorted().collect(Collectors.toList()),

             list -> list.stream().sorted().map(Math::sqrt).collect(Collectors.toMap(x->x, y->Math.pow(2,y)))),

         Arrays.asList(64,49,25,16,9,4,1,81,36)));

   }

}

呃,有點賣弄小聰明。 不過要是能將行為作為資料自由傳遞和施加於資料集產生結果,那麼其程式碼表達能力將如莊子之言,恣意瀟灑而無所極限。病毒,不就是將行為匿身於資料中的一種表現麼?

行為就是資料。

Java8函式框架解讀

函式程式設計的最直接的表現,莫過於將函式作為資料自由傳遞,結合泛型推導能力,使程式碼表達能力獲得飛一般的提升。那麼,Java8是怎麼支援函式程式設計的呢?主要有三個核心概念:

  • 函式介面(Function)

  • 流(Stream)

  • 聚合器(Collector)

函式介面

關於函式介面,需要記住的就是兩件事:

  • 函式介面是行為的抽象;

  • 函式介面是資料轉換器。

最直接的支援就是 java.util.Function 包。定義了四個最基礎的函式介面:

  • Supplier: 資料提供器,可以提供 T 型別物件;無參的建構式,提供了 get 方法;

  • Function: 資料轉換器,接收一個 T 型別的物件,傳回一個 R型別的物件; 單引數單傳回值的行為介面;提供了 apply, compose, andThen, identity 方法;

  • Consumer: 資料消費器, 接收一個 T型別的物件,無傳回值,通常用於設定T物件的值; 單引數無傳回值的行為介面;提供了 accept, andThen 方法;

  • Predicate: 條件測試器,接收一個 T 型別的物件,傳回布林值,通常用於傳遞條件函式; 單引數布林值的條件性介面。提供了 test (條件測試) , and-or- negate(與或非) 方法。

其中, compose, andThen, and, or, negate 用來組合函式介面而得到更強大的函式介面。

其它的函式介面都是透過這四個擴充套件而來。

  • 在引數個數上擴充套件: 比如接收雙引數的,有 Bi 字首, 比如 BiConsumer, BiFunction ;

  • 在型別上擴充套件: 比如接收原子型別引數的,有 [Int|Double|Long][Function|Consumer|Supplier|Predicate]

  • 特殊常用的變形: 比如 BinaryOperator , 是同型別的雙引數 BiFunction ,二元運運算元 ; UnaryOperator 是 Function 一元運運算元。

那麼,這些函式介面可以接收哪些值呢?

  • 類/物件的靜態方法取用、實體方法取用。取用符號為雙冒號 ::

  • 類的建構式取用,比如 Class::new

  • lambda運算式

聚合器

先說聚合器。每一個流式計算的末尾總有一個類似 collect(Collectors.toList()) 的方法呼叫。collect 是 Stream 的方法,而引數則是聚合器Collector。已有的聚合器定義在Collectors 的靜態方法裡。 那麼這個聚合器是怎麼實現的呢?

Reduce

大部分聚合器都是基於 Reduce 操作實現的。 Reduce ,名曰推導,含有三個要素: 初始值 init, 二元運運算元 BinaryOperator, 以及一個用於聚合結果的資料源S。

Reduce 的演演算法如下:

STEP1: 初始化結果 R = init ;

STEP2: 每次從 S 中取出一個值 v,透過二元運運算元施加到 R 和 v ,產生一個新值賦給 R = BinaryOperator(R, v);重覆 STEP2, 直到 S 中沒有值可取為止。

比如一個串列求和,Sum([1,2,3]) , 那麼定義一個初始值 0 以及一個二元加法操作 BO = a + b ,透過三步完成 Reduce 操作:step1: R = 0; step2: v=1, R = 0+v = 1; step2: v=2, R = 1 + v = 3 ; step3: v = 3, R = 3 + v = 6。

四要素

一個聚合器的實現,通常需要提供四要素:

  • 一個結果容器的初始值提供器 supplier ;

  • 一個用於將每次二元操作的中間結果與結果容器的值進行操作並重新設定結果容器的累積器 accumulator ;

  • 一個用於對Stream元素和中間結果進行操作的二元運運算元 combiner ;

  • 一個用於對結果容器進行最終聚合的轉換器 finisher(可選) 。

Collectors.CollectorImpl 的實現展示了這一點:

static class CollectorImpl implements Collector {

        private final Supplier supplier;

        private final BiConsumer accumulator;

        private final BinaryOperator combiner;

        private final Function finisher;

        private final Set characteristics;

 

        CollectorImpl(Supplier supplier,

                      BiConsumer accumulator,

                      BinaryOperator combiner,

                      Function finisher,

                      Set characteristics) {

            this.supplier = supplier;

            this.accumulator = accumulator;

            this.combiner = combiner;

            this.finisher = finisher;

            this.characteristics = characteristics;

        }

}

串列類聚合器

串列類聚合器實現,基本是基於Reduce 操作完成的。 看如下程式碼:

public static

    Collector> toList() {

        return new CollectorImpl<>((Supplier>) ArrayList::new, List::add,

                                   (left, right) -> { left.addAll(right); return left; },

                                   CH_ID);

首先使用 ArrayList::new 創造一個空串列; 然後 List:add 將Stream累積操作的中間結果加入到這個串列;第三個函式則將兩個串列元素進行合併成一個結果串列中。 就是這麼簡單。集合聚合器 toSet(), 字串聯結器 joining(),以及串列求和(summingXXX)、最大(maxBy)、最小值(minBy)等都是這個套路。

對映類聚合器

對映類聚合器基於Map合併來完成。看這段程式碼:

private static >

    BinaryOperator mapMerger(BinaryOperator mergeFunction) {

        return (m1, m2) -> {

            for (Map.Entry e : m2.entrySet())

                m1.merge(e.getKey(), e.getValue(), mergeFunction);

            return m1;

        };

    }

根據指定的值合併函式 mergeFunction, 傳回一個map合併器,用來合併兩個map裡相同key的值。mergeFunction用來對兩個map中相同key的值進行運算得到新的value值,如果value值為null,會移除相應的key,否則使用value值作為對應key的值。這個方法是私有的,主要為支撐 toMap,groupingBy 而生。

toMap的實現很簡短,實際上就是將指定stream的每個元素分別使用給定函式keyMapper, valueMapper進行對映得到 newKey, newValue,從而形成新的Map[newKey,newValue] (1), 再使用mapMerger(mergeFunction) 生成的 map 合併器將其合併到 mapSupplier (2)。如果只傳 keyMapper, valueMapper,那麼就只得到結果(1)。

public static >

    Collector toMap(Function super T, ? extends K> keyMapper,

                                Function super T, ? extends U> valueMapper,

                                BinaryOperator mergeFunction,

                                Supplier mapSupplier) {

        BiConsumer accumulator

                = (map, element) -> map.merge(keyMapper.apply(element),

                                              valueMapper.apply(element), mergeFunction);

        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);

    }

toMap 的一個示例見如下程式碼:

List list = Arrays.asList(1,2,3,4,5);

Supplier

> mapSupplier = () -> list.stream().collect(Collectors.toMap(x->x, y-> y * y));

 

Map mapValueAdd = list.stream().collect(Collectors.toMap(x->x, y->y, (v1,v2) -> v1+v2, mapSupplier));

System.out.println(mapValueAdd);

將一個 List 轉成 map[1=1,2=2,3=3,4=4,5=5],然後與另一個map[1=1,2=4,3=9,4=16,5=25]的相同key的value進行相加。註意到, toMap 的最後一個引數是 Supplier

, 是 Map 提供器,而不是 Map 物件。如果用Map物件,會報 no instances of type variables M exists so that conforms to Supplier。 在函式式程式設計的世界裡,通常是用函式來表達和組合的。

自定義聚合器

讓我們仿照 Collectors.toList() 做一個自定義的聚合器。實現一個含N個數的斐波那契序列 List。由於 Reduce 每次都從流中取一個數,因此需要生產一個含N個數的stream;可使用 Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream() , 亦可使用 IntStream.range(1,11) ,不過兩者的 collector 方法是不一樣的。這裡我們取前者。

現在,需要構造四要素:

  • 可變的結果容器提供器 Supplier> = () -> [0, 1] ; 註意這裡不能使用 Arrays.asList , 因為該方法生成的串列是不可變的。

  • 累積器 BiConsumer, Integer> accumulator(): 這裡流的元素未用,僅僅用來使計算進行和終止。新的元素從結果容器中取最後兩個相加後產生新的結果放到結果容器中。

  • 組合器 BinaryOperator> combiner() : 照葫蘆畫瓢,目前沒看出這步是做什麼用;直接 return null; 也是OK的。

  • 最終轉換器 Function, List> finisher() :在最終轉換器中,移除初始設定的兩個值 0, 1 。

程式碼如下:

/**

 * Created by shuqin on 17/12/5.

 */

public class FiboCollector implements Collector, List> {

 

  public Supplier> supplier() {

    return () -> {

      List result = new ArrayList<>();

      result.add(0); result.add(1);

      return result;

    };

  }

 

  @Override

  public BiConsumer, Integer> accumulator() {

    return (res, num) -> {

      Integer next = res.get(res.size()-1) + res.get(res.size()-2);

      res.add(next);

    };

  }

 

  @Override

  public BinaryOperator> combiner() {

    return null;

    //return (left, right) -> { left.addAll(right); return left; };

  }

 

  @Override

  public Function, List> finisher() {

    return res -> { res.remove(0); res.remove(1); return res; };

  }

 

  @Override

  public Set characteristics() {

    return Collections.emptySet();

  }

 

}

 

List fibo = Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream().collect(new FiboCollector());

System.out.println(fibo);

流(Stream)是Java8對函式式程式設計的重要支撐。大部分函式式工具都圍繞Stream展開。

Stream的介面

Stream 主要有四類介面:

  • 流到流之間的轉換:比如 filter(過濾), map(對映轉換), mapTo[Int|Long|Double] (到原子型別流的轉換), flatMap(高維結構平鋪),flatMapTo[Int|Long|Double], sorted(排序),distinct(不重覆值),peek(執行某種操作,流不變,可用於除錯),limit(限制到指定元素數量), skip(跳過若干元素) ;

  • 流到終值的轉換: 比如 toArray(轉為陣列),reduce(推導結果),collect(聚合結果),min(最小值), max(最大值), count (元素個數), anyMatch (任一匹配), allMatch(所有都匹配), noneMatch(一個都不匹配), findFirst(選擇首元素),findAny(任選一元素) ;

  • 直接遍歷: forEach (不保序遍歷,比如並行流), forEachOrdered(保序遍歷) ;

  • 構造流: empty (構造空流),of (單個元素的流及多元素順序流),iterate (無限長度的有序順序流),generate (將資料提供器轉換成無限非有序的順序流), concat (流的連線), Builder (用於構造流的Builder物件)

除了 Stream 本身自帶的生成Stream 的方法,陣列和容器及StreamSupport都有轉換為流的方法。比如 Arrays.stream , [List|Set|Collection].[stream|parallelStream] , StreamSupport.[int|long|double|]stream;

流的型別主要有:Reference(物件流), IntStream (int元素流), LongStream (long元素流), Double (double元素流) ,定義在類 StreamShape 中,主要將操作適配於型別系統。

flatMap 的一個例子見如下所示,將一個二維陣列轉換為一維陣列:

List nums = Arrays.asList(Arrays.asList(1,2,3), Arrays.asList(1,4,9), Arrays.asList(1,8,27))

                           .stream().flatMap(x -> x.stream()).collect(Collectors.toList());

System.out.println(nums);

collector實現

這裡我們僅分析序列是怎麼實現的。入口在類 java.util.stream.ReferencePipeline 的 collect 方法:

container = evaluate(ReduceOps.makeRef(collector));

return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)

          ? (R) container : collector.finisher().apply(container);

這裡的關鍵是 ReduceOps.makeRef(collector)。 點進去:

public static TerminalOp

    makeRef(Collector super T, I, ?> collector) {

        Supplier supplier = Objects.requireNonNull(collector).supplier();

        BiConsumer accumulator = collector.accumulator();

        BinaryOperator combiner = collector.combiner();

        class ReducingSink extends Box

                implements AccumulatingSink {

            @Override

            public void begin(long size) {

                state = supplier.get();

            }

 

            @Override

            public void accept(T t) {

                accumulator.accept(state, t);

            }

 

            @Override

            public void combine(ReducingSink other) {

                state = combiner.apply(state, other.state);

            }

        }

        return new ReduceOp(StreamShape.REFERENCE) {

            @Override

            public ReducingSink makeSink() {

                return new ReducingSink();

            }

 

            @Override

            public int getOpFlags() {

                return collector.characteristics().contains(Collector.Characteristics.UNORDERED)

                       ? StreamOpFlag.NOT_ORDERED

                       : 0;

            }

        };

    }

 

private static abstract class Box {

        U state;

 

        Box() {} // Avoid creation of special accessor

 

        public U get() {

            return state;

        }

    }

Box 是一個結果值的持有者; ReducingSink 用begin, accept, combine 三個方法定義了要進行的計算;ReducingSink是有狀態的流資料消費的計算抽象,閱讀Sink介面檔案可知。ReduceOps.makeRef(collector) 傳回了一個封裝了Reduce操作的ReduceOps物件。註意到,這裡都是宣告要執行的計算,而不涉及計算的實際過程。展示了表達與執行分離的思想。真正的計算過程啟動在 ReferencePipeline.evaluate 方法裡:

final R evaluate(TerminalOp terminalOp) {

        assert getOutputShape() == terminalOp.inputShape();

        if (linkedOrConsumed)

            throw new IllegalStateException(MSG_STREAM_LINKED);

        linkedOrConsumed = true;

 

        return isParallel()

               ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))

               : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));

    }

使用 IDE 的 go to implementations 功能, 跟進去,可以發現,最終在 AbstractPipeLine 中定義了:

@Override

    final void copyInto(Sink wrappedSink, Spliterator spliterator) {

        Objects.requireNonNull(wrappedSink);

 

        if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {

            wrappedSink.begin(spliterator.getExactSizeIfKnown());

            spliterator.forEachRemaining(wrappedSink);

            wrappedSink.end();

        }

        else {

            copyIntoWithCancel(wrappedSink, spliterator);

        }

    }

Spliterator 用來對流中的元素進行分割槽和遍歷以及施加Sink指定操作,可以用於併發計算。Spliterator的具體實現類定義在 Spliterators 的靜態類和靜態方法中。其中有:

陣列Spliterator:

static final class ArraySpliterator implements Spliterator

static final class IntArraySpliterator implements Spliterator.OfInt

static final class LongArraySpliterator implements Spliterator.OfLong

static final class DoubleArraySpliterator implements Spliterator.OfDouble

 

迭代Spliterator:

static class IteratorSpliterator implements Spliterator

static final class IntIteratorSpliterator implements Spliterator.OfInt

static final class LongIteratorSpliterator implements Spliterator.OfLong

static final class DoubleIteratorSpliterator implements Spliterator.OfDouble

 

抽象Spliterator:

public static abstract class AbstractSpliterator implements Spliterator

private static abstract class EmptySpliterator, C>

public static abstract class AbstractIntSpliterator implements Spliterator.OfInt

public static abstract class AbstractLongSpliterator implements Spliterator.OfLong

public static abstract class AbstractDoubleSpliterator implements Spliterator.OfDouble

每個具體類都實現了trySplit,forEachRemaining,tryAdvance,estimateSize,characteristics, getComparator。 trySplit 用於拆分流,提供併發能力;forEachRemaining,tryAdvance 用於遍歷和消費流中的資料。下麵展示了IteratorSpliterator的forEachRemaining,tryAdvance 兩個方法的實現。可以看到,木有特別的地方,就是遍歷元素並將指定操作施加於元素。

@Override

        public void forEachRemaining(Consumer super T> action) {

            if (action == null) throw new NullPointerException();

            Iterator extends T> i;

            if ((i = it) == null) {

                i = it = collection.iterator();

                est = (long)collection.size();

            }

            i.forEachRemaining(action);

        }

 

        @Override

        public boolean tryAdvance(Consumer super T> action) {

            if (action == null) throw new NullPointerException();

            if (it == null) {

                it = collection.iterator();

                est = (long) collection.size();

            }

            if (it.hasNext()) {

                action.accept(it.next());

                return true;

            }

            return false;

        }

整體流程就是這樣。回顧一下:

  • Collector 定義了必要的聚合操作函式;

  • ReduceOps.makeRef 將 Collector 封裝成一個計算物件 ReduceOps ,依賴的 ReducingSink 定義了具體的流資料消費過程;

  • Spliterator 用於對流中的元素進行分割槽和遍歷以及施加Sink指定的操作。

Pipeline

那麼,Spliterator 又是從哪裡來的呢?是透過類 java.util.stream.AbstractPipeline 的方法 sourceSpliterator 拿到的:

private Spliterator > sourceSpliterator(int terminalFlags) {

        // Get the source spliterator of the pipeline

        Spliterator > spliterator = null;

        if (sourceStage.sourceSpliterator != null) {

            spliterator = sourceStage.sourceSpliterator;

            sourceStage.sourceSpliterator = null;

        }

        else if (sourceStage.sourceSupplier != null) {

            spliterator = (Spliterator >) sourceStage.sourceSupplier.get();

            sourceStage.sourceSupplier = null;

        }

        else {

            throw new IllegalStateException(MSG_CONSUMED);

        }

        // code for isParallel

       return spliterator;

}

這裡的 sourceStage 是一個 AbstractPipeline。 Pipeline 是實現流式計算的流水線抽象,也是Stream的實現類。可以看到,java.util.stream 定義了四種 pipeline: DoublePipeline, IntPipeline, LongPipeline, ReferencePipeline。可以重點看 ReferencePipeline 的實現。比如 filter, map

abstract class ReferencePipeline

        extends AbstractPipeline>

        implements Stream

 

@Override

    public final Stream filter(Predicate super P_OUT> predicate) {

        Objects.requireNonNull(predicate);

        return new StatelessOp(this, StreamShape.REFERENCE,

                                     StreamOpFlag.NOT_SIZED) {

            @Override

            Sink opWrapSink(int flags, Sink sink) {

                return new Sink.ChainedReference(sink) {

                    @Override

                    public void begin(long size) {

                        downstream.begin(-1);

                    }

 

                    @Override

                    public void accept(P_OUT u) {

                        if (predicate.test(u))

                            downstream.accept(u);

                    }

                };

            }

        };

    }

 

    @Override

    @SuppressWarnings(“unchecked”)

    public final Stream map(Function super P_OUT, ? extends R> mapper) {

        Objects.requireNonNull(mapper);

        return new StatelessOp(this, StreamShape.REFERENCE,

                                     StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {

            @Override

            Sink opWrapSink(int flags, Sink sink) {

                return new Sink.ChainedReference(sink) {

                    @Override

                    public void accept(P_OUT u) {

                        downstream.accept(mapper.apply(u));

                    }

                };

            }

        };

    }

套路基本一樣,關鍵點在於 accept 方法。filter 只在滿足條件時將值傳給下一個 pipeline, 而 map 將計算的值傳給下一個 pipeline. StatelessOp 沒有什麼邏輯,JDK檔案解釋是:Base class for a stateless intermediate stage of a Stream。相應還有一個 StatefulOp, Head。 這些都是 ReferencePipeline ,負責將值在 pipeline 之間傳遞,交給 Sink 去計算。

static class Head extends ReferencePipeline

abstract static class StatelessOp extends ReferencePipeline

abstract static class StatefulOp extends ReferencePipeline

至此,我們對整個流計算過程有了更清晰的認識。 細節可以再逐步推敲。

函式式程式設計的益處

更精練的程式碼

函式程式設計的一大益處,是用更精練的程式碼表達常用資料處理樣式。函式介面能夠輕易地實現模板方法樣式,只要將不確定的業務邏輯抽象成函式介面,然後傳入不同的lambda運算式即可。博文“精練程式碼:一次Java函式式程式設計的重構之旅” 展示瞭如何使用函式式程式設計來重構常見程式碼,萃取更多可復用的程式碼樣式。

http://www.cnblogs.com/lovesqcc/p/7077971.html

這裡給出一個串列分組的例子。實際應用常常需要將一個串列 List[T] 轉換為一個 Map[K, List[T]] , 其中 K 是透過某個函式來實現的。 看下麵一段程式碼:

public static Map> buildRecordMap(List records, List colKeys) {

    Map> recordMap = new HashMap<>();

    records.forEach(

        record -> {

          String recordKey = buildRecordKey(record.getFieldValues(), colKeys);

          if (recordMap.get(recordKey) == null) {

            recordMap.put(recordKey, new ArrayList());

          }

          recordMap.get(recordKey).add(record);

    });

    return recordMap;

  }

可以使用 Collectors.groupingby 來簡潔地實現:

public static Map> buildRecordMapBrief(List records, List colKeys) {

    return records.stream().collect(Collectors.groupingBy(

        record -> buildRecordKey(record.getFieldValues(), colKeys)

    ));

  }

很多常用資料處理演演算法,都可以使用函式式程式設計的流式計算簡潔表達。

更通用的程式碼

使用函式介面,結合泛型,很容易用精練的程式碼,寫出非常通用的工具方法。 實際應用中,常常會有這樣的需求: 有兩個物件串列srcList和destList,兩個物件型別的某個欄位K具有相同的值;需要根據這個相同的值合併對應的兩個物件的資訊。

這裡給出了一個串列合併函式,可以將一個物件串列合併到指定的物件串列中。實現是: 先將待合併的串列srcList根據key值函式keyFunc構建起srcMap,然後遍歷dest串列的物件R,將待合併的資訊srcMap[key]及T透過合併函式mergeFunc生成的新物件R新增到最終結果串列。

public static List mergeList(List srcList, List destList ,

                                      Function keyFunc,

                                      BinaryOperator mergeFunc) {

  return mergeList(srcList, destList, keyFunc, keyFunc, mergeFunc);

}

 

public static List mergeList(List srcList, List destList ,

                                          Function skeyFunc, Function dkeyFunc,

                                          BiFunction mergeFunc) {

 

  Map srcMap = srcList.stream().collect(Collectors.toMap(skeyFunc, s -> s, (k1,k2) -> k1));

  return destList.stream().map(

      dest -> {

        K key = dkeyFunc.apply(dest);

        S src = srcMap.get(key);

        return mergeFunc.apply(src, dest);

      }

  ).collect(Collectors.toList());

 

}

更可測的程式碼

使用函式介面可以方便地隔離外部依賴,使得類和物件的方法更純粹、更具可測性。博文“使用Java函式介面及lambda運算式隔離和模擬外部依賴更容易滴單測”,“改善程式碼可測性的若干技巧”集中討論瞭如何使用函式介面提升程式碼的可單測性。

使用Java函式介面及lambda運算式隔離和模擬外部依賴更容易滴單測

http://www.cnblogs.com/lovesqcc/p/6917448.html

改善程式碼可測性的若干技巧

http://www.cnblogs.com/lovesqcc/p/7898319.html

組合的力量

函式程式設計的強大威力,在於將函式介面組合起來,構建更強大更具有通用性的實用工具方法。超越型別,超越操作與資料的邊界。

前面提到,函式介面就是資料轉換器。比如Function 就是“將T物件轉換成R物件的行為或資料轉換器”。對於實際工程應用的普通級函式程式設計足夠了。不過,要玩轉函式介面,就要升級下認識。 比如 Function, Function> 該怎麼理解呢?這是“一個一元函式g(h(s,q)) ,引數指定的二元函式h(s,q)應用於指定的兩個引數S,Q,得到一個一元函式f(t),這個函式接收一個T物件,傳回一個R物件”。 如下程式碼所示:

public static Function, Function> op(Function funcx, Function funcy) {

  return opFunc -> aT -> opFunc.apply(funcx.apply(aT), funcy.apply(aT));

}

 

System.out.println(op(x-> x.toString().length(), y-> y+”,world”).apply((x,y) -> x+” ” +y).apply(“hello”));

實現的是 h(t) = h(funx(t), funy(t)) ,h(x,y) 是一個雙引數函式。

Java函式介面實現函式組合及裝飾器樣式” 展示瞭如何使用極少量的程式碼實現裝飾器樣式,將簡單的函式介面組合成更強大功能的複合函式介面。

http://www.cnblogs.com/lovesqcc/p/7107558.html

來看上面的 public static List mergeList(List srcList, List destList , Function skeyFunc, Function dkeyFunc,BiFunction mergeFunc) , 通用性雖好,可是有5個引數,有點醜。怎麼改造下呢? 看實現,主要包含兩步:1. 將待合併串列轉化為 srcMap: map; 2. 使用指定的函式 dKeyFunc, mergeFunc 作用於destList和srcMap,得到最終結果。可以改寫程式碼如下:

public static List mergeList(List srcList, List destList ,

                                          Function skeyFunc, Function dkeyFunc,

                                          BiFunction mergeFunc) {

    return join(destList, mapKey(srcList, skeyFunc)).apply(dkeyFunc, (BiFunction) mergeFunc);

 

  }

 

  public static Map mapKey(List list, Function keyFunc) {

    return list.stream().collect(Collectors.toMap(keyFunc, t -> t, (k1,k2) -> k1));

  }

 

  public static BiFunction, BiFunction, List> join(List destList, Map srcMap) {

    return (dkeyFunc,mergeFunc) -> destList.stream().map(

        dest -> {

          K key = dkeyFunc.apply(dest);

          S src = srcMap.get(key);

          return mergeFunc.apply(src, dest);

        }).collect(Collectors.toList());

  }

 

System.out.println(mergeList(Arrays.asList(1,2), Arrays.asList(“an”, “a”), s-> s, t-> t.toString().length(), (s,t) -> s+t));

mapKey 是一個通用函式,用於將一個 list 按照指定的 keyFunc 轉成一個 Map; join 函式接受一個 list 和待合併的 srcMap, 傳回一個二元函式,該函式使用指定的 dkeyFunc 和 mergeFunc 來合併指定資料得到最終的結果串列。這可稱之為“延遲指定行為”。現在, mapKey 和 join 都是通用性函式。Amazing !

Java8泛型

在Java8函式式框架的解讀中,可以明顯看到,泛型無處不在。Java8的泛型推導能力也有很大的增強。可以說,如果沒有強大的泛型推導支撐,函式介面的威力將會大打折扣。

完整程式碼示例

package zzz.study.function;

 

import java.util.Arrays;

import java.util.List;

import java.util.Map;

import java.util.function.BiFunction;

import java.util.function.BinaryOperator;

import java.util.function.Function;

import java.util.function.Supplier;

import java.util.stream.Collectors;

 

/**

 * Created by shuqin on 17/12/3.

 */

public class FunctionUtil {

 

  public static List multiGetResult(List, R>> functions, List list) {

    return functions.stream().map(f -> f.apply(list)).collect(Collectors.toList());

  }

 

  public static List mergeList(List srcList, List destList ,

                                        Function keyFunc,

                                        BinaryOperator mergeFunc) {

    return mergeList(srcList, destList, keyFunc, keyFunc, mergeFunc);

  }

 

  public static List mergeList(List srcList, List destList ,

                                          Function skeyFunc, Function dkeyFunc,

                                          BiFunction mergeFunc) {

    return join(destList, mapKey(srcList, skeyFunc)).apply(dkeyFunc, (BiFunction) mergeFunc);

 

  }

 

  public static Map mapKey(List list, Function keyFunc) {

    return list.stream().collect(Collectors.toMap(keyFunc, t -> t, (k1,k2) -> k1));

  }

 

  public static BiFunction, BiFunction, List> join(List destList, Map srcMap) {

    return (dkeyFunc,mergeFunc) -> destList.stream().map(

        dest -> {

          K key = dkeyFunc.apply(dest);

          S src = srcMap.get(key);

          return mergeFunc.apply(src, dest);

        }).collect(Collectors.toList());

  }

 

  /** 對給定的值 x,y 應用指定的二元操作函式 */

  public static Function, R> op(T x, S y) {

    return opFunc -> opFunc.apply(x, y);

  }

 

  /** 將兩個函式使用組合成一個函式,這個函式接受一個二元操作函式 */

  public static Function, R> op(Function funcx, Function funcy, T x) {

    return opFunc -> opFunc.apply(funcx.apply(x), funcy.apply(x));

  }

 

  public static Function, Function> op(Function funcx, Function funcy) {

    return opFunc -> aT -> opFunc.apply(funcx.apply(aT), funcy.apply(aT));

  }

 

  /** 將兩個函式組合成一個疊加函式, compose(f,g) = f(g) */

  public static Function compose(Function funcx, Function funcy) {

    return x -> funcx.apply(funcy.apply(x));

  }

 

  /** 將若干個函式組合成一個疊加函式, compose(f1,f2,…fn) = f1(f2(…(fn))) */

  public static Function compose(Function… extraFuncs) {

    if (extraFuncs == null || extraFuncs.length == 0) {

      return x->x;

    }

    return x -> Arrays.stream(extraFuncs).reduce(y->y, FunctionUtil::compose).apply(x);

  }

 

   public static void main(String[] args) {

     System.out.println(multiGetResult(

         Arrays.asList(

             list -> list.stream().collect(Collectors.summarizingInt(x->x)),

             list -> list.stream().filter(x -> x < 50).sorted().collect(Collectors.toList()),

             list -> list.stream().collect(Collectors.groupingBy(x->(x%2==0? “even”: “odd”))),

             list -> list.stream().sorted().collect(Collectors.toList()),

             list -> list.stream().sorted().map(Math::sqrt).collect(Collectors.toMap(x->x, y->Math.pow(2,y)))),

         Arrays.asList(64,49,25,16,9,4,1,81,36)));

 

     List list = Arrays.asList(1,2,3,4,5);

     Supplier

> mapSupplier = () -> list.stream().collect(Collectors.toMap(x->x, y-> y * y));

 

     Map mapValueAdd = list.stream().collect(Collectors.toMap(x->x, y->y, (v1,v2) -> v1+v2, mapSupplier));

     System.out.println(mapValueAdd);

 

     List nums = Arrays.asList(Arrays.asList(1,2,3), Arrays.asList(1,4,9), Arrays.asList(1,8,27))

                                .stream().flatMap(x -> x.stream()).collect(Collectors.toList());

     System.out.println(nums);

 

     List fibo = Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream().collect(new FiboCollector());

     System.out.println(fibo);

 

     System.out.println(op(new Integer(3), Integer.valueOf(3)).apply((x,y) -> x.equals(y.toString())));

 

     System.out.println(op(x-> x.length(), y-> y+”,world”, “hello”).apply((x,y) -> x+” ” +y));

 

     System.out.println(op(x-> x, y-> y+”,world”).apply((x,y) -> x+” ” +y).apply(“hello”));

 

     System.out.println(op(x-> x.toString().length(), y-> y+”,world”).apply((x,y) -> x+” ” +y).apply(“hello”));

 

     System.out.println(mergeList(Arrays.asList(1,2), Arrays.asList(“an”, “a”),

                                  s-> s, t-> t.toString().length(), (s,t) -> s+t));

 

   }

 

}

小結

本文深入學習了Java8函式式程式設計框架:Function&Stream;&Collector;,並展示了函式式程式設計在實際應用中所帶來的諸多益處。函式式程式設計是一把大鋒若鈍的奇劍。基於函式介面程式設計,將函式作為資料自由傳遞,結合泛型推導能力,可編寫出精練、通用、易測的程式碼,使程式碼表達能力獲得飛一般的提升。

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂