Java8 Lambda、方法引用和stream流示例

本文深入探讨Java8的重要新特性,包括Lambda表达式、方法引用、流API、并行流和收集器。详细讲解如何使用这些特性简化代码,提高开发效率和程序性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public class Apple {

	private String color;
	private Double weight;
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	public Double getWeight() {
		return weight;
	}
	public void setWeight(Double weight) {
		this.weight = weight;
	}
	@Override
	public String toString() {
		return "Apple [color=" + color + ", weight=" + weight + "]";
	}
	
}

1.对列表排序

	List<Apple> inventory = new ArrayList<>();
	//方法引用
	inventory.sort(Comparator.comparing(Apple :: getWeight));

之前可以这样:

public class AppleComparator implements Comparator<Apple> {
	public int compare(Apple a1, Apple a2){
		 return a1.getWeight().compareTo(a2.getWeight());
	 }
}

List<Apple> inventory = new ArrayList<>();
inventory.sort(new AppleComparator());

或者这样

	List<Apple> inventory = new ArrayList<>();
    inventory.sort(new Comparator<Apple>() {
    	public int compare(Apple a1, Apple a2){
    		return a1.getWeight().compareTo(a2.getWeight());
   		}
     });

使用 Lambda 表达式:

inventory.sort((Apple a1, Apple a2)
				 -> a1.getWeight().compareTo(a2.getWeight())
);

Lambda根据上下文来推断Lambda表达式参数的类型:

inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));

利用Comparator的静态辅助方法comparing:

Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());

最终:

import static java.util.Comparator.comparing;

List<Apple> inventory = new ArrayList<>();
inventory.sort(comparing((a) -> a.getWeight()));
//方法引用
ventory.sort(comparing(Apple::getWeight));

1.2.比较器复合
Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法。这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);	
List<Apple> inventory = new ArrayList<>();

(1) 逆序

inventory.sort(comparing(Apple::getWeight).reversed()); //按重量递减排序

(2) 比较器链

inventory.sort(comparing(Apple::getWeight)
		 .reversed()//按重量递减排序
		 .thenComparing(Apple::getColor));//一样重时按颜色排序

(3) 谓词复合

   Predicate<Apple> redApple = a -> "red".equals(a.getColor());
   
   // 可以使用negate方法来返回一个Predicate的非,比如苹果不是红的
   Predicate<Apple> notRedApple = redApple.negate();//产生现有Predicate对象redApple的非
   
   //可能想要把两个Lambda用and方法组合起来,比如一个苹果既是红色又比较重:
   Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
   
   //可以进一步组合谓词,表达要么是重(150克以上)的红苹果,要么是绿苹果
   Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150)
   													  .or(a -> "green".equals(a.getColor())
   											  );

注意, and和or方法是按照在表达式链中的位置,从左向右确定优先级的。因此, a.or(b).and(d)可以看作(a || b) && d。

1.3.函数复合
Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。

  1. andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。数学上会写作g(f(x))

    Function<Integer, Integer> f = x -> x + 1;//函数f给数字加1
    Function<Integer, Integer> g = x -> x * 2;//函数g给数字乘2
    
    f&g 组合成一个函数h
    Function<Integer, Integer> h = f.andThen(g);//先给数字加1,再给结果乘2
    int result = h.apply(1);//return 4
    
  2. compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。数学会写作f(g(x))

    Function<Integer, Integer> f = x -> x + 1;//函数f给数字加1
    Function<Integer, Integer> g = x -> x * 2;//函数g给数字乘2
    
    //f&g 组合成一个函数h
    Function<Integer, Integer> h = f.compose(g);  //先给数字乘2,在给结果加1
    int result = h.apply(1); //return 3
    

例:用String表示的一封信做文本转换:

public class Letter{
	public static String addHeader(String text){
		return "From Raoul, Mario and Alan: " + text;
	}
	public static String addFooter(String text){
		return text + " Kind regards";
	}
	public static String checkSpelling(String text){
		return text.replaceAll("labda", "lambda");
	}
}

Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling)
														   .andThen(Letter::addFooter);

2. stream流
注意,和迭代器类似,流只能遍历一次

List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println);//java.lang.IllegalStateException:流已被操作或关闭

流只能消费一次!

2.1. 流操作

List<Apple> inventory = new ArrayList<>();
List<String> colors = inventory.stream()//获取流
						 .filter(d -> d.getWeight() > 300)//中间操作,过滤
						 .map(Apple::getColor)//中间操作,提取颜色
						 .limit(3)//中间操作,截断流
						 .collect(toList());//将stream转换为List
  1. filter、 map和limit可以连成一条流水线;
  2. collect触发流水线执行并关闭它。

可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作。如下图所示:
在这里插入图片描述

2.2.使用流
流的使用一般包括三件事:

  1. 一个数据源(如集合)来执行一个查询;
  2. 一个中间操作链,形成一条流的流水线;
  3. 一个终端操作,执行流水线,并能生成结果。

中间操作

操 作类 型返回类型操作参数函数描述符
filter中间Stream< T>Predicate< T>T -> boolean
map中间Stream< R>Function<T, R>T -> R
limit中间Stream< T>
sorted中间Stream< T>Comparator< T>(T, T) -> int
distinct中间Stream< T>

终端操作

操 作类 型目 的
forEach终端消费流中的每个元素并对其应用 Lambda。这一操作返回 void
count终端返回流中元素的个数。这一操作返回 long
collect终端把流归约成一个集合,比如 List、 Map 甚至是 Integer

collect()终端操作用于collect(toList())的特殊情况。这一操作会创建一个与流具有相同元素的列表。

2.2.1.筛选和切片

  public class Dish {
    	private final String name;
    	private final boolean vegetarian;
    	private final int calories;
    	private final Type type;
    
    	public Dish(String name, boolean vegetarian, int calories, Type type) {
    		this.name = name;
    		this.vegetarian = vegetarian;
    		this.calories = calories;
    		this.type = type;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public boolean isVegetarian() {
    		return vegetarian;
    	}
    
    	public int getCalories() {
    		return calories;
    	}
    
    	public Type getType() {
    		return type;
    	}
    
    	@Override
    	public String toString() {
    		return name;
    	}
    	
    	public enum Type { MEAT, FISH, OTHER }
    }
  1. 用谓词筛选
    Streams接口支持filter方法
List<Dish> vegetarianMenu = menu.stream()
								.filter(Dish::isVegetarian)
								.collect(Collectors.toList())

Streams接口支持filter方法
流还支持一个叫作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);

截短流
流支持limit(n)方法,该方法会返回一个不超过给定长度的流。

List<Dish> dishes = menu.stream()
					    .filter(d -> d.getCalories() > 300)
					    .limit(3)
					    .collect(Collectors.toList());

跳过元素
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。

List<Dish> dishes = menu.stream()
						.filter(d -> d.getCalories() > 300)
						.skip(2)
						.collect(Collectors.toList());

2.2.2.映射
Stream API通过map和flatMap方法从某些对象中选择信息

对流中每一个元素应用函数

List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
								 .map(Dish::getName)
								// .map(String::length)//再链接上一个map
								 .collect(Collectors.toList());

流的扁平化
例 如 , 给 定 单 词 列 表[“Hello”,“World”],你想要返回列表[“H”,“e”,“l”, “o”,“W”,“r”,“d”]。
第一个版本可能是这样的

List<String> words = Arrays.asList("Hello","World");
words.stream()
	 .map(word -> word.split(""))
	 .distinct()
	 .collect(Collectors.toList());

这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String[](String列 表 )。 因 此 , map 返 回 的 流 实 际 上 是 Stream<String[]> 类 型 的 。 你 真 正 想 要 的 是 用Stream来表示一个字符流。如下图:
在这里插入图片描述

尝试使用map和Arrays.stream()

List<String> words = Arrays.asList("Hello","World");
words.stream()
	 .map(word -> word.split(""))
	 .map(Arrays :: stream)
	 .distinct()
	 .collect(Collectors.toList());

现在得到的是一个流的列表(更准确地说是Stream< String>),先是把每个单词转换成一个字母数组,然后把每个数组变成了一个独立的流

使用flatMap

List<String> words = Arrays.asList("Hello","World");
words.stream()
	 .map(word -> word.split(""))
	 .flatMap(Arrays :: stream)
	 .distinct()
	 .collect(Collectors.toList());

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。如下图所示:
在这里插入图片描述
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

2.2.3.查找和匹配
查找数据集中的某些元素是否匹配一个给定的属性。 StreamAPI通过allMatch、 anyMatch、 noneMatch、 findFirst和findAny方法提供了这样的工具。

检查谓词是否至少匹配一个元素 anyMatch方法
anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,你可以用它来看看菜单里面是否有素食可选择

if(menu.stream().anyMatch(Dish::isVegetarian)){
	System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

anyMatch方法返回一个boolean,因此是一个终端操作。

检查谓词是否匹配所有元素 allMatch和noneMatch方法
*allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。比如,你可以用它来看看菜品是否有利健康

boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);

和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,
你可以用noneMatch重写前面的例子

boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);

anyMatch、 allMatch和noneMatch这三个操作都用到了我们所谓的短路,就是Java中&&和||运算符短路在流中的版本。

查找元素 findAny方法
findAny方法将返回当前流中的任意元素。

List<String> words = Arrays.asList("Hello","World");
words.stream()
	 .map(word -> word.split(""))
	 .flatMap(Arrays :: stream)
	 .distinct()
	 .findAny()
	.ifPresent(System.out::println);

查找第一个元素 findFirst方法

例如,给定一个数字列表,下面的代码能找出第一个平方能被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

findFirst找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少

2.2.4.归约 reduce操作

元素求和
对流中所有的元素求和:

List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
int product = numbers.stream().reduce(0, (a, b) -> a * b);

对流中所有的元素相乘:

List<Integer> numbers = Arrays.asList(4, 5, 3, 9);
int product = numbers.stream().reduce(1, (a, b) -> a * b);

reduce接受两个参数:

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

reduce求和操作如下图:

也可以使用Integer类静态的sum方法

int sum = numbers.stream().reduce(0, Integer::sum);

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

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

最大值和最小值

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

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

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

在这里插入图片描述

2.2.5.数值流

原始类型流特化
Java 8引入了三个原始类型特化流接口: IntStream、 DoubleStream和LongStream,分别将流中的元素特化为int、 long和double,从而避免了暗含的装箱成本。(主要解决拆装箱造成的复杂性和效率差异)

  1. 映射到数值流
    将流转换为特化版本的常用方法是mapToInt、 mapToDouble和mapToLong。(返回的是一个特化流,而不是Stream< T>)
  2. 转换回对象流 boxed方法
    有了数值流,你可能会想把它转换回非特化流.
	IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
	Stream<Integer> stream = intStream.boxed();
  1. 默认值OptionalInt
    对于三种原始流特化,也分别有一个Optional原始类型特化版本: OptionalInt、 OptionalDouble和OptionalLong。()
    例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt(区分没有元素的流和最大值真的是0的流):
	OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();

现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:

int max = maxCalories.orElse(1);//如果没有最大值的话,显式提供一个默认最大值

数值范围 range和rangeClosed方法
range是不包含结束值,rangeClosed包含结束值。

IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());

例子(复合勾股定理的数组):

Stream<double[]> pythagoreanTriples2 = IntStream.rangeClosed(1, 100).boxed()
																	.flatMap(a ->
																			 IntStream.rangeClosed(a, 100)
																			 		  .mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)})
																			 		  .filter(t -> t[2] % 1 == 0)
																			 );
pythagoreanTriples2.limit(5)
		.forEach(t ->
		System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

2.2.6.构建流

由值创建流 静态方法Stream.of

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
//可以使用empty得到一个空流,
Stream<String> emptyStream = Stream.empty();

由数组创建流 Arrays.stream

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

由文件生成流

例看看一个文件中有多少各不相同的词:

long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {//流会自动关闭
		uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();
	} catch (IOException e) {
}

由函数生成流:创建无限流 Stream.iterate和Stream.generate。
由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

  1. 迭代
Stream.iterate(0, n -> n + 2)
	  .limit(10)
	  .forEach(System.out::println);
  1. 生成
    generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。
Stream.generate(Math::random)
	  .limit(5)
	  .forEach(System.out::println);

3.用流收集数据(Collectors类)

收集器用作高级归约
例:

List<Transaction> transactions = transactionStream.collect(Collectors.toList());

归约和汇总
例:

long howManyDishes = menu.stream().collect(Collectors.counting());
//简写
long howManyDishes = menu.stream().count();

或者

import static java.util.stream.Collectors.*;
long howManyDishes = menu.stream().collect(counting());

1.最大值和最小值

Collectors.maxBy()
Collectors.minBy()

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

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

Collectors.summingLong和Collectors.summingDouble方法的作用完全一样

平均数:
Collectors.averagingInt,averagingLong和averagingDouble可以计算数值的平均数

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

Collectors.summarizing可以同时获得总和、平均值、最大值和最小值,相应的summarizingLong和summarizingDouble。

3.连接字符串 joining

//joining在内部使用了StringBuilder来把生成的字符串逐个追加起来
String shortMenu = menu.stream().map(Dish::getName).collect(joining());

joining工厂方法有一个重载版本可以接受元素之间的分界符

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

4.广义的归约汇总 reducing

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

它需要三个参数:

  1. 第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。
  2. 第二个参数函数,将菜肴转换成一个表示其所含热量的int。
  3. 第三个参数是一个BinaryOperator,将两个项目累积成一个同类型的值。这里它就是对两个int求和。

例,取最高:

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

用reduce方法来实现toListCollector所做的工作:

Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
List<Integer> numbers = stream.reduce(
										new ArrayList<Integer>(),
										(List<Integer> l, Integer e) -> {
											l.add(e);
											return l; 
										},
										(List<Integer> l1, List<Integer> l2) -> {
											l1.addAll(l2);
											return l1; }
										);

分组 Collectors.groupingBy

  1. 多级分组

把一个内层groupingBy传递给外层groupingBy,并定义一个为流中项目分类的二级标准

public enum CaloricLevel { DIET, NORMAL, FAT }

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;
											}
										 )
									)
								);


/**
	输出
		{
			MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
			FISH={DIET=[prawns], NORMAL=[salmon]},
			OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}
		}
*/

在这里插入图片描述

  1. 按子组收集数据
    传递给第一个groupingBy的第二个收集器可以是任何类型.
/**
 *return {MEAT=3, FISH=2, OTHER=4} 
 */
Map<Dish.Type, Long> typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));


/**
 *return	Optional<Dish>		{FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}
 */
Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream().collect(
																			groupingBy(Dish::getType,
																			maxBy(comparingInt(Dish::getCalories)))
																		);

普通的单参数groupingBy(f)(其中f是分类函数)实际上是groupingBy(f,toList())的简便写法.

其他例子:

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;
																		  }, toSet() )));

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

//return {false=[pork, beef, chicken, prawns, salmon],true=[french fries, rice, season fruit, pizza]}
Map<Boolean, List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));

也可以传递第二个收集器

/**
return: 二级map:
	{false={FISH=[prawns, salmon], MEAT=[pork, beef, chicken]},
	true={OTHER=[french fries, rice, season fruit, pizza]}}
*/
Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType = menu.stream().collect(
																	partitioningBy(
																		Dish::isVegetarian,groupingBy(Dish::getType))
																	);

Collectors类的静态工厂方法

工厂方法返回类型用 于使用示例
toListList< T>把流中所有项目收集到一个 ListList< Dish> dishes = menuStream.collect(toList());
toSetSet< T>把流中所有项目收集到一个 Set,删除重复项Set< Dish> dishes = menuStream.collect(toSet());
toCollectionCollection< T>把流中所有项目收集到给定的供应源创建的集合Collection< Dish> dishes = menuStream.collect(toCollection(),ArrayList::new);
countingLong计算流中元素的个数long howManyDishes = menuStream.collect(counting());
summingIntInteger对流中项目的一个整数属性求和int totalCalories =menuStream.collect(summingInt(Dish::getCalories));
averagingIntDouble计算流中项目 Integer 属性的平均值double avgCalories =menuStream.collect(averagingInt(Dish::getCalories));
summarizingIntIntSummaryStatistics收集关于流中项目 Integer 属性的统计值,例如最大、最小、总和与平均值IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories));
joiningString连接对流中每个项目调用 toString 方法所生成的字符串String shortMenu = menuStream.map(Dish::getName).collect(joining(", "));
maxByOptional< T>一个包裹了流中按照给定比较器选出的最大元素的 Optional,或如果流为空则为 Optional.empty()Optional< Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
minByOptional< T>一个包裹了流中按照给定比较器选出的最小元素的 Optional,或如果流为空则为 Optional.empty()Optional< Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
reducing归约操作产生的类型从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果应用转换函数int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));
groupingByMap<K, List< T>>根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果 Map 的键Map<Dish.Type,List< Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType));
partitioningByMap<Boolean,List< T>>根据对流中每个项目应用谓词的结果来对项目进行分区Map<Boolean,List< Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian));

收集器接口 Collector

public interface Collector<T, A, R> {
	Supplier<A> supplier();
	BiConsumer<A, T> accumulator();
	Function<A, R> finisher();
	BinaryOperator<A> combiner();
	Set<Characteristics> characteristics();
}

列表适用以下定义。

  1. T是流中要收集的项目的泛型。
  2. A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
  3. R是收集操作得到的对象(通常但并不一定是集合)的类型。
    例如,你可以实现一个ToListCollector< T>类,将Stream< T>中的所有元素收集到一个
    List里,它的签名如下:
    public class ToListCollector< T> implements Collector<T, List< T>, List< T>>

建立新的结果容器: supplier方法

public Supplier<List<T>> supplier() {
	return () -> new ArrayList<T>();
}

public Supplier<List<T>> supplier() {
	return ArrayList::new;
}

将元素添加到结果容器: accumulator方法

public BiConsumer<List<T>, T> accumulator() {
	return (list, item) -> list.add(item);
}

public BiConsumer<List<T>, T> accumulator() {
	return List::add;
}

对结果容器应用最终转换: finisher方法

public Function<List<T>, List<T>> finisher() {
	return Function.identity();
}

合并两个结果容器: combiner方法

public BinaryOperator<List<T>> combiner() {
	return (list1, list2) -> {
		list1.addAll(list2);
		return list1; }
}

characteristics方法
characteristics会返回一个不可变的Characteristics集合,它定义了收集器的行为.
Characteristics是一个包含三个项目的枚举。

  1. UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响。
  2. CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约
  3. IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。

例子

public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {

	@Override
	//创建集合操作的起始点
	public Supplier<List<T>> supplier() {
		return ArrayList::new;
	}
	@Override
		//累 积 遍 历 过 的项目,原位修改累加器
		public BiConsumer<List<T>, T> accumulator() {
		return List::add;
	}
	@Override
	//恒 等函数
	public Function<List<T>, List<T>> finisher() {
		return Function.indentity();
	}
	@Override
	//修 改 第 一 个 累 加器,将其与第二个累加器的内容合并
	public BinaryOperator<List<T>> combiner() {
		return (list1, list2) -> {
			list1.addAll(list2);
		return list1;//返回修改后的第一个累加器
	};
	}
	@Override
	public Set<Characteristics> characteristics() {
		//为收集器添加IDENTITY_FINISH和CONCURRENT标志
		return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT));
	}
}

3.并行数据处理与性能

3.1.并行流 parallelStream
并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

3.1.1.将顺序流转换为并行流

public static long parallelSum(long n) {
	return Stream.iterate(1L, i -> i + 1)
				 .limit(n)
				 .parallel()//将流转换为并行流
				 .reduce(0L, Long::sum);
}

对顺序流调用parallel方法并不意味着流本身有任何实际的变化。它在内部实际上就是设了一个boolean标志,表示你想让调用parallel之后进行的所有操作都并行执行。类似地,你只需要对并行流调用sequential方法就可以把它变成顺序流。
请注意,你可能以为把这两个方法结合起来,就可以更细化地控制在遍历流时哪些操作要并行执行,哪些要顺序执行。
例如

stream.parallel()
	 .filter(...)
	 .sequential()
	 .map(...)
	 .parallel()
	 .reduce();

但最后一次parallel或sequential调用会影响整个流水线。在本例中,流水线会并行执行,因为最后调用的是它

配置并行流使用的线程池
并行流内部使用了默认的ForkJoinPool(7.2节会进一步讲到分支/合并框架),它默认的线 程 数 量 就 是 你 的 处 理 器 数 量 , 这 个 值 是 由 Runtime.getRuntime().availableProcessors()得到的。
但 是 你 可 以 通 过 系 统 属 性 java.util.concurrent.ForkJoinPool.common.parallelism来改变线程池大小,如下所示:

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");

这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值,除非你有很好的理由,否则强烈建议你不要修改它。

Spliterator
Spliterator是Java 8中加入的另一个新接口;这个名字代表“可分迭代器”(splitable iterator)。和Iterator一样, Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值