java软件任务分组管理_了不起的Java-Stream流规约、分组、分区

问题引入

如果你在做一些汇总操作,比如

1、对一个交易列表按货币分组,获取每种货币的和(Map)

2、将交易分成贵的、不贵的(Map>)

3、多级分组,按城市分组,再按贵和不贵分组

如果是传统的写法,使用外部迭代即可,会有很多for+if组合,类似:

private static voidgroupImperatively() {

Map> transactionsByCurrencies = new HashMap<>();for(Transaction transaction : transactions) {

Currency currency=transaction.getCurrency();

List transactionsForCurrency =transactionsByCurrencies.get(currency);if (transactionsForCurrency == null) {

transactionsForCurrency= new ArrayList<>();

transactionsByCurrencies.put(currency, transactionsForCurrency);

}

transactionsForCurrency.add(transaction);

}

System.out.println(transactionsByCurrencies);

}

而使用Stream,可以用Collectors收集器:

Map> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency));

这个类提供很多工厂方法,主要分3类

1、规约、汇总;

2、分组

3、分区

使用数据

import java.util.*;public classDish {private finalString name;private final booleanvegetarian;private final intcalories;private finalType type;public Dish(String name, boolean vegetarian, intcalories, Type type) {this.name =name;this.vegetarian =vegetarian;this.calories =calories;this.type =type;

}publicString getName() {returnname;

}public booleanisVegetarian() {returnvegetarian;

}public intgetCalories() {returncalories;

}publicType getType() {returntype;

}public enumType { MEAT, FISH, OTHER }

@OverridepublicString toString() {returnname;

}public static final List 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, 400, Dish.Type.FISH),new Dish("salmon", false, 450, Dish.Type.FISH));

}

规约汇总

先统计之前例子里的数据,统计一共有多少菜。

menu.stream().collect(counting());

找出卡路里的最大和最小值

private staticDish findMostCaloricDishUsingComparator() {

Comparator dishCaloriesComparator =Comparator.comparingInt(Dish::getCalories);

BinaryOperator moreCaloricOf =BinaryOperator.maxBy(dishCaloriesComparator);returnmenu.stream().collect(reducing(moreCaloricOf)).get();

}

也可以用reduce来取最大最小值,推荐用法

private staticDish findMostCaloricDish() {return menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ?d1 : d2)).get();

}

汇总

private static intcalculateTotalCalories() {returnmenu.stream().collect(summingInt(Dish::getCalories));

}

平均数

private staticDouble calculateAverageCalories() {returnmenu.stream().collect(averagingInt(Dish::getCalories));

}

一次性获取最大、最小、平均、和

private staticIntSummaryStatistics calculateMenuStatistics() {returnmenu.stream().collect(summarizingInt(Dish::getCalories));

}

结果

Menu statistics: IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}

连接字符串

private staticString getShortMenu() {returnmenu.stream().map(Dish::getName).collect(joining());

}private staticString getShortMenuCommaSeparated() {return menu.stream().map(Dish::getName).collect(joining(", "));

}

广义规约reduce

以上的写法都是通过reduce来实现的,统统可以用reduce来写,比如总计

//总计,Lambda方式

private static intcalculateTotalCalories() {return menu.stream().collect(reducing(0, Dish::getCalories, (Integer i, Integer j) -> i +j));

}//使用方法引用来总计

private static intcalculateTotalCaloriesWithMethodReference() {return menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum));

}//不用Collectors的汇总

private static intcalculateTotalCaloriesWithoutCollectors() {returnmenu.stream().map(Dish::getCalories).reduce(Integer::sum).get();

}//IntStream方式

private static intcalculateTotalCaloriesUsingSum() {returnmenu.stream().mapToInt(Dish::getCalories).sum();

}

以上的方式,IntStream最好,一是比较直观,二是没有Integer的装箱,性能最佳。

分组groupingBy

也叫分类,使用groupingBy方法,参数是Function方法引用,也是分类函数,分组的输出一个map,key就是类型

定义:

public static Collector>>groupingBy(Function super T, ? extends K>classifier) {returngroupingBy(classifier, toList());

}public static Collector> groupingBy(Function super T, ? extends K>classifier,

Collector super T, A, D>downstream) {return groupingBy(classifier, HashMap::new, downstream);

}

例子

//单层分类

private static Map>groupDishesByType() {returnmenu.stream().collect(groupingBy(Dish::getType));

}//单层自定义分类

private static Map>groupDishesByCaloricLevel() {returnmenu.stream().collect(

groupingBy(dish->{if (dish.getCalories() <= 400) returnCaloricLevel.DIET;else if (dish.getCalories() <= 700) returnCaloricLevel.NORMAL;else returnCaloricLevel.FAT;

} ));

}//2层分类,第一层是类型,第二层是卡路里级别

private static Map>>groupDishedByTypeAndCaloricLevel() {returnmenu.stream().collect(

groupingBy(Dish::getType,

groupingBy((Dish dish)->{if (dish.getCalories() <= 400) returnCaloricLevel.DIET;else if (dish.getCalories() <= 700) returnCaloricLevel.NORMAL;else returnCaloricLevel.FAT;

} )

)

);

}//子分组计数

private static MapcountDishesInGroups() {returnmenu.stream().collect(groupingBy(Dish::getType, counting()));

}//子分组取最大值

private static Map>mostCaloricDishesByType() {returnmenu.stream().collect(

groupingBy(Dish::getType,

reducing((Dish d1, Dish d2)-> d1.getCalories() > d2.getCalories() ?d1 : d2)));

}//不使用Optional

private static MapmostCaloricDishesByTypeWithoutOprionals() {returnmenu.stream().collect(

groupingBy(Dish::getType,

collectingAndThen(

reducing((d1, d2)-> d1.getCalories() > d2.getCalories() ?d1 : d2),

Optional::get)));

}//子组汇总

private static MapsumCaloriesByType() {returnmenu.stream().collect(groupingBy(Dish::getType,

summingInt(Dish::getCalories)));

}//分组自定义转换

private static Map>caloricLevelsByType() {returnmenu.stream().collect(

groupingBy(Dish::getType, mapping(

dish-> { if (dish.getCalories() <= 400) returnCaloricLevel.DIET;else if (dish.getCalories() <= 700) returnCaloricLevel.NORMAL;else returnCaloricLevel.FAT; },

toSet() )));

}

分区partitioningBy

分区就是区分"是"or"非"的分组,分区里可以嵌套分组,定义

private static Map>partitionByVegeterian() {returnmenu.stream().collect(partitioningBy(Dish::isVegetarian));

}private static Map>>vegetarianDishesByType() {returnmenu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));

}private staticObject mostCaloricPartitionedByVegetarian() {returnmenu.stream().collect(

partitioningBy(Dish::isVegetarian,

collectingAndThen(

maxBy(comparingInt(Dish::getCalories)),

Optional::get)));

}

结果:

Dishes partitioned by vegetarian: {false=[pork, beef, chicken, prawns, salmon], true=[french fries, rice, season fruit, pizza]}

Vegetarian Dishes by type: {false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}, true={OTHER=[french fries, rice, season fruit, pizza]}}

Most caloric dishes by vegetarian: {false=pork, true=pizza}

分区的例子,求质数(素数)

即对于大于1的数,如果除了1和它本身,它不能再被其它正整数整除,那么我们说它是一个质数。

传统的判断一个数是不是质数的写法:

public static boolean isPrimeNormal(intnum) {for(int i=2; i

}

}return true;

}//优化的算法是只测试待测数平方根以下:

private static boolean isPrime(intsrc) {double sqrt =Math.sqrt(src);if (src < 2) {return false;

}if (src == 2 || src == 3) {return true;

}if (src % 2 == 0) {//先判断是否为偶数,若偶数就直接结束程序

return false;

}for (int i = 3; i <= sqrt; i+=2) {if (src % i == 0) {return false;

}

}return true;

}

Stream写法:

public static Map> partitionPrimes(intn) {return IntStream.rangeClosed(2, n).boxed()

.collect(partitioningBy(candidate->isPrime(candidate)));

}public static boolean isPrime(intcandidate) {return IntStream.rangeClosed(2, candidate-1)

.limit((long) Math.floor(Math.sqrt((double) candidate)) - 1)

.noneMatch(i-> candidate % i == 0);

}

结果

{false=[4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 34, 35, 36, 38, 39, 40, 42, 44, 45, 46, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 60, 62, 63, 64, 65, 66, 68, 69, 70, 72, 74, 75, 76, 77, 78, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 98, 99, 100], true=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]}

Map转换ToMap

ToMap 收集器用于收集流元素至 Map 实例,实现该功能需要提供两个函数:

keyMapper

valueMapper

keyMapper 用于从流元素中抽取map 的key,valueMapper抽取与可以关联的value。定义:

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

Function super T, ? extends U> valueMapper)

下面示例收集流元素至Map中,存储字符串作为key,其长度作为value:

List bookList = new ArrayList<>();

bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318"));

bookList.add(new Book("The Two Towers", 1954, "0345339711"));

bookList.add(new Book("The Return of the King", 1955, "0618129111"));public Map listToMap(Listbooks) {returnbooks.stream().collect(Collectors.toMap(Book::getIsbn, Book::getName));

}

Function.identity() 是一个预定义的返回接收参数的快捷函数。

如果我们集合中包含重复元素会怎么样?与toSet相反,toMap不能过滤重复元素。这个比较好理解————其如何确定key关联那个value?

List listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");

assertThatThrownBy(() -> {

listWithDuplicates.stream().collect(toMap(Function.identity(), String::length));

}).isInstanceOf(IllegalStateException.class);

我们看到,toMap甚至不判断值是否相等,如果key重复,立刻抛出IllegalStateException异常。要解决这个问题,我们需要使用另一种方法和附加参数mergefunction:

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

Function super T, ? extends U>valueMapper,

BinaryOperator mergeFunction)

BinaryOperator是合并函数

实验:

public Map listToMapWithDupKey(Listbooks) {returnbooks.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),

(existing, replacement)->existing));

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值