天天看點

深層解析steam到lambda使用擴充,看完懂了不止一點點Stream 啟發之流式程式設計故事性範式Stream 深入之文法糖常見文法糖Lambda 語句文法糖Lambda 之函數式程式設計函數式接口函數參數化後函數式程式設計思維擴充應用分析Lambda 生産者程式設計分析示例:多數場景下的代碼收束利器Lambda 消費者程式設計分析示例:通用 Builder 實作Lambda 轉化者程式設計分析啟發應用示例:Feed流組裝範式 【2020-08-21】補充擴充思維

Stream 啟發之流式程式設計

流式程式設計是一種外表優美,邏輯自洽的一種程式設計範式,Jdk 很早就在做嘗試。

// Stream 出來之前就有很多流式程式設計實作,比如
String sql = new StringBuilder("select * from ").append(table).(" where 1=1 ").append(queryStr).toString();
// 自定義實作的構造者
JavaBean obj = JavaBeanBuilder.new().arg1(param1).arg2(param2).argN(paramN).bulid();
           

故事性

故事性:如果簡單的單行代碼表示單詞或短語,多行代碼完成一個閉環功能,我稱其描述了一個故事。

流式程式設計天然有一個故事模闆:
Start -> [Self Create] -> [Self Do 1] -> ... -> [Self Do n] -> [Output] -> End
就醬,一行語句描述一個代碼故事~
           

範式

流式程式設計的 [流對象] 至少有兩種方法:

  1. 過程方法 -> 消費Data或Function,傳回 this
  2. 結果方法 -> 傳回目标對象(或消費流結果)
// 用故事模闆講述一個故事
String story = new StringBuffer("Story:").append(man).append(action).append(woman).toString();

// Stream常見示例
List<student> result = students.stream().sorted(Comparator.comparing(Student::getSex)).collect(Collectors.toList());

Map<String, String> res = Stream.of(objs).collect(Collectors.toMap(Obj::getId, Obj::getName);
Map<String, List<Student>> classStudentsMap = students.stream().collect(Collectors.groupingBy(Student::ClassId));
Map<Long,List<Long>> result = list.stream().collect(Collectors.groupingBy(JavaBean::getKeyId, Collectors.mapping(JavaBean::getValue, Collectors.toList())));

List<String> ids = orders.stream().map(o -> o.getUserId()).distinct().collect(Collectors.toList());
long count = datas.stream().filter(d -> d.getScore() > 80).count();
String output = strs.stream().distinct().collect(Collectors.joining(","));
Optional<Book> thickBook = books.parallelStream().max(Comparator.comparing(Book::getPages));
datas.stream().forEach(JavaBean::doSomthing);

過程方法:filter、map、sorted、distinct...
結果方法:collect、count、max、min、forEach...
           

Stream 深入之文法糖

文法糖,底層文法衍生出的新的文法。

常見文法糖

反編譯揭秘一些文法糖:

// 自動拆裝箱
Integer i = 2;
int t = i;
i++;
↓
Integer i = Integer.valueOf(2);
int t = i.intgetValue();
i = Integer.valueOf(i.intgetValue() + 1)
// 數字加下劃線自動去下劃線
int num = 1000_0_0000;
long nums = 1000_0000000_0_0_0L;
↓
int num = 100000000;
long nums = 10000000000000L;
// 字元串相加
String str1 = "A" + "B";
String str5 = 4 + str1;
↓
String str1 = "AB";
(new StringBuilder()).append(4).append(str1).toString();
// while、for 循環底層基礎文法
while (i < 5) {
    i++;
}
↓
for (; i.intgetValue() < 5; i = Integer.valueOf(i.intgetValue() + 1)) ;
// case 語句
String str = "2";
switch (str) {
    case "2":
        g = 5;
        break;
    case "5":
        g = 10;
        break;
    default:
        g = 2;
}
↓
String str = "2";
Object integer = str; 
int i1 = -1; 
switch (((String)integer).hashCode()) { 
    case 50:
        if (((String)integer).equals("2")) i1 = 0; break;
    case 53:
        if (((String)integer).equals("5")) i1 = 1; break; 
} 
switch (i1) {
    case 0:
        g = 5;
        break;
    case 1:
        g = 10;
        break;
    default:
        g = 2;
}
// 枚舉
public static enum TestEnum {
    V1,V2
}
↓
public final class TestEnum extends Enum {
    private TestEnum(String s, int i) {
        super(s, i); 
    } 
    public static TestEnum[] values() { 
        TestEnum at[]; 
        int i; 
        TestEnum at1[]; 
        System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i); 
        return at1; 
    }
    public static TestEnum valueOf(String s) { 
        return (TestEnum)Enum.valueOf(TestEnum.class, s); 
    } 
 
    public static final TestEnum V1;
    public static final TestEnum V2; 
    private static final TestEnum ENUM$VALUES[]; 
    static { 
        V1 = new TestEnum("V1", 0); 
        V2 = new TestEnum("V2", 1); 
        ENUM$VALUES = (new TestEnum[] { 
            V1, V2
        }); 
    }
}
           

Lambda 語句文法糖

Lambda 語句文法糖分析

public static void main(String[] args) {
    Supplier supplier = () -> 3;
    Object a = supplier.get();
    Function function = (t) -> 4;
    Object b = function.apply(a);
    Consumer consumer = (t) -> {};
    consumer.accept(b);
    Predicate predicate = (t) -> false;
    predicate.test(a == b);
}

↓

public static void main(final String[] args) {
    final Supplier supplier = () -> Integer.valueOf(3);
    final Object a = supplier.get();
    final Function function = t -> Integer.valueOf(4);
    final Object b = function.apply(a);
    final Consumer consumer = t -> {};
    consumer.accept(b);
    final Predicate predicate = t -> false;
    predicate.test(Boolean.valueOf(a == b));
}
private static /* synthetic */ boolean lambda$main$3(final Object t) {
    return false;
}
private static /* synthetic */ void lambda$main$2(final Object t) {
}
private static /* synthetic */ Object lambda$main$1(final Object t) {
    return Integer.valueOf(4);
}
private static /* synthetic */ Object lambda$main$0() {
    return Integer.valueOf(3);
}

↓

檢視反編譯 main 函數的 TypeCode
javap -c LambdaSyntaxSugar
public static void main(java.lang.String[] args);
    Flags: PUBLIC, STATIC
    Code:
                linenumber      17
            0: invokedynamic   BootstrapMethod #0, get:()Ljava/util/function/Supplier;
            5: astore_1        /* supplier */
                linenumber      18
            6: aload_1         /* supplier */
            7: invokeinterface java/util/function/Supplier.get:()Ljava/lang/Object;
            12: astore_2        /* a */
                linenumber      20
            13: invokedynamic   BootstrapMethod #1, apply:()Ljava/util/function/Function;
            18: astore_3        /* function */
                linenumber      21
            19: aload_3         /* function */
            20: aload_2         /* a */
            21: invokeinterface java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
            26: astore          b
                linenumber      23
            28: invokedynamic   BootstrapMethod #2, accept:()Ljava/util/function/Consumer;
            33: astore          consumer
                linenumber      24
            35: aload           consumer
            37: aload           b
            39: invokeinterface java/util/function/Consumer.accept:(Ljava/lang/Object;)V
                linenumber      26
            44: invokedynamic   BootstrapMethod #3, test:()Ljava/util/function/Predicate;
            49: astore          predicate
                linenumber      27
            51: aload           predicate
            53: aload_2         /* a */
            54: aload           b
            56: if_acmpne       63
            59: iconst_1       
            60: goto            64
            63: iconst_0       
            64: invokestatic    java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
            67: invokeinterface java/util/function/Predicate.test:(Ljava/lang/Object;)Z
            72: pop            
                linenumber      28
            73: return

↓

檢視常量池:
javap -v LambdaSyntaxSugar

// 截取部分 BootstrapMethods 部分
BootstrapMethods:
  0: #56 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invok
e/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #57 ()Ljava/lang/Object;
      #58 invokestatic syntax/LambdaSyntaxSugar.lambda$main$0:()Ljava/lang/Object;
      #57 ()Ljava/lang/Object;
  1: #56 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invok
e/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #62 (Ljava/lang/Object;)Ljava/lang/Object;
      #63 invokestatic syntax/LambdaSyntaxSugar.lambda$main$1:(Ljava/lang/Object;)Ljava/lang/Object;
      #62 (Ljava/lang/Object;)Ljava/lang/Object;
  2: #56 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invok
e/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #67 (Ljava/lang/Object;)V
      #68 invokestatic syntax/LambdaSyntaxSugar.lambda$main$2:(Ljava/lang/Object;)V
      #67 (Ljava/lang/Object;)V
  3: #56 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invok
e/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #72 (Ljava/lang/Object;)Z
      #73 invokestatic syntax/LambdaSyntaxSugar.lambda$main$3:(Ljava/lang/Object;)Z
      #72 (Ljava/lang/Object;)Z
           

預設情況下生成的方法都是靜态方法,隻有在非靜态方法裡面使用 Lambda 且 Lambda 方法體裡面使用了非靜态成員方法或變量,才生成成員方法。

public class LambdaSyntaxSugar {
    private String str;
    public void not_main() {
        Supplier supplier = () -> 3;
        Supplier supplier2 = () -> this.str;
    }
}

↓

public class LambdaSyntaxSugar {
    private String str;
    public LambdaSyntaxSugar() {
        /*SL:14*/super();
    }
    public void not_main() {
        final Supplier supplier = /*EL:17*/() -> Integer.valueOf(3);
        final Supplier supplier2 = /*EL:18*/() -> this.str;
    }
    private /* synthetic */ Object lambda$not_main$1() {
        /*SL:18*/return this.str;
    }
    private static /* synthetic */ Object lambda$not_main$0() {
        /*SL:17*/return Integer.valueOf(3);
    }
}
           

綜上:

1. 編譯器會為每一個 lambda 表達式生成一個方法。
        方法名是 lambda$所在函數名$0,1,2,...,但方法引用的表達式不會生成方法。
2. 在 lambda 地方會産生一個 invokeDynamic 指令,這個指令會調用
        bootstrap(引導)方法,bootstrap 方法會指向自動生成的 lambda$所在函數名$0 方法或者方法引用的方法。
3. bootstrap方法使用上是調用了 LambdaMetafactory.metafactory 靜态方法
        該方法傳回了 CallSite (調用站點),裡面包含了 MethodHandle (方法句柄)也就是最終調用的方法。
4. 如果函數體裡面沒有使用 this,那麼就是 static 方法,否則就是成員方法
           

出色的反編譯工具:

  1. JD-GUI(要注意支援的 Jdk 版本)
  2. Luyten(推薦)
  3. CFR

Lambda 之函數式程式設計

萬物可對象的原則的話,Lambda 表達式都可以對應一種函數式接口實作,而文法裡也通過文法糖封裝實作了 Lambda 和函數式接口對象自動比對和轉化。

函數式接口

特征:

  1. 接口隻有一個未實作的方法。
  2. 接口上添加了 @FunctionalInterface 注解。
Jdk 提供了一系列預設的函數式接口(java.util.function.*),主要包含四大類:
1、Supplier<T>:T get();無輸入,“生産”一個T類型的傳回值。
2、Consumer<T>:void accept(T t);輸入類型 T,“消費”掉,無傳回。
3、Function<T, R>:R apply(T t);輸入類型T傳回類型R。
4、Predicate<T>:boolean test(T t);輸入類型T,并進行條件“判斷”,傳回true|false。特殊的 Function<T, Boolean>
           

Lambda 表達式自動識别變成接口函數的實作對象:

參數 -> 方法體

無參數+無傳回值:() -> {...} 無接收類型,作為變量傳輸需要自定義類型,雖然 Runnable 就是這種函數式接口,不建議直接使用。
無參+有傳回值:() -> {...;return x;} 為 Supplier<T> 實作接收
有參+無傳回值:(Object..) -> {...} 為 Consumer/BiConsumer<T..> 實作接收,至多兩個參數,更多參數需要自己定義 Consumer
有參+有傳回值:(Object) -> {...;return x;} 為 Function<T, R> 實作接收
           

函數參數化後

1、【性能提升】不用生成額外的匿名類,通過把函數式接口的匿名内部類方法的調用改成類靜态方法調用來完成。

2、【程式設計方式更靈活】将方法傳輸後,能攜帶更多的資訊。

  1. 普通參數 可以轉變成 生産者 Supplier 傳入,對象工廠表示:我們不再生産産品,我們隻提供專利。
  2. 多态或重載 可以轉變成 動态 Lambda 政策,參照 Collectors(Lambda 轉化者程式設計分析)
  3. 轉化業務可以政策化封裝,參照 Collectors(Lambda 轉化者程式設計分析)

函數式程式設計思維擴充

思考模式:生産者 and 消費者 + 粒度細化到函數

函數參數:入參、出參
1. 入參:無、有(資料、服務)
2. 出參:void、有(資料、服務)

排列組合:
1. 無入參+有出參:純生産者
2. 有入參+無出參:純消費者
3. 無入參+無出參:自産自銷(best 穩定)
4. 有入參+有出參:交易者

【異常】扮演的角色?  ->  淘汰機制。
           

應用分析

應用方向:學會将函數看成一種類型的參數,可以解鎖很多玩法。

Lambda 生産者程式設計分析

// 定義一個生産者
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
由于 Java 裡面隻能傳回一個對象,是以純生産者一般隻有一種定義和變化。

對于我們日常的程式設計,任何參數可以的演變方向:
method1(int a);
method2(JavaBean b);
method3(int a, JavaBean b);
↓
method1(Supplier<Integer> a);
method2(Supplier<JavaBean> b);
method3(Supplier<Integer> a, Supplier<JavaBean> b);
           

示例:多數場景下的代碼收束利器

Message msg = new Message();
// do something1
// do something2
// do something3
msg.setArg1(arg1);
msg.setArg2(arg3);
// do something4
msg.setArg3(arg3);
sender.send(msg);

↓

// do something1
// do something2
// do something3
// send msg
sender.send(() -> {
    Message msg = new Message();
    msg.setArg1(arg1);
    msg.setArg2(arg3);
    // do something4
    msg.setArg3(arg3);
    return msg;
});

預設構造器是一個天然的 Supplier<T>:
Supplier<JavaBean> supplier = JavaBean::new
JavaBean bean = supplier.get();

如果将構造器參數化,會有什麼好玩的東西呢?
沒想出來...
           

Lambda 消費者程式設計分析

// 定義一個消費者接口,一個參數可能的組合
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
↓
(t) -> t.doSomthing();
(t) -> [lambda$]doSomthing(t);

// 定義一個消費者接口,兩個參數可能的組合
@FunctionalInterface
public interface Consumer<T, U> {
    void accept(T t, U u);
}
↓
(t, u) -> t.doSomthing(u);
(t, u) -> [lambda$]doSomthing(t, u);

// 定義一個消費者接口,三個參數可能的組合

依次類推,取決于傳入的方法是與第一個參數是否相關,規律總結如下:
Comsumer<T, 其他類型..> -> accept(T t, 其他參數..)
1、[類T]::方法 -(其他參數),第一個參數t類型T,則調用:t.方法(其他參數)
2、[類A]::靜态方法 -(t, 其他參數),第一個參數t類型T,則調用:類A.靜态方法(t, 其他參數)
3、[對象obj-類T/A]::方法 -(t, 其他參數),調用:obj.方法(t, 其他參數)
4、(t, 其他參數) -> {...} ,自動生成的匿名函數:[static] lambda$目前函數名$1(t, 其他參數)
           

示例:通用 Builder 實作

函數式程式設計前的傳統實作:

public class JavaBeanBuilder {
    private JavaBean instant;
    public JavaBeanBuilder(JavaBean instant) {
        this.instant = instant;
    }
    public static JavaBeanBuilder builder() {
        return new JavaBeanBuilder(new JavaBean());
    }
    public JavaBeanBuilder arg1(Long value) {
        this.instant.setArg1(value);
        return this;
    }
    public JavaBeanBuilder arg2(String value) {
        this.instant.setArg2(value);
        return this;
    }
    ...
    public JavaBean build() {
        JavaBean result = this.instant;
        this.instant = null;
        return result;
    }

    public static void main(String[] args) {
        JavaBean bean = JavaBeanBuilder.builder().arg1(1L).arg2("2")...build();
        System.out.println(bean.getArg1());
    }
}
           

基于函數式程式設計的通用 Builder 實作:

public class Builder<T> {
    private T instant;
    public Builder(Supplier<T> supplier) {
        this.instant = supplier.get();
    }
    public static <T> Builder<T> of(Supplier<T> supplier) {
        return new Builder(supplier);
    }
    public <D> Builder<T> with(BiConsumer<T, D> consumer, D value) {
        consumer.accept(this.instant, value);
        return this;
    }
    public T build() {
        T result = this.instant;
        this.instant = null;
        return result;
    }
    public static void main(String[] args) {
        JavaBean bean = Builder.of(JavaBean::new).with(JavaBean::setArg1, 1L).with(JavaBean::setArg2, "2")...build();
        System.out.println(bean.getKeyId());
    }
}
           

Lambda 轉化者程式設計分析

Function<T, R> 轉化者是生産者和消費者特征的集合體,在 JDK 中表現最明顯的就是 Stream 程式設計中的 Collertors

Collertors 提供一系列靜态方法生成政策用于資料轉化,政策的通式如下:

static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
    private final Supplier<A> supplier;
    private final BiConsumer<A, T> accumulator;
    private final BinaryOperator<A> combiner;
    private final Function<A, R> finisher;
    private final Set<Characteristics> characteristics;

    CollectorImpl(Supplier<A> supplier,
                    BiConsumer<A, T> accumulator,
                    BinaryOperator<A> combiner,
                    Function<A,R> finisher,
                    Set<Characteristics> characteristics) {
        this.supplier = supplier;
        this.accumulator = accumulator;
        this.combiner = combiner;
        this.finisher = finisher;
        this.characteristics = characteristics;
    }

    CollectorImpl(Supplier<A> supplier,
                    BiConsumer<A, T> accumulator,
                    BinaryOperator<A> combiner,
                    Set<Characteristics> characteristics) {
        this(supplier, accumulator, combiner, castingIdentity(), characteristics);
    }

    @Override
    public BiConsumer<A, T> accumulator() {
        return accumulator;
    }

    @Override
    public Supplier<A> supplier() {
        return supplier;
    }

    @Override
    public BinaryOperator<A> combiner() {
        return combiner;
    }

    @Override
    public Function<A, R> finisher() {
        return finisher;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return characteristics;
    }
}

【中繼資料】接口中繼資料含義:
T:輸入的元素類型
A:累積結果的容器類型
R:最終生成的結果類型

【範式】各方法用途:
supplier: 建立新的結果容器
accumulator:将輸入元素合并到結果容器中
combiner:合并兩個結果容器(并行流使用,将多個線程産生的結果容器合并)
finisher:将結果容器轉換成最終的表示

↓

<R, A> R collect(Collector<? super T, A, R> collector);

例如:
...stream()...collect(Collectors.toList());

public static <T> Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                (left, right) -> { left.addAll(right); return left; },
                                // i -> (R) i
                                CH_ID);
}

...stream()....collect(Collectors.toCollection(TreeSet::new));

public static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
    return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                (r1, r2) -> { r1.addAll(r2); return r1; },
                                CH_ID);
}

...stream().....collect(Collectors.joining(","));

public static Collector<CharSequence, ?, String> joining() {
    return new CollectorImpl<CharSequence, StringBuilder, String>(
            StringBuilder::new, StringBuilder::append,
            (r1, r2) -> { r1.append(r2); return r1; },
            StringBuilder::toString, CH_NOID);
}

...stream()...collect(Collectors.groupingBy
           

啟發應用

業務通用轉化範式?

例如通用 Feed 流組裝業務:
Collector<T, A, R>  ->  BusinessCollector<DTO, List<Feed>, PageVO>
* supplier: 建立新的 List<Feed>
* accumulator:将輸入元素合并到結果容器中 DTO -> Feed
* combiner:合并兩個結果容器(并行流使用,将多個線程産生的結果容器合并) Feeds -> NewFeeds
* finisher:将結果容器轉換成最終的表示  Feeds -> PageVO
【擴充】
* filters:過濾器 List<Function> 額外過濾器,鍊式過濾處理,可插拔
* maps:通用業務映射器,實作不同場景的不同組裝

show me code ? TODO...
           

示例:Feed流組裝範式 【2020-08-21】補充

Feed流類結構:

{
    hasMore
    List<FeedResource> {
        ResourceType -> 視訊、MV、Mlog、廣告、直播、話題、圈子
        ...
        RelateResource.. {
            創作者
            藝人
            歌曲
            标簽
            權限
            評論
            ...
        }
    }
    msg
    ...
}
           

組裝流程:

FeedResourceIdWithType[]
↓
 ← GroupBy ResourceType
↓
Map<ResourceType, ResourceId[]>
↓
 ← ResourceConverter<ResourceId[],FeedResource(RelateResourceId[])[]> []
↓
FeedResource(RelateResourceId[])[] -> Map<RelateResourceType, RelateResourceId[]>
↓
 ← RelateResourceConverter<RelateResourceId[], Map<RelateResourceId, RelateResource>> []
↓
Map<RelateResourceId, RelateResource>[]
↓
 ← FeedResourceFiller<FeedResource(RelateResourceId[]), Map<RelateResourceId, RelateResource>[]>
↓
FeedResource(RelateResource[])[]
↓
 ← Rejust List and Package
↓
FeedResourcePage<FeedResource>(
    hasMore
    FeedResource(RelateResource[])[]
    msg
    ...
)
           

編碼僞代碼:

// 為了友善書寫,T[] 等價于 List<T>
class FeedResourceCollector<Map<ResourceType, ResourceId[]>, FeedResource(RelateResource[])[], FeedResourcePage> {
    private Map<ResourceType, FeedResourceFunction<ResourceId[], FeedResource(RelateResource[])[]>> feedResourceConvertors;
    private Map<ResourceType, FeedRelateResourceIdFunction<FeedResource, Map<RelateResourceType, RelateResourceId[]>>> feedResourceRelateIdConverts;
    private Map<RelateResourceType, RelateResourceFunction<RelateResourceId[], Map<RelateResourceId, RelateResource>>> relateResourceConvertors;
    private FeedResourceFillConsumer<FeedResource(RelateResource[])[], Map<RelateResourceType, Map<RelateResourceId, RelateResource>>> feedResourceFiller;
    private FeedResourcePackageFunction<FeedResource(RelateResource[])[], FeedResourcePage<FeedResource>> feedPackager;

    private void init() {
        // loading feedResourceConvertors
        feedResourceConvertors.put(VideoType, videoService::getByIds);
        feedResourceConvertors.put(MvType, mvService::getByIds);
        feedResourceConvertors.put(MlogType, mlogService::getByIds);
        feedResourceConvertors.put(AdType, adService::getByIds);
        feedResourceConvertors.put(LiveRoomType, liveRoomService::getByIds);
        feedResourceConvertors.put(TalkType, talkService::getByIds);
        feedResourceConvertors.put(CircleType, circleService::getByIds);
        ...
        // loading feedResourceRelateIdConverts
        relateResourceConvertors.put(VideoType, VideoResourceBean::getRelateIdsMap);
        relateResourceConvertors.put(MvType, MvResourceBean::getRelateIdsMap);
        relateResourceConvertors.put(MlogType, MlogResourceBean::getRelateIdsMap);
        relateResourceConvertors.put(AdType, AdResourceBean::getRelateIdsMap);
        relateResourceConvertors.put(LiveRoomType, LiveRoomResourceBean::getRelateIdsMap);
        relateResourceConvertors.put(TalkType, TalkResourceBean::getRelateIdsMap);
        relateResourceConvertors.put(CircleType, CircleResourceBean::getRelateIdsMap);
        // loading relateResourceConvertors
        relateResourceConvertors.put(CreatorType, creatorService::getByIds);
        relateResourceConvertors.put(ArtistType, artistService::getByIds);
        relateResourceConvertors.put(SongType, songService::getByIds);
        relateResourceConvertors.put(TagType, tagService::getByIds);
        relateResourceConvertors.put(PriviligeType, priviligeService::getByIds);
        relateResourceConvertors.put(CommentType, commentService::getByIds);
        ...
        // link feedResourceFiller
        feedResourceFiller = FillFeedResourceFiller::apply
        // link feedPackager
        feedPackager = NewFeedPackager::package
    }

    public FeedResourcePage<FeedResource> renderFeedPage(Map<ResourceType, ResourceId[]> resourceIdMap) {
        FeedResource(RelateResource[])[] feeds = new ArrayList<>();
        // 1、得到 主feed集合(關聯資源未填充,隻有相關ID)
        feedResourceConvertors.forEach(convertor -> {
            feeds.addAll(convertor.getValue().apply(resourceIdMap.get(convertor.getKey())));
        });
        // 2、提取 關聯資源ID,按 資源分類聚合
        Map<RelateResourceType, RelateResourceId[]> relateResourceIdsMap = new HashMap<>();
        feeds.forEach(feed -> {
            Map<RelateResourceType, RelateResourceId[]> tempIdsMap = feedResourceRelateIdConverts.get(feed.getResourceType()).apply(feed);
            // combine
            tempIdsMap.forEach(entry -> {
                relateResourceIdsMap.computeIfAbsent(entry.getKey(), ArrayList::new).addAll(entry.getValue());
            });
        });
        // 3、查詢關聯資源,按資源分類聚合
        Map<RelateResourceType, Map<RelateResourceId, RelateResource>> relateResourceMaps = new HashMap<>();
        relateResourceIdsMap.forEach(entry -> {
            relateResourceMaps.put(entry.getKey(), relateResourceConvertors.get(entry.getKey()).apply(entry.getValue()));
        });
        // 4、填充關聯資源到 主feed集合
        feedResourceFiller.apply(feeds, relateResourceMaps);
        // 5、打包 到 結果集
        return feedPackager.apply(feeds);
    }
}
           

擴充思維

如果随着作業系統(CPU愈發小巧廉價,單機可以裝上千個)或 JDK 的發展(并發的最小粒度不再是程序線程,而是方法棧),程式設計模式又會有怎樣的變化?

  1. 參照 GPU 圖計算模式,JDK 編譯器自動優化,分析出函數語句入參出參的依賴性,形成執行圖模型,實作方法棧并行執行。
  2. 參照 Serverless 和容器模型,當方法棧資料無狀态化後,實作函數分發執行。