Java8 流式编程

流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。

使用流,无需迭代集合中的元素,就可以提取/操作特定的元素

假设我要生成一个随机序列,范围在5到100之间,不重复,随机生成7个数字,而且要排序,最后输出序列,可以这么做:

public static void main(String[] args) {
    new Random(47)  // 种子
        .ints(5, 100)
        .distinct()
        .limit(7)
        .sorted()
        .forEach(System.out::println);
}

整个过程是一个工作流,它不需要单独提取出这个序列在进行操作。

如果要实现刚刚的功能,而不使用流,就可能会是这样子:

public static void main(String[] args) {
    Random random = new Random(47);
    SortedSet<Integer> set = new TreeSet<>();
    while (set.size() < 7){
        int r = random.nextInt(100);
        if(r >= 5){
            set.add(r);
        }
    }
    System.out.println(set);
}

流操作的类型有三种:

  • 创建流
  • 修改流元素
  • 消费流元素(终端操作,打印/收集元素等等)
    • .forEach()
    • .sum()

创建流

可以用Steam.of将对象数组/一组元素转换为

String[] arr = {"C++ ", "Python ", "Java"};
Stream.of(arr).forEach(System.out::print);
Stream.of("C++ ", "Python ", "Java ").forEach(System.out::print);

每个集合也可以用.stream()来产生一个流

List<String> strList = Arrays.asList("C++ ", "Python ", "Java ");
strList.stream().forEach(System.out::print);

// C++ Python Java 

对于原始数据类型的数组/列表,可以用Arrays.stream()

  • 如果用Steam.of(),输出的是一个地址
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Arrays.stream(arr).forEach(System.out::println);    // 输出元素1 2 3 4 ...
Stream.of(arr).forEach(System.out::println);        // 输出地址 [I@214c265e

创建随机数流

生成4个随机整数,输出

  • ints(n):控制流的大小为4,生成4个整数,返回一个IntStream
  • forEach(System.out::println):打印每个元素
Random random = new Random(47);	// 47是参数,种子
random.ints(4).forEach(System.out::println);

生成5个随机整数,只取前3个整数

Random random = new Random();
random.ints(5).limit(3).forEach(System.out::println);

创建6个随机整数,范围在10~20之间

  • ints(m,n):生成[m,n)之间的整数,返回一个IntStream
  • 用了ints(m,n)的方法,要加limit(),不然就是无限循环
Random random = new Random();
random.ints(10,20).limit(6).forEach(System.out::println);

也可以用init(streamSize,numberOrigin,numberBound)来创建:

  • 第一个参数是流的大小,第二第三个参数是表示随机数的范围
  • 下面这段代码就是:生成6个随机整数,范围在10~20之间
Random random = new Random();
random.ints(6,10,20).forEach(System.out::println);

创建整形序列流

假设我要创建一个[1,100]的连续序列,求它的和,可以用 IntStream 提供的 range(m,n)方法

public static void main(String[] args) {
    // 1-100的序列数组
    int[] intsArr = IntStream.range(1, 101).toArray();
    int sum = Arrays.stream(intsArr).sum();
    System.out.println(sum);
}

可以写的再简单一点:

public static void main(String[] args) {
    // 1-100的序列数组
    int sum = IntStream.range(1, 101).sum();
    System.out.println(sum);
}

对比传统的方法,就会觉得用 会方便很多:

// 传统方法
public static void main(String[] args) {
    int sum = 0;
    for(int i = 1; i <= 100;++i){
        sum += i;
    }
    System.out.println(sum);
}

使用Stream.generate()创建

Stream.generate() 返回无限流,我们可以自定义生成的元素

生成5个随机整数

Stream.generate(() -> {
    return new Random().nextInt();
}).limit(5).forEach(System.out::println);

也可以简写成:

Stream.generate(new Random()::nextInt).limit(5).forEach(System.out::println);

使用Stream.iterate()创建

方法原型:

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
	// ...   
}

第一个参数:种子

第二个参数:方法(lambda表达式)

将 种子 传递给 方法,方法的运行结果 作为流的下一个元素,添加到流中,存储起来,作为下次调用iterate()的第二个参数

iterate()生成斐波那契数列

iterate()只记忆结果

public class Generator {
    private int x = 0;
    Stream<Integer> numbers(){
        return Stream.iterate(1,(i)->{
            int res = x + i;
            x = i;
            return res;
        });
    }

    public static void main(String[] args) {
        // 生成前10项
        new Generator().numbers().limit(10).forEach(System.out::println);
        
        // 生成第10到20项
        new Generator()
            .numbers()
            .skip(10)	// 过滤前10个
            .limit(10)	// 然后取10个
            .forEach(System.out::println);
    }
}

Arrays.stream()Stream.of()

Arrays 类中含有一个名为 stream() 的静态方法用于把 int/long/double数组 转换成为流。

int、double、long等基本类型的数组,用 Arrays.stream(数组名) 进行流的操作

如果是String(String是一个对象),可以用 Stream.of(对象数组名) 进行流的操作

Stream.of()Arrays.stream() 是有区别的:

  • 如果是基本类型的对象数组(如IntegerLong),两个方法是一样的结果
Integer[] intArr = {1, 2, 3, 4};
Arrays.stream(intArr).forEach(System.out::print);	// 1234
Stream.of(intArr).forEach(System.out::print);		// 1234
  • 如果是基本类型的数组(如intlong),两个方法返回的结果不一样
int[] intArr = {1, 2, 3, 4};
Arrays.stream(intArr).forEach(System.out::print);	// 1234
Stream.of(intArr).forEach(System.out::print);		// [I@214c265e

这是因为int[]传入Stream.of()会被当作一个对象,而传入Arrays.stream()会被当成一个数组

流的中间操作

什么是流的中间操作?就是修改流中的元素,返回一个修改后的Stream。

一个流创建之后有6个元素,我跳过前3个,只取最后3个,那中间操作就是 “跳过前三个”

peek() 跟踪和调试

peek() 的目的是:无修改的查看流中的元素

peek()参数可接收lambda表达式

当流中的一个元素通过管道的时候,就会调用一次peek()

例1

public static void main(String[] args) {
    String[] str = {"java ", "c++  ", "php  ", "dart "};
    Stream.of(str)
        .skip(1)
        .peek(System.out::print)	// 输出当前流的情况
        .map(s -> "第1次变化:" + s)	// 映射,修改str中字符串的值
        .peek(System.out::print)
        .forEach(System.out::println);
}

例1-输出结果:

c++  第1次变化:c++  第1次变化:c++  
php  第1次变化:php  第1次变化:php  
dart 第1次变化:dart 第1次变化:dart 

例2

public static void main(String[] args) {
    String[] str = {"java ", "c++  ", "php  ", "dart "};
    Stream.of(str)
        .skip(1)
        .peek(System.out::print)
        .map(String::toUpperCase)	// 映射,将流中的元素全转成大写
        .forEach(System.out::print);
}

例2-输出结果

c++  C++  php  PHP  dart DART 

sorted() 排序

sorted()接收一个lambda表达式,也可以接收一个 比较器sorted()预设了一些默认的比较器)

基本类型数组排序

我查了资料,发现 基本类型的数组的sorted()不能传比较器,它们的包装类就可以

public static void main(String[] args) {
    // 基本类型数组
    int[] arr = {1, 18, 12, 16, 4};
    // 默认正序
    Arrays.stream(arr).sorted().forEach(System.out::println);	// 1 4 12 16 18
}

对象/包装类数组排序

public static void main(String[] args) {
    // Integer类型数组
    Integer[] integerArr = {1, 18, 12, 16, 4};
    // 正序
    Arrays.stream(integerArr).sorted().forEach(System.out::println);	// 1 4 12 16 18
    // 逆序
    Arrays.stream(arr).sorted(Comparator.reverseOrder()).forEach(System.out::println);	// 18 16 12 4 1
}

集合排序

public static void main(String[] args) {   
    // List 集合
    List<Integer> list = new LinkedList<>(Arrays.asList(1,18,12,16,4));
    // 正序
    list.stream().sorted().forEach(System.out::println);	// 1 4 12 16 18
    // 逆序
    list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);	// 18 16 12 4 1
}

distinct() 去重

消除流中重复的元素

public static void main(String[] args) {
    // 数组
    Integer[] arr = {1, 1, 1, 2, 2};
    Arrays.stream(arr).distinct().forEach(System.out::println);	// 1 2
}

filter(Predicate) 过滤器

filter()接收到参数是一个函数,如果函数返回 true,则保留这些元素。返回false 则删除这些元素

假定有一个数组,保留其中的奇数:

public class Main {
    public static void main(String[] args) {
        // 数组
        Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
        Arrays.stream(arr).filter(Main::Odd).forEach(System.out::println); // 1 3 5 7
    }

    // 奇数返回true
    public static Boolean Odd(int number) {
        return number % 2 == 1;
    }
}

同样的,filter()也接收正则表达式

public static void main(String[] args) {
    // 数组
    Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
    Arrays.stream(arr).filter(num -> num % 2 == 1).forEach(System.out::println);
}

map()相关 - 映射到元素

  • map(Function):将函数操作应用在输入流的元素中,并将返回值传递到输出流中。
  • mapToInt(ToIntFunction):操作同上,但结果是 IntStream
  • mapToLong(ToLongFunction):操作同上,但结果是 LongStream
  • mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream
public static void main(String[] args) {
    // 数组
    Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
    Arrays.stream(arr)
        .map(s -> s * 3)    // 每个元素 × 3
        .forEach(System.out::println);	// 2 4 9 16 10 12 14

}

skip(long n) 跳过前n个元素

skip(long n)可以省略流的前n个元素

public static void main(String[] args) {
    int[] arr= {1,2,3,4,5};
    // 省略前 3 个
    Arrays.stream(arr).skip(3).forEach(System.out::println);	// 4 5

}

limit(long n) 保留前n个元素

limit(long n) 可以保留前n个元素,其他的不要

public static void main(String[] args) {
    int[] arr= {1,2,3,4,5};
    // 保留前 3 个
    Arrays.stream(arr).limit(3).forEach(System.out::println);	// 1 2 3

}

parallel() 流的并行处理

parallel() 可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。

使用场景(前提是确保线程的安全):

  • 将文件下载,保存到服务器
  • 遍历

假设这样一个场景:有一个商品的分类ld,要根据分类Id,去数据库找具体的信息(IO操作)

传统的遍历

  • 运行时间:732毫秒
public static void main(String[] args) {
    List<Category> list = getCategoryIdList();    // 获取商品分类的Id
    
	// 普通的遍历
    long start = System.currentTimeMillis();

    for (Category category : list) {
        System.out.println(getById(category.id));
    }
    long end = System.currentTimeMillis();
    System.out.println("普通遍历 运行时间:" + (end - start) + "毫秒");
}

image-20200823230332170

使用 parallel()遍历

  • 遍历的结果是无序的

  • 运行时间:607毫秒

    public static void main(String[] args) {
        List<Category> list = getCategoryIdList();    // 获取商品分类的Id

        // 并行遍历
        long start = System.currentTimeMillis();
        
        list.stream().parallel().forEach(category -> {
            // IO 操作
            System.out.println(getById(category.id));	// 根据分类Id,查询具体信息
        });
        
        long end = System.currentTimeMillis();
        
        System.out.println("parallel() 运行时间:" + (end - start) + "毫秒");

    }

image-20200823230457355

使用 Java8 的 parallel 可以加快某些操作的速度,但如果是一些简单的操作,那就得不偿失了

此外,parallel() 遍历的结果是无序的

流的终端操作

终端操作:获取流的最终结果,无法再往后传递流,比如打印、获取集合、查找,都算是终端操作

返回 Optional 对象

Optional 类 隐藏了可能存在空指针的不确定性

一些终端操作,会返回一个Optional 对象,因为这些操作,不能保证预期结果一定存在,也就是隐藏了空指针的信息。如果流是空的,那就会返回Option.empty

  • findFirst() 返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty

  • findAny() 返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty

  • max()min() 返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty

    reduce() 不再以 identity 形式开头,而是将其返回值包装在 Optional 中。

    identity 对象成为其他形式的 reduce() 的默认结果,因此不存在空结果的风险)

findFirst()

public static void main(String[] args) {
    String [] str1 = {"java","python","c++"};
    String [] str2 = {};

    System.out.println(Stream.of(str1).findFirst());	// Optional[java]
    System.out.println(Stream.of(str2).findFirst());	// Optional.empty

}

findAny()

public static void main(String[] args) {
    String [] str1 = {"java","python","c++"};
    String [] str2 = {};

    System.out.println(Stream.of(str1).findAny());	// Optional[java]
    System.out.println(Stream.of(str2).findAny());	// Optional.empty

}

max()min()

public static void main(String[] args) {
    Integer[] arr1 = {1, 2, 3, 4, 5};
    Integer[] arr2 = {};

    System.out.println(Stream.of(arr1).max(Integer::compareTo));	// Optional[5]
    System.out.println(Stream.of(arr1).min(Integer::compareTo));	// Optional[5]
    
    System.out.println(Stream.of(arr2).min(Integer::compareTo));	// Optional.empty
    System.out.println(Stream.of(arr2).max(Integer::compareTo));	// Optional.empty

}

解包 Optional:

  • isPresent():判断 Optional 对象中是否包含元素
  • ifPresent(Consumer):如果 Optional对象中包含元素,就调用 Consumer方法(lambda表达式)
  • get():获取 Optional对象的 元素
  • orElse(otherObject):如果值存在则直接返回,否则生成 otherObject
  • orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。
  • orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数生成一个异常。

toArray() 返回数组

  • toArray():将流按照数组返回

  • toArray(T[] a):生成自定义类型的数组

生成随机整型数组:

public static void main(String[] args) {
    int[] arr = new Random().ints(5).toArray();	// 生成生成5个int型,转换为int数组
}

toArray(T[] a)将集合转成数组

    public static void main(String[] args) {
        List<String> list = new LinkedList<>();
        list.add("java ");
        list.add("python");

        String[] str = (String[]) list.toArray(new String[0]);

        Stream.of(str).forEach(System.out::print);	// java python
    }

forEach() 遍历

  • forEach(Consumer)常见如 System.out::println 作为 Consumer 函数。

  • forEachOrdered(Consumer): 保证 forEach 按照原始流顺序操作。(parallel()操作之后是无序的,用这个可以强制保持原始流的顺序)

两个函数都接收lambda表达式

使用forEach遍历数组:

Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
// 接收lambda表达式
// i 就是数组中的元素
Arrays.stream(arr).forEach(i -> {
    // do something...
});

传统方法遍历数组:

Integer[] arr = {1, 2, 3, 4, 5, 6, 7};
for(Integer i : arr){
    // do something...
}

collect() 返回集合

  • collect(Collector):使用 Collector 收集流元素到结果集合中。

  • collect(Supplier, BiConsumer, BiConsumer):同上,第一个参数 Supplier 创建了一个新的结果集合,第二个参数 BiConsumer 将下一个元素收集到结果集合中,第三个参数 BiConsumer 用于将两个结果集合合并起来。

生成随机数,保存到 LinkedList中

public static void main(String[] args) {
    LinkedList<Integer> list = new Random(47)
        .ints(10)
        .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
    	
    
    for (Integer integer : list) {
        System.out.print(integer+ " ");
    }
}

reduce() 组合

使用reduce() 组合所有流中的元素

  • reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional

  • reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。

  • reduce(identity, BiFunction, BinaryOperator):更复杂的使用形式

    • identity:组合函数的标识值,累加器的初始值。

    • BiFunction:累加器,一个函数,用于将额外的元素合并到结果中

    • BinaryOperator:用于组合两个值的关联、不干扰、无状态函数,必须与累加器函数兼容

      只有在并行流中才会执行

使用Stream.reduce()合并流的元素,并产生单个值

  • 传统方法使用for循环求和

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int sum = 0;
        for(int i : numbers){
            sum += i;
        }
        System.out.println(sum); // 55
    
    }
    
  • 使用reduce() 求和

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int sum = Arrays.stream(numbers).reduce(0, (a, b) -> a + b);
        System.out.println(sum);
    }
    

    累加器的初始值设置为0,(a,b)-> a+b是累加器,结果保存到第一个参数中

count() 统计

统计流中的个数,返回值是long类型

String[] arr = {"123", "456", "789"};
long cnt = Stream.of(arr).count();
System.out.println(cnt);  // 3

max()min() 最大数值

  • max()min():数值流操作无需 Comparator。返回一个Optional对象

数组最大值、最小值

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

    
    System.out.println(Arrays.stream(arr).min());				 // OptionalInt[1]
    System.out.println(Arrays.stream(arr).min().getAsInt());	// 1

}

max(Comparator)min(Comparator)

  • max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。
  • min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。
  • 返回的都是Optional对象
    public static void main(String[] args) {
        String[] str = {"java","c++","python"};
        System.out.println(Arrays.stream(str).max(String::compareTo));  // Optional[python]
        System.out.println(Arrays.stream(str).max(String::compareTo).get());	// python
    }

sum() 求和

求流元素的和

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sum = Arrays.stream(arr).sum();
    System.out.println(sum);		// 55
}

average() 平均值

求流元素的平均值,返回一个OptionalDouble对象

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    OptionalDouble average = Arrays.stream(arr).average();
    System.out.println(average);				// OptionalDouble[5.5]
    System.out.println(average.getAsDouble());	// 5.5
}

匹配

  • allMatch(Predicate) :如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。
  • anyMatch(Predicate):如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 时停止执行计算。
  • noneMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。

说简单,就是传一个lambda表达式,然后返回布尔值

判断数组中是否全都为奇数

public static void main(String[] args) {
    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 判断所有元素是否都为奇数
    boolean flag = Arrays.stream(arr).allMatch(i -> i % 2 == 1);
    System.out.println(flag);
}

查找

  • findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty
  • findAny(:返回含有任意流元素的 Optional,如果流为空返回 Optional.empty
public static void main(String[] args) {
    String[] str = {"java","c++","python"};
    System.out.println(Arrays.stream(str).findFirst());			// Optional[java]
    System.out.println(Arrays.stream(str).findFirst().get());	// Java
}
内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/47Pineapple/p/13795983.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!