Java8 中的Stream API详解

Java 8 中Stream API详解

Stream的作用

  • Java8 中的Stream是对集合对象功能的增强,专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作
  • Stream API借助于同样新出的Lambda表达式,极大的提高了编程效率和程序的可读性
  • 同时提供串行和并行两种模式进行汇聚操作,并发模式可以充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。
  • 无需编写一行多线程代码就可以方便的写出高性能的并发程序

Stream的性质

  • 流(Stream):就是数据渠道,用于操作数据源(集合、数组)所生成的元素序列
  • 集合注重的数据,流注重与计算
  • Stream不会自己存储数据
  • Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
  • Stream操作是延迟执行的。这意味着会等到需要结果的时候才执行。

什么是聚合操作

​ 在传统J2EE的应用中,java代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:

  • 每日平均消费金额
  • 最受欢迎的商品
  • 取一定量的数据样本作为首页推荐

这类的操作。而在java集合API中,仅仅有极少量的辅助型方法,更多的时候需要用Iterator来遍历集合,完成相关的聚合应用逻辑。

举例:

获取数组最大值

 public static void main(String[] args) { 
        int[] nums={1,2,3,4,5,6,7};
        int max = Arrays.stream(nums).max().getAsInt();
        System.out.println(max);
    }

Stream总览

什么是流

​ Stream不是集合元素,不是数据结构并不保存数据,是有关算法和计算的,类似于一个高版本的Iterator。我们只需要给出需要对其包含的元素执行什么操作,Stream就会隐式地在内部进行遍历,做出相应的数据转换。

​ Stream如同一个迭代器,单向,不可往复,数据只能遍历一次,遍历一次后即用尽了。

​ Stream可以并行化操作,迭代器只能命令式地、串行化操作。

​ Stream的一大特点就是数据源本身可以是无限的。

流的构成

分为三个基本步骤

​ 获取一个数据源——数据转化——执行操作获取先要的结构

每次转换原有的Stream对象不改变,返回一个新的Stream对象(可以有多次转化),这就允许对其操作可以向链条一样排列,变成一个管道

有多种方式生成Stream Source:

  • 从Collection和数组
    • Collection.stream()
    • Collection.oarallelStream()
    • Arrays.stream(T array)or Strean.of()
  • 从BufferReader
    • java.io.BUfferedReader.lines()
  • 静态工程
  • 自己创建

流的操作类型

  • Intermediate:其主要目的是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。仅仅调用到这类方法,并没有真正开始流的遍历
  • Terminal:一个流只能有一个terminal操作,其操作的执行才是真正开始流的遍历,并生成一个结果

流的使用详解

流的构造与转换

  • 构造流的几种常见方法
public static void main(String[] args) {
        // 1. Individual values
        Stream stream = Stream.of("a", "b", "c");
        // 2. Arrays
        String [] strArray = new String[] {"a", "B", "c"};
        stream = Stream.of(strArray);
        stream = Arrays.stream(strArray);
        // 3. Collections
        List<String> list = Arrays.asList(strArray);
        stream = list.stream();
    }
  • 数值流的构造
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);
  • 流转换为其他数据结构
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

流的操作

  • 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理,而在终止操作时一次性全部处理。即惰性求值。Stream的中间操作时不会有任何结果数据输出的。
  • Stream的中间操作可以在整体上分为:筛选与切片、映射、排序
筛选与切片
方法 描述
filter(Predicate p) 接收Lambda表达式,从流中排除某些元素
distinct() 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize) 截断流,使其元素不能超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前n个元素的流。若流中的元素不足n个,则返回一个空流与limit(n)互补
示例
  • Employee类
public class Employee implements Serializable {
    private String name;
    private Integer age;
    private Double salary;
}
  • 构件对象数组
protected List<Employee> list = Arrays.asList(
    new Employee("张三", 18, 9999.99),
    new Employee("李四", 38, 5555.55),
    new Employee("王五", 60, 6666.66),
    new Employee("赵六", 8, 7777.77),
    new Employee("田七", 58, 3333.33)
);
  • Filter()方法

主要是接收Lambda表达式,从流中排除某些元素

//内部迭代:在此过程中没有进行过迭代,由Stream api进行迭代
//中间操作:不会执行任何操作
Stream<Person> stream = list.stream().filter((e) -> {
    System.out.println("Stream API 中间操作");
    return e.getAge() > 30;
});
  • limit()方法

主要作用为:截断流,使其元素不超过给定数量

//过滤之后取2个值
list.stream().filter((e) -> e.getAge() >30 ).limit(2).forEach(System.out :: println);

​ 在其中我们可以配合其他的中间操作,并截断流,使我们可以取得相应个数的元素。

  • skip()方法

跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

//跳过前2个值
list.stream().skip(2).forEach(System.out :: println);
  • distinct()方法

筛选,通过流所生成元素的hashCode和equals()去除重复元素

list.stream().distinct().forEach(System.out :: println);

distict需要在实体中重写hashCode和equal方法才能使用

映射
方法 描述
map(Function f) 接收一个函数,该函数会被引用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 接收一个函数,该函数会被引用到每个元素上,产生一个新的DoubleStream
mapToInt(ToIntFunction f) 接收一个函数,该函数会被引用到每个元素上,产生一个新的IntStream
mapToLong(ToLongFunction f) 接收一个函数,该函数会被引用到每个元素上,产生一个新的LongStream
flagMap(function f) 接收一个函数,将流中的每个值都换成另一个流,然后把所有流,连接成一个流。
示例
  • map() 方法

    //将流中每一个元素都映射到map的函数中,每个元素执行这个函数,再返回
    List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
    list.stream().map((e) -> e.toUpperCase()).forEach(System.out::printf);
    
    //获取Person中的每一个人得名字name,再返回一个集合
    List<String> names = this.list.stream().map(Person :: getName).collect(Collectors.toList());
    
  • flagMap()

    /**
         * flatMap —— 接收一个函数作为参数,将流中的每个值都换成一个流,然后把所有流连接成一个流
         */
        @Test
        public void testFlatMap () {
            StreamAPI_Test s = new StreamAPI_Test();
            List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
            list.stream().flatMap((e) -> s.filterCharacter(e)).forEach(System.out::println);
    
            //如果使用map则需要这样写
            list.stream().map((e) -> s.filterCharacter(e)).forEach((e) -> {
                e.forEach(System.out::println);
            });
        }
    
        /**
         * 将一个字符串转换为流
         */
        public Stream<Character> filterCharacter(String str){
            List<Character> list = new ArrayList<>();
            for (Character ch : str.toCharArray()) {
                list.add(ch);
            }
            return list.stream();
        }
    

其实map方法相当于Collection的add方法,如果add的是个集合的话会变为二维数组,而flatMap相当于Collection的addAll犯法

排序
方法 描述
sorted() 产生一个新流,其按自然顺序排序
sort(Comparator comp) 产生一个新流,其中按比较器书序排序
// 自然排序
List<Employee> persons = list.stream().sorted().collect(Collectors.toList());

//定制排序
List<Employee> persons1 = list.stream().sorted((e1, e2) -> {
    if (e1.getAge() == e2.getAge()) {
        return 0;
    } else if (e1.getAge() > e2.getAge()) {
        return 1;
    } else {
        return -1;
    }
}).collect(Collectors.toList());

猜你喜欢

转载自blog.csdn.net/issunmingzhi/article/details/106413031