Java8-11新特性总结(五) Stream

Java 8 Stream

概要

  • Java 8 API 添加了一种新的抽象称为流Stream,Stream以一种声明的方式处理数据。
  • Stream以一种类似SQL语句从数据库查询数据的直观方式在Java集合运算和表达的抽象中体现。
  • Stream API 可以极大提高Java程序员的生产力并写出高效率、干净、简洁的代码。
  • 将集合看作流,流通过管道传输并在节点上进行筛选、排序、聚合等处理。
  • 管道处理分为中间操作处理(intermediate operation)和最终操作处理(terminal operation)。

什么是Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作。

注:

元素队列:特定类型对象并形成一个队列。

数据源:流的来源。可以是集合,数组,I/O channel,产生器generator等。

聚合操作:filter、map、reduce、find、match、sorted等中间操作。

Pipelining::中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,比如延迟执行(laziness)和短路( short-circuiting)。

内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。

生成流

使用Collection下的 stream() 和 parallelStream() 方法。

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

使用Arrays 中的 stream() 方法,将数组转成流。

Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);

使用Stream中的静态方法:of()、iterate()、generate()。

Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
 
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
 
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);

使用 BufferedReader.lines() 方法,将每行内容转成流。

BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);

使用 Pattern.splitAsStream() 方法,将字符串分隔成流。

Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);

中间操作

forEach

Stream提供'forEach'来迭代流中的元素,对比之前的几种迭代方式如下:

public class Test {
	
	public static void main(String[] args) {
		List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");

        //1、迭代器iterator
        Iterator<String> iterator = strings.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}

        //2、传统for循环
		for (int i = 0; i < strings.size(); i++) {
			if(!strings.get(i).isEmpty()) {
				System.out.println(strings.get(i));
			}
		}

        //3、foreach增强
		for (String string : strings) {
			if(!string.isEmpty()) {
				System.out.println(string);
			}
		}

        //4、foreach
        strings.forEach(System.out::println);
		
	}
}

filter

用于筛选符合条件的元素。

public class Test {
	
	public static void main(String[] args) {
		List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
		strings.stream().filter(str -> !str.isEmpty()).forEach(System.out::println);
	}
	
}

limit

用于获取指定数量的流。

public class Test {
	
	public static void main(String[] args) {
		List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
		strings.stream().limit(5).forEach(System.out::println);
	}
	
}

sorted

用于对流中元素的排序操作。

public class Test {
	
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(5,8,9,7,11);
        //升序
		list.stream().sorted().forEach(System.out::println);
        //降序
        list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
        //多条件排序
        //list.stream().sorted(Comparator.comparing(Class::method).thenComparing(Class::method))
	}
	
}

map、flatMap

相同点:

  • 都是依赖FuncX(入参,返回值)进行转换(将一个类型依据程序逻辑转换成另一种类型,根据入参和返回值)。
  • 都能在转换后直接被subscribe

区别:

  • map返回的是结果集,flatmap返回的是包含结果集的Observable(返回结果不同)
  • map被订阅时每传递一个事件执行一次onNext方法,flatmap多用于多对多,一对多,再被转化为多个时,一般利用from/just进行一一分发,被订阅时将所有数据传递完毕汇总到一个Observable然后一一执行onNext方法(执行顺序不同)>>>>(如单纯用于一对一转换则和map相同)
  • map只能单一转换,单一只的是只能一对一进行转换,指一个对象可以转化为另一个对象但是不能转换成对象数组(map返回结果集不能直接使用from/just再次进行事件分发,一旦转换成对象数组的话,再处理集合/数组的结果时需要利用for一一遍历取出,而使用RxJava就是为了剔除这样的嵌套结构,使得整体的逻辑性更强。)
  • flatmap既可以单一转换也可以一对多/多对多转换,flatmap要求返回Observable,因此可以再内部进行from/just的再次事件分发,一一取出单一对象(转换对象的能力不同)

public class Test {
    List<String[]> eggs = new ArrayList<>();

    @Before
    public void init() {
        // 第一箱鸡蛋
        eggs.add(new String[]{"鸡蛋_1", "鸡蛋_1", "鸡蛋_1", "鸡蛋_1", "鸡蛋_1"});
        // 第二箱鸡蛋
        eggs.add(new String[]{"鸡蛋_2", "鸡蛋_2", "鸡蛋_2", "鸡蛋_2", "鸡蛋_2"});
    }

    // 自增生成组编号
    static int group = 1;
    // 自增生成学生编号
    static int student = 1;

    /**
     * 把二箱鸡蛋分别加工成煎蛋,还是放在原来的两箱,分给2组学生
     */
    @Test
    public void 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]
         */
    }

    /**
     * 把二箱鸡蛋分别加工成煎蛋,然后放到一起【10个煎蛋】,分给10个学生
     */
    @Test
    public void 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
         */
    }

}

并行(parallel)

充分利用多线程,提高程序运行效率

public class Test {
	
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(5,8,9,7,11);
		list.parallelStream().forEach(System.out::println);
	}
	
}

注意:使用并行流虽然可以提高程序运行效率,但是不代表所有情况都得使用并行流,盲目使用可能导致以下结果:

  1. 效率不增反减。
  2. 增加额外的复杂度,程序更加容易出错。
  3. 运行结果不正确。

解释:

  • parallel stream是基于fork/join框架的,简单点说就是使用多线程来完成的,使用parallel stream时要考虑初始化fork/join框架的时间,如果要执行的任务很简单,那么初始化fork/join框架的时间会远多于执行任务所需时间,也就导致了效率的降低.根据附录doug Lee的说明,任务数量*执行方法的行数>=10000或者执行的是消耗大量时间操作(如io/数据库)才有必要使用。
  • 在spring框架中,假设有一组主键id,使用这组id去数据库获取记录。

//DB.fetchRecord(long id)使用当前线程session连接数据库 ids.parallelStream().map(DB::fetchRecord).collect(Collections.toList());

//这里使用parallel stream是正确的,但是运行会报错,类似于 can't obtain session from current thread.原因就是多线程运行,对应的线程没有绑定的session,要完成上面的功能需要提供一个特殊版本的DB方法

  • session问题已经解决,如果获取到的记录需要和ids顺序相同,那么使用parallel获取到的结果就是不正确的,原因还是因为多线程。

如何正确使用:

  1. 确保要执行的任务对线程环境没有依赖
  2. 任务消耗时间长/数据量大到不用思考是否要用parallel
  3. 结果没有顺序要求

流收集数据

通过Collectors类提供的工厂方法(例如groupingBy)创建的收集器来完成。

Collectors.toList:流对象返回List对象

List<String> strings = Arrays.asList("sad","   ","wqe","","cv","gj");
System.out.println(strings.stream().filter(s -> !s.isBlank()).collect(Collectors.toList()));

Collectors.joining:流对象toString后返回字符串

List<String> strings = Arrays.asList("sad","   ","wqe","","cv","gj");
System.out.println(strings.stream().filter(s -> !s.isBlank()).collect(Collectors.joining("-")));

Collectors.counting:流元素计数

List<String> strings = Arrays.asList("sad","   ","wqe","","cv","gj");
System.out.println(strings.stream().filter(s -> !s.isBlank()).collect(Collectors.counting()));
System.out.println(strings.stream().filter(s -> !s.isBlank()).count());

Collectors.maxBy 、 Collectors.minBy:计算流中的最大值或最小值

List<Integer> integers = Arrays.asList(5,50,20,1,100,253);
Comparator<Integer> comparator = Integer::compareTo;
System.out.println(integers.stream().collect(Collectors.maxBy(comparator)).get());
//System.out.println(integers.stream().max(Comparator.comparing(Integer::intValue)).get());
System.out.println(integers.stream().collect(Collectors.minBy(comparator)).get());
System.out.println(integers.stream().max(comparator).get());
System.out.println(integers.stream().min(comparator).get());

Collectors.summingInt / Double / Long:数值求和

List<Integer> integers = Arrays.asList(5,50,20,1,100,253);
System.out.println(integers.stream().collect(Collectors.summingInt(Integer::intValue)));
System.out.println((Integer) integers.stream().mapToInt(Integer::intValue).sum());

Collectors.averagingInt / Double / Long:数组平均数

List<Integer> integers = Arrays.asList(5,50,20,1,100,253);
System.out.println(integers.stream().collect(Collectors.averagingInt(Integer::intValue)));

Collectors.summarizingInt / Double / Long:多元素计数汇总

List<Integer> integers = Arrays.asList(5,50,20,1,100,253);
System.out.println(integers.stream().collect(Collectors.summarizingInt(Integer::intValue)));
//IntSummaryStatistics{count=6, sum=429, min=1, average=71.500000, max=253}

Collectors.reducing:流元素规约

//求和
List<Integer> integers = Arrays.asList(5,50,20,1,100,253);
System.out.println(integers.stream().collect(Collectors.reducing(0,Integer::intValue,(i,j) -> i+j)));
System.out.println(integers.stream().map(Integer::intValue).reduce(0, (i, j) -> i + j));
System.out.println(integers.stream().reduce(0, (i, j) -> i + j));
System.out.println(integers.stream().reduce(0, Integer::sum));

Collectors.groupingBy:流元素分组

List<Integer> list = Arrays.asList(15,20,24,18,50);
System.out.println(list.stream().collect(Collectors.groupingBy(item -> "分组")));
//{分组=[15, 20, 24, 18, 50]}

Collectors.partitioningBy:流元素分区

List<Integer> list = Arrays.asList(15,20,24,18,50);
System.out.println(list.stream().collect(Collectors.partitioningBy(item -> item.intValue() > 20)));
//{false=[15, 20, 18], true=[24, 50]}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值