Java8实战笔记 -- Stream流相关

一、流与集合

1.1 只能遍历一次

类似迭代器,只能遍历一次,遍历过说明被消费掉。
在这里插入图片描述

1.2. 外部迭代与内部迭代

  • 外部迭代
    fore-ach
    在这里插入图片描述
    在这里插入图片描述
  • 内部迭代
    在这里插入图片描述

1.3 集合与流的差异

在这里插入图片描述

二、流操作

在这里插入图片描述
在这里插入图片描述

2.1中间操作

在这里插入图片描述

2.2 流的扁平化

eg:筛选出不同的字符[“Hello”,“World”]

words.stream().map(word -> word.split("")) 
			  .distinct()
              .collect(toList());

传递给map方法的Lambda为每个单词返回了一个String[](String列表)。因此,map返回的流实际上是Stream<String[]>类型在这里插入图片描述
使用map和Arrays.stream

String[] arrayOfWords = {"Goodbye", "World"}; 
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);

在这里插入图片描述
现在得到的是一个流的列表(更准确地说是 Stream)!的确,你先是把每个单词转换成一个字母数组,然后把每个数组变成了一 个独立的流。
使用flatmap
在这里插入图片描述
flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
在这里插入图片描述
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

2.3 查找和匹配

另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream
API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。

  • anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”
if(menu.stream().anyMatch(Dish::isVegetarian)){ 
    System.out.println("The menu is (somewhat) vegetarian friendly!!"); 
}

anyMatch方法返回一个boolean -》 终端操作。

  • allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词
//匹配所有菜的卡路里小于1000
boolean isHealthy = menu.stream()
                   .allMatch(d -> d.getCalories() < 1000);
  • noneMatch和allMatch相对。它可以确保流中没有任何元素与给定的谓词匹配。
//匹配没有菜品的卡路里大于1000
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
  • anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路
    findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian) .findAny();

Optional简介 Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在
上面的代码中,findAny可能什么元素都没找到。
Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法也不错。

  • isPresent()将在Optional包含值的时候返回true, 否则返回false。
  • ifPresent(Consumer block)会在值存在的时候执行给定的代码块。我们在第3章 介绍了Consumer函数式接口;它让你传递一个接收T类型参数,并返回void的Lambda 表达式。
  • T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
  • T orElse(T other)会在值存在时返回值,否则返回一个默认值。 例如,在前面的代码中你需要显式地检查Optional对象中是否存在一道菜可以访问其名称:
    在这里插入图片描述
  • findFirst 方法,它的工作方式类似于findany。可能想要找到第一个元素
//下面的代码能找出第一个平方 能被3整除的数:
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream()
					.map(x -> x * x)
					.filter(x -> x % 3 == 0) .findFirst(); // 9

在这里插入图片描述

2.4 元素求和

  • reduce 重复应用的模式做了抽象
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

reduce接受两个参数:

  • 一个初始值,这里是0;
  • 一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是
 lambda (a, b) -> a + b。

你也很容易把所有的元素相乘,只需要将另一个Lambda:(a, b) -> a * b传递给reduce 操作就可以了:

//乘
int product = numbers.stream().reduce(1, (a, b) -> a * b);

无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

返回optional 考虑流中没有任何元素的情况。reduce操作无法返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和 可能不存在。
最大值

Optional<Integer> max = numbers.stream().reduce(Integer::max);

最小值

Optional<Integer> min = numbers.stream().reduce(Integer::min);

在这里插入图片描述
同:

long count = menu.stream().count();

在这里插入图片描述

2.5 练习

基础数据的录入

Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
        new Transaction(brian, 2011, 300),
        new Transaction(raoul, 2012, 1000),
        new Transaction(raoul, 2011, 400),
        new Transaction(mario, 2012, 710),
        new Transaction(mario, 2012, 700),
        new Transaction(alan, 2012, 950)
);

排序


 //找出2011年的所有交易并按交易额排序(从低到高)
List<Transaction> collect = transactions.stream()
		 //过滤 year为2011你那
		 .filter(transaction -> transaction.getYear() == 2011)
		 //排序
         .sorted(Comparator.comparing(Transaction::getValue))
         .collect(Collectors.toList());
collect.stream().forEach( System.out::println);

通过map查到集合内某个属性的集合

//交易员都在哪些不同的城市工作过
 List<String> collect1 = transactions.stream()
 //获取城市
 .map(transaction -> transaction.getTrader().getCity())
 //去重
 .distinct()
 .collect(Collectors.toList());
 out.println(collect1.toString());
 ------------------------------------------------------
  //返回所有交易员的姓名字符串,按字母顺序排序
 String reduce = transactions.stream()
 //获取List<Trader>
 .map(Transaction::getTrader)
 //获取每个Trader中的name =>List<String>
 .map(Trader::getName)
 //去重
 .distinct()
 //排序
 .sorted()
 //讲List<String>拼接成一整个字符串
 .reduce("", (n1, n2) -> n1 + n2);
 //String collect = transactions.stream().map(Transaction::getTrader).map(Trader::getName).distinct().sorted().collect(Collectors.joining());
 //System.out.println(collect);

判断存在一个

//有没有交易员是在米兰工作的
 boolean milan = transactions.stream()
 //判断存在一个
 .anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
 out.println(milan);

累加

 //打印生活在剑桥的交易员的所有交易额
 Integer cambridge = transactions.stream()
 	  //筛选出城市为Cambridge
 	  .filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
 	  //获取城市为Cambridge的交易额
      .map(Transaction::getValue)
      //累加
      .reduce(0, (a, b) -> a + b);
      //.cout();
 out.println(cambridge);
 out.println(count);

最大值、最小值

 //所有交易中,最高的交易额是多少
 Optional<Integer> reduce = transactions.stream()
 	//获取交易额
 	.map(Transaction::getValue)
 	//最大值
 	.reduce(Integer::max);
 Integer max = reduce.get();
 out.println(max);
 -------------------------------------------------
  //找到交易额最小的交易
 Optional<Integer> min = transactions.stream()
 //获取交易额
 .map(Transaction::getValue)
 //最小值
 .min(Comparator.comparing(Integer::intValue));
 out.println(min);

2.6 数值流

原始类型流转换
IntStream、DoubleStream和LongStream
LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。

  1. 映射到数值流
//算流中元素的总和
int calories = transactions.stream() //返回Stream<Transaction>
        .mapToInt(Transaction::getValue).sum(); //返回intstream
out.println(calories);
  1. 转换回对象流
IntStream intStream = transactions.stream() 
                .mapToInt((Transaction::getValue); //返回intstream
Stream<Integer> stream = intStream.boxed(); //将Stream转 换为数值流

  1. 默认值OptionalInt
OptionalInt maxCalories = menu.stream()
.mapToInt(Dish::getCalories) .max();
//现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:
 int max = maxCalories.orElse(1);
//如果没有最大值的话,显 式提供一个默认最大值

数值范围rangeClosed

IntStream intStream = IntStream.rangeClosed(1, 100) //1-100是范围
.filter(n -> n % 2 == 0);

规约和汇总Collectors.counting()

long howManyDishes = menu.stream().collect(Collectors.counting());
//这还可以写得更为直接:
long howManyDishes = menu.stream().count();

查找流中的最大值和最小值maxBy/minBy

Comparator<Dish> dishCaloriesComparator = 
Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream()
.collect(maxBy(dishCaloriesComparator));

汇总summingInt

int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

平均值averagingInt
还有Collectors.averagingInt,连同对应的averagingLong和 averagingDouble可以计算数值的平均数

//平均值
Double collect1 = transactions.stream()
.collect(Collectors.averagingInt(Transaction::getValue));
out.println(collect1);

连接字符串

//连接字符串
String collect = transactions.stream()
        .map(transaction -> transaction.getTrader().getCity())
        //拼接 以空格分开
        .collect(Collectors.joining(" "));
out.println(collect);

//结果: Cambridge Cambridge Cambridge Milan Milan Cambridge

广义的归约汇总

//广义的归约汇总
Integer collect = transactions.stream()
	.collect(Collectors.reducing(0, Transaction::getValue, (a, b) -> a + b));
	out.println(collect);
	//输出4060
 	//最大值
    out.println(currentTimeMillis()); //1601025168614
    Optional<Transaction> collect = transactions.stream().collect(Collectors.maxBy(Comparator.comparingInt(Transaction::getValue)));
    out.println(currentTimeMillis()); //1601025168656
    Optional<Transaction> collect1 = transactions.stream().collect(Collectors.reducing((t1, t2) -> t1.getValue() > t2.getValue() ? t1 : t2));
    Transaction transaction = collect1.get();
    out.println(transaction.getValue()); 
    out.println(currentTimeMillis()); //1601025168657
}

分组:

分组:
Map<Dish.Type, List<Dish>> dishesByType =
	menu.stream().collect(groupingBy(Dish::getType)); 
//其结果是下面的Map:
//{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}

你可能想把热量不到400卡路里的菜划分为“低热量”(diet),热量400到700 卡路里的菜划为“普通”(normal),高于700卡路里的划为“高热量”(fat)。由于Dish类的作者 没有把这个操作写成一个方法,你无法使用方法引用,但你可以把这个逻辑写成Lambda表达式: public enum CaloricLevel { DIET, NORMAL, FAT }

Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
menu.stream().collect( groupingBy(dish -> {
	if (dish.getCalories() <= 400) 
		return CaloricLevel.DIET; 
	else if (dish.getCalories() <= 700)
		return CaloricLevel.NORMAL;
	else 
		return CaloricLevel.FAT; 
} ));

多级分组:

Map<String, Map<Integer, List<Transaction>>> collect = 
	transactions.stream().collect
	//根据city分组
	(Collectors.groupingBy(transaction -> transaction.getTrader().getCity(),
	//根据year分组
	Collectors.groupingBy(Transaction::getYear)));
	collect.keySet()
	.forEach(t -> out.println(t + "" + collect.get(t)));
------------------------------------------------------------------
输出
Milan{2012=[Transaction(trader=Trader(name=Mario, city=Milan), year=2012, value=710), 
Transaction(trader=Trader(name=Mario, city=Milan), year=2012, value=700)]}
Cambridge{2011=[Transaction(trader=Trader(name=Brian, city=Cambridge), year=2011, value=300), 
Transaction(trader=Trader(name=Raoul, city=Cambridge), year=2011, value=400)], 
2012=[Transaction(trader=Trader(name=Raoul, city=Cambridge), year=2012, value=1000), Transaction(trader=Trader(name=Alan, city=Cambridge), year=2012, value=950)]}

分区
分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函
数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以 分为两组——true是一组,false是一组。

Map<Boolean, List<Transaction>> collect = 
	transactions.stream()
	.collect(Collectors.partitioningBy(transaction -> transaction.getValue() > 800));

//输出:
{false=[Transaction(trader=Trader(name=Brian, city=Cambridge), year=2011, value=300), Transaction(trader=Trader(name=Raoul, city=Cambridge), year=2011, value=400), Transaction(trader=Trader(name=Mario, city=Milan), year=2012, value=710), Transaction(trader=Trader(name=Mario, city=Milan), year=2012, value=700)], true=[Transaction(trader=Trader(name=Raoul, city=Cambridge), year=2012, value=1000), Transaction(trader=Trader(name=Alan, city=Cambridge), year=2012, value=950)]}

分区的优势
分区的好处在于保留了分区函数返回true或false的两套流元素列表。

Map<Boolean, Map<String, List<Transaction>>> collect1 = 
transactions.stream().collect(Collectors.partitioningBy(transaction -> transaction.getValue() > 800, Collectors.groupingBy(transaction -> transaction.getTrader().getCity())));
out.println(collect1);

//输出
{false={Milan=[Transaction(trader=Trader(name=Mario, city=Milan), year=2012, value=710), 
Transaction(trader=Trader(name=Mario, city=Milan), year=2012, value=700)], Cambridge=[Transaction(trader=Trader(name=Brian, city=Cambridge), year=2011, value=300), 
Transaction(trader=Trader(name=Raoul, city=Cambridge), year=2011, value=400)]}, 
true={Cambridge=[Transaction(trader=Trader(name=Raoul, city=Cambridge), year=2012, value=1000), 
Transaction(trader=Trader(name=Alan, city=Cambridge), year=2012, value=950)]}}

在这里插入图片描述
在这里插入图片描述

备 工作遇到的问题解决

1 Stream流对字符串进行处理(处理excel导入的题目答案多选,进行处理)

public String handleRightAnswer(String answer) {
    if (answer.length() == NumberConstant.ONE || answer.length() == NumberConstant.ZERO) {
        return answer;
    }
    //String answer = "ABC";
    char[] chars = answer.toCharArray();
    List<String> oneAnswer = Arrays.asList(answer);
    String collect = oneAnswer.stream().map(choose -> choose.split(""))
            .flatMap(Arrays::stream).collect(Collectors.joining(","));
    //return IntStream.range(0, chars.length).mapToObj(i -> chars[i] +"").collect(Collectors.joining(","));
    answer = Stream.of(answer).map(choose -> choose.split("")).flatMap(Arrays::stream).collect(Collectors.joining(","));
    return answer;
    // 最后answer =  "A,B,C"
}

2 对List根据某一字段进行去重(对部门id相同的进行去重)

ArrayList<JSONObject> distinctDept = deptIdAndNameList.stream().collect(
        Collectors.collectingAndThen(Collectors.toCollection(
                () -> new TreeSet<>(comparing(a -> a.get("deptId").toString()))),
                ArrayList::new));
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值