1. 程式人生 > 程式設計 >Java12 Collectors.teeing 你真的需要了解一下

Java12 Collectors.teeing 你真的需要了解一下

前言

在 Java 12 裡面有個非常好用但在官方 JEP 沒有公佈的功能,因為它只是 Collector 中的一個小改動,它的作用是 merge 兩個 collector 的結果,這句話顯得很抽象,老規矩,我們先來看個圖:

管道改造經常會用這個小東西,通常我們叫它「三通」,它的主要作用就是將 downstream1 和 downstream2 的流入合併,然後從 merger 流出

有了這個形象的說明我們就進入正題吧

Collectors.teeing

上面提到的小功能就是 Collectors.teeing API,先來看一下 JDK 關於該 API 的說明,看著覺得難受的直接忽略,繼續向下看例子就好了:

/**
 * Returns a {@code Collector} that is a composite of two downstream collectors.
 * Every element passed to the resulting collector is processed by both downstream
 * collectors,then their results are merged using the specified merge function
 * into the final result.
 *
 * <p>The resulting collector functions do the following:
 *
 * <ul>
 * <li>supplier: creates a result container that contains result containers
 * obtained by calling each collector's supplier
 * <li>accumulator: calls each collector's accumulator with its result container
 * and the input element
 * <li>combiner: calls each collector's combiner with two result containers
 * <li>finisher: calls each collector's finisher with its result container,* then calls the supplied merger and returns its result.
 * </ul>
 *
 * <p>The resulting collector is {@link
Collector.Characteristics#UNORDERED} if both downstream * collectors are unordered and {@link Collector.Characteristics#CONCURRENT} if both downstream * collectors are concurrent. * * @param <T> the type of the input elements * @param <R1> the result type of the first collector * @param
<R2> the result type of the second collector * @param <R> the final result type * @param downstream1 the first downstream collector * @param downstream2 the second downstream collector * @param merger the function which merges two results into the single one * @return a {@code Collector} which aggregates the results of two supplied collectors. * @since 12 */
public static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,R1> downstream1,Collector<? super T,R2> downstream2,BiFunction<? super R1,? super R2,R> merger) { return teeing0(downstream1,downstream2,merger); } 複製程式碼

API 描述重的一句話非常關鍵:

Every element passed to the resulting collector is processed by both downstream collectors 結合「三通圖」來說明就是,集合中每一個要被傳入 merger 的元素都會經過 downstream1 和 downstream2 的加工處理

其中 merger 型別是 BiFunction,也就是說接收兩個引數,並輸出一個值,請看它的 apply 方法

@FunctionalInterface
public interface BiFunction<T,U,R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t,U u);
}
複製程式碼

至於可以如何處理,我們來看一些例子吧

例子

為了更好的說明 teeing 的使用,列舉了四個例子,看過這四個例子再回看上面的 API 說明,相信你會柳暗花明了

計數和累加

先來看一個經典的問題,給定的數字集合,需要對映整數流中的元素數量和它們的和

class CountSum {
	private final Long count;
	private final Integer sum;
	public CountSum(Long count,Integer sum) {
		this.count = count;
		this.sum = sum;
	}

	@Override
	public String toString() {
		return "CountSum{" +
				"count=" + count +
				",sum=" + sum +
				'}';
	}
}
複製程式碼

通過 Collectors.teeing 處理

CountSum countsum = Stream.of(2,11,1,5,7,8,12)
        .collect(Collectors.teeing(
                counting(),summingInt(e -> e),CountSum::new));

System.out.println(countsum.toString());
複製程式碼
  • downstream1 通過 Collectors 的靜態方法 counting 進行集合計數
  • downstream2 通過 Collectors 的靜態方法 summingInt 進行集合元素值的累加
  • merger 通過 CountSum 構造器收集結果

執行結果:

CountSum{count=7,sum=46}

我們通過 teeing 一次性得到我們想要的結果,繼續向下看其他例子:

最大值與最小值

通過給定的集合, 一次性計算出集合的最大值與最小值,同樣新建一個類 MinMax,並建立構造器用於 merger 收集結果

class MinMax {
	private final Integer min;
	private final Integer max;
	public MinMax(Integer min,Integer max) {
		this.min = min;
		this.max = max;
	}

	@Override
	public String toString() {
		return "MinMax{" +
				"min=" + min +
				",max=" + max +
				'}';
	}
}
複製程式碼

通過 teeing API 計算結果:

MinMax minmax = Stream.of(2,12)
        .collect(Collectors.teeing(
                minBy(Comparator.naturalOrder()),maxBy(Comparator.naturalOrder()),(Optional<Integer> a,Optional<Integer> b) -> new MinMax(a.orElse(Integer.MIN_VALUE),b.orElse(Integer.MAX_VALUE))));

System.out.println(minmax.toString());
複製程式碼
  • downstream1 通過 Collectors 的靜態方法 minBy,通過 Comparator 比較器按照自然排序找到最小值
  • downstream2 通過 Collectors 的靜態方法 maxBy,通過 Comparator 比較器按照自然排序找到最大值
  • merger 通過 MinMax 構造器收集結果,只不過為了應對 NPE,將 BiFunction 的兩個入參經過 Optional 處理

執行結果:

MinMax{min=1,max=12}

為了驗證一下 Optional,我們將集合中新增一個 null 元素,並修改一下排序規則來看一下排序結果:

MinMax minmax = Stream.of(null,2,12)
				.collect(Collectors.teeing(
						minBy(Comparator.nullsFirst(Comparator.naturalOrder())),maxBy(Comparator.nullsLast(Comparator.naturalOrder())),b.orElse(Integer.MAX_VALUE))));
複製程式碼
  • downstream1 處理規則是將 null 放在排序的最前面
  • downstream2 處理規則是將 null 放在排序的最後面
  • merger 處理時,都會執行 optional.orElse 方法,分別輸出最小值與最大值

執行結果:

MinMax{min=-2147483648,max=2147483647}

瓜的總重和單個重量

接下來舉一個更貼合實際的操作物件的例子

// 定義瓜的型別和重量
class Melon {
	private final String type;
	private final int weight;
	public Melon(String type,int weight) {
		this.type = type;
		this.weight = weight;
	}

	public String getType() {
		return type;
	}

	public int getWeight() {
		return weight;
	}
}

// 總重和單個重量列表
class WeightsAndTotal {
	private final int totalWeight;
	private final List<Integer> weights;
	public WeightsAndTotal(int totalWeight,List<Integer> weights) {
		this.totalWeight = totalWeight;
		this.weights = weights;
	}

	@Override
	public String toString() {
		return "WeightsAndTotal{" +
				"totalWeight=" + totalWeight +
				",weights=" + weights +
				'}';
	}
}
複製程式碼

通過 teeing API 計算總重量和單個列表重量

List<Melon> melons = Arrays.asList(new Melon("Crenshaw",1200),new Melon("Gac",3000),new Melon("Hemi",2600),1600),new Melon("Apollo",new Melon("Horned",1700),2600)
);


WeightsAndTotal weightsAndTotal = melons.stream()
    .collect(Collectors.teeing(
            summingInt(Melon::getWeight),mapping(m -> m.getWeight(),toList()),WeightsAndTotal::new));

System.out.println(weightsAndTotal.toString());
複製程式碼
  • downstream1 通過 Collectors 的靜態方法 summingInt 做重量累加
  • downstream2 通過 Collectors 的靜態方法 mapping 提取出瓜的重量,並通過流的終結操作 toList() 獲取結果
  • merger 通過 WeightsAndTotal 構造器獲取結果

執行結果:

WeightsAndTotal{totalWeight=19500,weights=[1200,3000,2600,1600,1200,1700,2600]}

繼續一個更貼合實際的例子吧:

預約人員列表和預約人數

class Guest {
	private String name;
	private boolean participating;
	private Integer participantsNumber;

	public Guest(String name,boolean participating,Integer participantsNumber) {
		this.name = name;
		this.participating = participating;
		this.participantsNumber = participantsNumber;
	}
	public boolean isParticipating() {
		return participating;
	}

	public Integer getParticipantsNumber() {
		return participantsNumber;
	}

	public String getName() {
		return name;
	}
}

class EventParticipation {
	private List<String> guestNameList;
	private Integer totalNumberOfParticipants;

	public EventParticipation(List<String> guestNameList,Integer totalNumberOfParticipants) {
		this.guestNameList = guestNameList;
		this.totalNumberOfParticipants = totalNumberOfParticipants;
	}

	@Override
	public String toString() {
		return "EventParticipation { " +
				"guests = " + guestNameList +
				",total number of participants = " + totalNumberOfParticipants +
				" }";
	}
}
複製程式碼

通過 teeing API 處理

var result = Stream.of(
                new Guest("Marco",true,3),new Guest("David",false,2),new Guest("Roger",6))
                .collect(Collectors.teeing(
                        Collectors.filtering(Guest::isParticipating,Collectors.mapping(Guest::getName,Collectors.toList())),Collectors.summingInt(Guest::getParticipantsNumber),EventParticipation::new
                ));
System.out.println(result);
複製程式碼
  • downstream1 通過 filtering 方法過濾出確定參加的人,並 mapping 出他們的姓名,最終放到 toList 集合中
  • downstream2 通過 summingInt 方法計數累加
  • merger 通過 EventParticipation 構造器收集結果

其中我們定義了 var result 來收集結果,並沒有指定型別,這個語法糖也加速了我們程式設計的效率

執行結果:

EventParticipation { guests = [Marco,Roger],total number of participants = 11 }

總結

其實 teeing API 就是靈活應用 Collectors 裡面定義的靜態方法,將集合元素通過 downstream1 和 downstream2 進行處理,最終通過 merger 收集起來,當專案中有同時獲取兩個收集結果時,是時候應用我們的 teeing API 了

靈魂追問

  1. Collectors 裡面的靜態方法你應用的熟練嗎?
  2. 專案中你們在用 JDK 的版本是多少?
  3. Lambda 的使用熟練嗎?
  4. 你的燈還亮著嗎?

  1. Java併發系列文章,持續更新中
  2. Maven 依賴傳遞性透徹理解
  3. 讀取Excel還用POI?試試這個開源框架
  4. 基礎面試,為什麼面試官總喜歡問Java String

以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本著將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注......