Java-Lambda篇-第二章:流方法

第二章开始我们开始介绍这些Lambda语法糖,可以说这一章满满的都是一股甜味~

Java程序员在使用集合类的时候,一个通用模式是进行迭代操作,然后返回每个元素,这需要for循环,但是这样并行改造会变得非常麻烦。若是单一的for循环问题不大,但是面对一整个满是循环的庞大代码就显得负担很重。

我们的方法就是用内部迭代的方式,接下来我们必须理解Stream 。接下来我们掌握一些基本的Stream操作:

 

一、生成列表

collect(toList()) 方法可以由Stream里的值生成一个列表(List)。

Stream中的of方法可以使用一组初始值生成Stream组。*实际上collect用法不止于此。

List<String> coo = Stream.of("a","b","c").collect(Collectors.toList());

这个例子展示了示例代码的通用格式,首先由列表生成一个Stream,然后在Stream中进行一定的操作,最终由collect操作,由Stream生成列表。想象下,Stream是一个汉堡包,第一份步骤和最后一步都是给汉堡包加上第一片和最后一篇面包。

 

二、中间方法

中间方法相当于我们在汽车工厂中的各种机械臂,给汽车上发动机、喷漆、拧螺丝、拼接等等操作:

方法

简介

传入类型(接口)

返回值形式

JDK

map

接收一个转换函数作为参数,利用该函数将流中的参数转换成另一种形式.

Function<T,R>

Stream

8

filter

接收一个条件函数作为参数,返回满足函数条件(结果为true)的值或数组。

Predicate<T>

Stream

8

flatMap

接收一个转换函数作为参数,最终返回并平铺流中的所有结果(,,多维流转一维流)

Function<T,R>

Stream

8

sorted

接收一个比较器和get函数作为参数(数组、List类可不接收)对数组进行排序。

Comparator

Stream

8

distinct

去除流中的重复值。

-

Stream

 

Limit

接收一个参数,指定流最终返回的最大数量

-

Stream

 

skip

接收一个参数,指定流截断前n个数值,可与limit相互配合

-

Stream

 

takeWhile

接收一个条件函数,对数组中的值进行顺序条件判断,直至有参数返回false终止,并返回true数组

Predicate

Stream

9

dropWhile

接收一个条件函数,对数组中的值进行顺序条件判断,直至有参数返回false终止,并返回剩余数组

Predicate

Stream

9

iterate

生成一个无限流,使用迭代的方式,传入一个起始值和一个递增函数

 

 

 

generate

生成一个无限流,接受一个 Supplier类型的 Lambda 提供新的值

 

 

 

三、终端方法

终端方法是我们最终给汽车出厂的各种方法,是合格生产,还是定制化操作,它只能被消费一次,成功出产后这个流就不能再次被中间方法所操作了

方法

简介

传入类型(接口)

返回值形式

partitioningBy

接受一个条件函数,并返回Map<Boolen,List<T>>的集合,分为truefalse两组。

Collector

Map<Boolean, List<T>>

groupingBy

接受一个条件函数,并返回 Map<K, List<T>>的集合,根据不同的返回值分为不同的Map键。

Collector

Map<K, List<T>>

joining

接受一个函数,并返回String,其中可以传入三个参数,分隔符、起始符、结尾符。

Collector

String

count

统计流总数,和list.size()作用相同

-

Long

reduce

Steam类归约,设定一个初始值(或不设定),传入一个函数作为积累器,返回最终函数积累结果。

BinaryOperator

<T>,Optional

reducing

Collectors类归约,设定一个初始值(或不设定),传入一个函数作为积累器,返回最终函数积累结果。

-

<T>

forEach

接收一个函数,对其数组中所有的值进行迭代器操作,无返回值.

Consumer

无返回值

maxBy&minBy

接收一个比较器,返回Optional对象。

Comparator

Optional

allMatch

接收一个函数,如果全部元素符合函数条件,则返回true,否则flase

Function<T,R>

Boolean

anyMatch

接收一个函数,如果任意元素符合函数条件,则返回true,否则flase

Function<T,R>

Boolean

noneMatch

接收一个函数,如果没有元素符合函数条件,则返回true,否则flase

Function<T,R>

Boolean

FindAny

返回数组中随机的值(需要在并行流下,且数据量较大的时候,才有效果)

-

Optional

FindFirst

返回数组中首个的值

-

Optional

max & min

传入一个比较器并接受一个get函数,将根据比较器和get函数的值返回Optional对象。

Comparator

Optional

summingInt

传入累计函数,返回数值类型,可以是返回Int,Long,Double类型

-

Int/Long/Double

summarizingInt

传入累计函数,返回一个IntSummaryStatistics,它包含了数值数量,平均数,最大,最小等等数据

-

IntSummaryStatistics

 接下来我们可以测试这些方法的使用模式了:

中间方法介绍:

1.筛选方法 filter

提示:遍历数据并检查其中元素的时候可以使用filter,它接受(传入)一个函数作为参数,返回满足函数条件(结果为true)的值或数组。

实例1:

Stream<Integer> test1 = Stream.of(10,5,20);
//生成流
Predicate<Integer> asLset10 = x -> x>5;
//书写Lambda表达式
test1 = test1.filter(asLset10);
//传入函数给流
system.out.println(test1.collect(Collectors.toList()));
//流最终操作转化为List并打印List

实例2:

List<Dish> vegetarianMenu = menu.stream() 
                                .filter(Dish::isVegetarian)  //检查是否是素食者
                                .collect(toList());          //最终操作,转化为List

2.筛选重复项 distinct

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); 
numbers.stream() 
              .filter(i -> i % 2 == 0) 
              .distinct() 
              .forEach(System.out::println);

3.切片 takeWhile,dropWhile

Java 9 引入的新方法,它可以高效的选择和丢弃元素中的元素,例如忽略前几个元素或者按照设定大小对流实施截断。

List<Dish> specialMenu = Arrays.asList( 
new Dish("seasonal fruit", true, 120, Dish.Type.OTHER), 
new Dish("prawns", false, 300, Dish.Type.FISH), 
new Dish("rice", true, 350, Dish.Type.OTHER), 
new Dish("chicken", false, 400, Dish.Type.MEAT), 
new Dish("french fries", true, 530, Dish.Type.OTHER)); 
//添加菜肴(已经排好序了)

//takeWhile:筛选直到卡路里大于或等于320的菜肴为止,触碰到结果为Fales时返回已经检查过的结果集。
List<Dish> slicedMenu1 = specialMenu.stream() 
                                    .takeWhile(dish -> dish.getCalories() < 320) 
                                    .collect(toList()); 

//dropWhile:返回筛选卡路里大于或者等于320的菜肴剩余的数组,触碰到结果为Fales时返回剩余未检查的结果集。
List<Dish> slicedMenu1 = specialMenu.stream() 
                                    .dropWhile(dish -> dish.getCalories() < 320) 
                                    .collect(toList()); 

 这两个方法并非不能一同使用的,同时使用的话,我们可以取得卡路里在320和420之间的菜肴,你可以自己尝试一范。

4.截断流 limit(n)skip(n)

如果你用过SQL limit关键字恐怕已经颤动了你的DNA,没错,它可以限制输出多少结果,保证最终只有多少结果存在于List中,而skip相反,它可以设定跳过多少结果,前几个对象并不会被List所收录。

//limit,限制数据返回数量
List<Dish> dishes = specialMenu.stream() 
                               .filter(dish -> dish.getCalories() > 300)
                               .limit(3)  //返回三个数据
                               .collect(toList());
//skip,跳过前置数据
List<Dish> dishes = menu.stream()
                  .filter(d -> d.getCalories() > 300) 
                  .skip(2)   //跳过前两个数据
                  .collect(toList());

//二者合二为一,可导致数据无返回(操作猛如虎,智商二百五)
List<Dish> specialMenu = Arrays.asList( 
new Dish("seasonal fruit", true, 120, Dish.Type.OTHER), 
new Dish("prawns", false, 300, Dish.Type.FISH));

List<Dish> dishes = menu.stream()
                  .skip(2)   //跳过前两个数据
                  .filter(d -> d.getCalories() > 300) 
                  .limit(2)  //返回两个数据
                  .collect(toList());
//总共就两个数据,你还都跳过了,数据都没啦!

 5.映射 map

map可以将数组中的每个值都进行函数操作,并进行转化为一个全新的流,例如对List<String> 进行map操作后,你可以得到List<Integer>!

//实例:
List<Integer> dishNameLengths = menu.stream() 
                                    .map(Dish::getName) //菜谱List<Dish> 转化为 List<String>
                                    .map(String::length) //名称List<String> 转化为 List<Interger>
                                    .collect(toList());//最终操作

6.平铺 flatMap

哦,这个比较难以理解,在你深入学习后可能会遇到这样的情况:Optional<Optional>,包装对象中还包装着包装对象,这会不会很难懂?简单来说flatMap遇到二维数组时会将它平铺为一维数组,来应对流中还嵌套着流的情况。

static int student = 1;//学生编号
static int  group = 1;//小组编号

List<String[]> eggs = new ArrayList<>();
eggs.add(new String[]{"鸡蛋1","鸡蛋1","鸡蛋1","鸡蛋1","鸡蛋1"});
eggs.add(new String[]{"鸡蛋2","鸡蛋2","鸡蛋2","鸡蛋2","鸡蛋2"});

//map方法:
 eggs.stream()
     .map(x-> Arrays.stream(x).map(y -> y.replace("鸡","荷包")))
     .forEach(x -> System.out.println("组"+ group++ + ":" + Arrays.toString(x.toArray())));
//组1:[荷包蛋1, 荷包蛋1, 荷包蛋1, 荷包蛋1, 荷包蛋1]
//组2:[荷包蛋2, 荷包蛋2, 荷包蛋2, 荷包蛋2, 荷包蛋2]

//flatmap方法:
eggs.stream()
    .flatMap(x-> Arrays.stream(x).map(y -> y.replace("鸡","荷包")))
    .forEach(x -> System.out.println("学生"+ student++ + ":" + x));
//学生1:荷包蛋1
//学生2:荷包蛋1
//学生3:荷包蛋1
//学生4:荷包蛋1
//学生5:荷包蛋1
//学生6:荷包蛋2
//学生7:荷包蛋2
//学生8:荷包蛋2
//学生9:荷包蛋2
//学生10:荷包蛋2

7. 随机返回 findAny

顾名思义

Optional<Dish> dish = menu.stream() 
                          .filter(Dish::isVegetarian) 
                          .findAny();
                          .ifPresent(dish -> System.out.println(dish.getName());如果包含值则打印,否则什么都不做

8. 首个值 findFirst

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5); 
Optional firstSquareDivisibleByThree = someNumbers.stream() 
                                                  .map(n -> n * n) 
                                                  .filter(n -> n % 3 == 0) 
                                                  .findFirst(); // 9 

9. 元素求和 reduce

可以设定一个初始值,接收一个函数作为积累器,返回最终的结果。如果不设定初始值,那么会返回一个Optional对象

Stream<Integer> myTest2 = Stream.of(4,2,4,2,4);

int cont = myTest2.reduce(0,Integer::sum);
System.out.println(cont);

//reduce操作中第一参数的 0 ,表示以 0 为起点,目前展示的是求和,也可以求差,求乘积等等操作,
//要注意的是,如果是乘积就不能以0为开头了,否则0乘以任何数都是0!

10. 最大最小值 max&min

顾名思义,返回列表中的最大值

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

 最终方法介绍:

1.最大值和最小值

Collectors.maxBy Collectors.minBy 来计算流中的最大值或最小值。

Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories); 
//建立一个比较器对Int值作比较
Optional<Dish> mostCalorieDish = menu.stream() 
                                     .collect(Collectors.maxBy(dishCaloriesComparator));
//传入比较器,得到最大的Dish

 2.汇总 summingInt

Collectors.summingInt 它接受一个把对象映射所求和所需int的函数,并返回一个收集器

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

Collectors.summingLong 和 Collectors.summingDouble 方法的作用完全一样,可以用于求和字段为 long 或 double 的情况。

 

但汇总不仅仅是求和;还有 Collectors.averagingInt,连同对应的 averagingLong averagingDouble 可以计算数值的平均数:

double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories)); 

但如果你需要获得更多这样的结果你可以使用summarizingInt 工厂方法返回的收集器,它可以一次性获得以上所有的结果:

IntSummaryStatistics menuStatistics = menu.stream()
                                          .collect(summarizingInt(Dish::getCalories));
//你可以观察到一个非常特殊的结果:
//IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}

同理,还有的summarizingLongsummarizingDouble工厂方法有相关的LongSummaryStatistics DoubleSummaryStatistics 类型,适用于收集的属性是原始类型 long double 的情况。

 

3.字符串串联 joining

joining 工厂方法返回的收集器会把对流中每一个对象应用 toString 方法得到的所有字符串连接成一个字符串。

String shortMenu = menu.stream().map(Dish::getName).collect(joining());
//porkbeefchickenfrench friesriceseason fruitpizzaprawnssalmon
//中间没有标点符号,所以这些菜谱名字都结到一块去了

 这样做的可读性并不好,所以joing有个重载版本可以接受元素之间的分界符:

String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
//pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon

同时,如果还可以添加前缀符和后缀符

String shortMenu = menu.stream().map(Dish::getName).collect(joining(", ","[","]"));
//[pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]

4. 归约汇总 reducing

Collectors.reducing 工厂方法也可以直接返回一个归约汇总:

int totalCalories = menu.stream()
                        .collect(reducing( 0,Dish::getCalories,(i, j) -> i + j));

它需要三个参数。

  • 第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值 和而言 0 是一个合适的值。
  • 第二个参数就是将菜肴转换成一个表示其所含热量的 int。
  • 第三个参数是一个 BinaryOperator 函数表达式,描述了将两个项目如何进行累积操作。这里它就是 对两个 int 求和。

同理也能通过这种方式来找到热量最高的菜:

Optional mostCalorieDish = menu.stream()
                               .collect(reducing((d1, d2)->d1.getCalories() > d2.getCalories() ? d1 : d2));

而这种单参数的reducing是一种特殊情况,它把流中的第一个项目作为起点,把恒等函数作为一个转换函数.这意味着它没有起点,最终会返回一个Optional<Dish>对象.

 

reduce方法和Stream接口的collect方法有何不同?

reduce方法旨在把两个值结合起来生成一个新值,它是一个不可变的归约。与此相反,collect方法的设计就是要改变容器,从而累积要输出的结果。

在多线程并发修改同一个数据结构很可能会导致List本身遭到破坏!所以你如果需要保证线程安全的情况下collect方法特别适合表达可变容器上的归约

 

进一步理解:

int totalCalories = menu.stream()
                        .collect(reducing(0, //初始值
                        Dish::getCalories,   //转换函数
                        Integer::sum));      //积累函数

但这里有个要点,必须要注意:

List<Dish> menu = dishService.getDishList();

//实例1:
String shortMenu = menu.stream()
                       .map(Dish::getName)
                       .collect( reducing ( (s1, s2) -> s1 + s2 ) ).get();
//这里获取的字符串,而没有初始值的reducing返回的也是字符串

//实例2:
String shortMenu = menu.stream()
                       .collect(reducing( "",Dish::getName, (s1, s2) -> s1 + s2 ) );
//这里初始值是字符串类型,而不断叠加的对象也是字符串类型

//实例3
String shortMenu = menu.stream()
                       .collect(reducing((d1, d2) -> d1.getName() + d2.getName())).get();
//这里就错误了,传入的时Dish对象流,而最终收集者要求返回String对象流,前后不一致导致无法编译。

5.分组 groupingBy

Collectors.groupingBy工厂方法返回 的收集器就可以轻松地完成类似于数据库分组的任务,如下所示:

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

分组有个重载方法,允许我们传入一个Collector类型的参数,通过这种方式,我们可以传入过滤的谓词(filtering),现在我们想要筛选出卡路里大于500的菜肴

Map<Dish.Type,List<Dish> caloricDishesByType = menu.stream() 
.collect(groupingBy(Dish::getType,
                    filtering(dish -> dish.getCalories() > 500, toList()))); 

//{OTHER=[french fries, pizza], MEAT=[pork, beef], FISH=[]} 

如果你需要转换类型,那么你可以使用mapping

Map<Dish.Type,List<Dish>> dishNamesByType = menu.stream() 
.collect(groupingBy(Dish::getType, 
                    mapping(Dish::getName, toList())));

//{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}

6.partitioningBy 分区

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

Map<Boolen,List<Dish>> partitionedMenu = menu.stream()
                                             .collect(partitioningBy(Dish::isVegetarian)); 
                                             //分区函数,判断条件就是蔬菜,返回Boolean
//结果:
//{false=[pork, beef, chicken, prawns, salmon],
// true=[french fries, rice, season fruit, pizza]} 

 相比于groupingBy,partitioningBy的效果更加清晰,紧凑,高效.

 实战之质数分区

1.首先我们需要将前n个自然数分为指数和非质数,但在这之前,找出测试某个待测数字是否是质数的谓词会很有帮助:

public boolean isPrime(int candidate){ 
     return IntStream.rangeClosed(2, candidate) //range出从2到传入整数的所有数字
                     .noneMatch(i -> candidate % i == 0); //所有数字去除去传入的整数,如果等于0代表它不是质数
     //如果该数字不能被任何数字整除,那么则返回true
 }

 2.优化操作,如果使用平方根,那么我们的操作会少不少

public boolean isPrime(int candidate){ 
   int candidateRoot = (int) Math.sqrt((double) candidate); //对传入的整数进行平方根操作;
   return IntStream.rangeClosed(2, candidateRoot ) 
                   .noneMatch(i -> candidate % i == 0); //列出从2到平方根得所有整数去整除;
}

3.分流操作:

public Map<Boolean,List<Integer>> partitionPrimes(int n) { 
    return IntStream.rangeClosed(2, n).boxed() 
                         //这个是个Int特化流,所以并没有进行装箱,所以需要boxed()进行装箱
                         .collect( 
                              partitioningBy(candidate -> isPrime(candidate))); 
}

非常简单~

posted @   木半夏曦  阅读(308)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示