1. 程式人生 > >Collector收集器

Collector收集器


/**
 * @author Andypan
 * @CollectorTest.java
 * @{describe}
 * @date 2017年8月8日 下午3:36:02
 */
public class CollectorTest
{


@Test
public void test()
{
List<Dish> menu = Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT), new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER), new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER), new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH), new Dish("salmon", false, 450, Dish.Type.FISH));


// 查詢流中的最大值和最小值
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream().collect(Collectors.maxBy(dishCaloriesComparator));
System.out.println(mostCalorieDish.get());


// 彙總
int totalCalories = menu.stream().collect(Collectors.summingInt(Dish::getCalories));
// 平均
double avgCalories = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
// 通過一次summarizing操作你可以就數出選單中元素的個數,並得到菜餚熱量總和、平均值、最大值和最小值:
IntSummaryStatistics menuStatistics = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories));
System.out.println(menuStatistics);
// IntSummaryStatistics{count=9, sum=4200,
// min=120,average=466.666667,max=800}


// 連線字串
String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining(", "));


// 廣義的歸約彙總
// 它需要三個引數。
//  第一個引數是歸約操作的起始值,也是流中沒有元素時的返回值,所以很顯然對於數值
// 和而言0是一個合適的值。
//  第二個引數就是將菜餚轉換成一個表示其所含熱量的int。
//  第三個引數是一個BinaryOperator,將兩個專案累積成一個同類型的值。這裡它就是
// 對兩個int求和。
totalCalories = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j));


Optional<Dish> mostCalorieDish2 = menu.stream()
.collect(Collectors.reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));


// 分組
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(Collectors.groupingBy(Dish::getType));


// 分類函式不一定像方法引用那樣可用,因為你想用以分類的條件可能比簡單的屬性訪問器要複雜
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream().collect(Collectors.groupingBy(dish -> {
if (dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if (dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else
return CaloricLevel.FAT;
}));


// 多級分組
// 這種多級分組操作可以擴充套件至任意層級,n級分組就會得到一個代表n級樹形結構的n級
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream()
.collect(groupingBy(Dish::getType, groupingBy(dish -> {
if (dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if (dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else
return CaloricLevel.FAT;
})));


// 按子組收集資料
// 要數一數選單中每類菜有多少個,可以傳遞counting收集器作為groupingBy收集器的第二個引數:
Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));


Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream()
.collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories))));


// 把收集器的結果轉換為另一種型別
Map<Dish.Type, Dish> mostCaloricByType3 = menu.stream().collect(
groupingBy(Dish::getType, collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));


// 與groupingBy聯合使用的其他收集器的例子
// 然而常常和groupingBy聯合使用的另一個收集器是mapping方法生成的。這個方法接受兩
// 個引數:一個函式對流中的元素做變換,另一個則將變換的結果物件收集起來。其目的是在累加
// 之前對每個輸入元素應用一個對映函式,這樣就可以讓接受特定型別元素的收集器適應不同型別的物件。
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream()
.collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));


Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream()
.collect(groupingBy(Dish::getType, mapping(dish -> {
if (dish.getCalories() <= 400)
return CaloricLevel.DIET;
else if (dish.getCalories() <= 700)
return CaloricLevel.NORMAL;
else
return CaloricLevel.FAT;
}, toCollection(HashSet::new))));


/**************************** 分割槽 **********************************/


// 分割槽是分組的特殊情況:由一個謂詞(返回一個布林值的函式)作為分類函式,它稱分割槽函
// 數。分割槽函式返回一個布林值,這意味著得到的分組Map的鍵型別是Boolean,於是它最多可以
// 分為兩組——true是一組,false是一組
Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian));
// {false=[pork, beef, chicken, prawns, salmon],
// true=[french fries, rice, season fruit, pizza]}
List<Dish> vegetarianDishes = partitionedMenu.get(true);


// ,partitioningBy工廠方法有一個過載版本,可以像下面這樣傳遞第二個收集器:
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream()
.collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));


// 你可以重用前面的程式碼來找到素食和非素食中熱量最高的菜
Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = menu.stream().collect(partitioningBy(Dish::isVegetarian,
collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));


// (1) menu.stream().collect(partitioningBy(Dish::isVegetarian,
// partitioningBy (d -> d.getCalories() > 500)));
// (2) menu.stream().collect(partitioningBy(Dish::isVegetarian,
// partitioningBy (Dish::getType)));
// (3) menu.stream().collect(partitioningBy(Dish::isVegetarian,
// counting()));
// 答案如下。
// (1) 這是一個有效的多級分割槽,產生以下二級Map:
// { false={false=[chicken, prawns, salmon], true=[pork, beef]},
// true={false=[rice, season fruit], true=[french fries, pizza]}}
// (2) 這無法編譯,因為partitioningBy需要一個謂詞,也就是返回一個布林值的函式。
// 方法引用Dish::getType不能用作謂詞。
// (3) 它會計算每個分割槽中專案的數目,得到以下Map:
// {false=5, true=4}


}


// 現在最主要的一部分工作已經做好了。為了把前n個數字分為質數和非質數,只要建立一
// 個包含這n個數的流,用剛剛寫的isPrime方法作為謂詞,再給partitioningBy收集器歸約就好了
public Map<Boolean, List<Integer>> partitionPrimes(int n)
{
return IntStream.rangeClosed(2, n).boxed().collect(partitioningBy(candidate -> isPrime(candidate)));
}


public boolean isPrime1(int candidate)
{
return IntStream.range(2, candidate).noneMatch(i -> candidate % i == 0);
}


// 一個簡單的優化是僅測試小於等於待測數平方根的因子:
public boolean isPrime(int candidate)
{
int candidateRoot = (int) Math.sqrt((double) candidate);
return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
}


public enum CaloricLevel {
DIET, NORMAL, FAT
}
}