关于java8中的流库中的常用方法

Java中的流库

从迭代到流的操作

案例:在处理集合时,我们通常会迭代遍历它的元素,并在每个元素上执行某项操作,如下所示:

List<String> strs = Files.readAllLines(Paths.get("src\\eight\\a.txt"), StandardCharsets.UTF_8);
long count = strs.stream()
    .filter(str -> str.length() < 12)
    .count();

可以将stream修改成parallelStream就可以让流库以并行方式来执行过滤和计数。

List<String> strs = Files.readAllLines(Paths.get("src\\eight\\a.txt"), StandardCharsets.UTF_8);
long count = strs.parallelStream()
	.filter(str -> str.length() < 12)
	.count();

流遵循了做什么而非怎么做的原则,在流的示例中,我们描述了需要做什么;

流表面上看起来和集合很类似,都可以让我们转换和获取数据,但是它们之间存在着显著的差异:

  • 流并不存储其元素。这些元素可能存储在底层的集合中,或者是按需生成的。
  • 流的操作不会修改其数据源。例如,filter方法不会从流中移除元素,而是会生成一个新的流,其中不包含被过滤掉的元素。
  • 流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。如:我们指向查找前5个长单词而不是所有长单词,那么filter方法就会在匹配到第五个单词后停止过滤,因此,我们甚至可以操作无限流。

案例中的代码,是操作流时的典型流程。我们建立了一个包含三个阶段的操作管道:

  • 创建一个流;
  • 指定将初始流转换为其他流的中间操作,可能包含多个步骤;
  • 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作,从此之后,这个流便不能使用了。

流的创建

可以用Collection接口的stream方法将任何集合转换为一个流,如果有一个数组,那么可以使用静态的Stream.of方法。

// 数组转换为流
String test = "123;312;123;1231;23123;123;";
Stream<String> split = Stream.of(test.split(";"));
// 可变长参数
Stream<String> stringStream = Stream.of("1", "2", "3");
// 空的流
Stream<Object> empty = Stream.empty();

Stream接口有两个用于创建无限流的静态方法。generate方法会接收一个不包含任何引元的函数(是一个Supplier<T>接口的对象)。无论何时,只要需要一个流类型的值,该函数就会被用以产生一个这样的值。

Stream<String> generate = Stream.generate(() -> "qiao");
// 随机数
Stream<Double> generate1 = Stream.generate(Math::random);

如果想要产生像0,1,2,3。。。这样的序列,可以使用iterate方法。它会接收一个种子值,以及一个函数(UnaryOperation<T>),并且会反复地将该函数应用到之前的结果上。

// 如果输出的话,会一直产生,无限制
Stream<BigInteger> iterate = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));

有限序列,书上可以传入两个lambda表达式的方法并没有找到。改写为一下代码:

Stream<BigInteger> iterates = Stream.iterate(BigInteger.ZERO,n -> n.add(BigInteger.ONE)).limit(100L);

filtermapflatMap方法

流的转换会产生一个新的流,它的元素派生自另一个流中的元素。下面我们将使用filter将字符串流转换为只包含长单词的流。

String test = "123;312;123;1231123123123;23123;123;";
Stream<String> strings = Stream.of(test);
Stream<String> stringStream = strings.filter(s -> s.length() > 12);

filter的引元是Predicate<T>,即从T到boolean的函数。

通常,我们想要按照某种方式来转换流中的值,此时,可以使用map方法并传递执行该转换的函数,如下所示:

public class TestMain {
    public static void main(String[] args) throws IOException {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add(new User("test" + i));
        }
        List<Account> collect = users.stream()
                .map(user -> userToAccount(user))
                .collect(Collectors.toList());
    }
    
    private static Account userToAccount(User user) {
        return new Account(user.getName(), BigDecimal.ZERO);
    }
}

public class User {
    private String name;
    // 省略构造器,get和set方法
}
public class Account {
    private String accountName;
    private BigDecimal balance;
    // 省略构造器,get和set方法
}

如果我们想把多个相同元素的流合并为一个流,我们应该这样做:

public static void main(String[] args) throws IOException {
    String a = "1231233123";
    String b = "qwerqwerqwer";
    String c = "adsfasdfadsf";
    List<String> strings = new ArrayList<>();
    strings.add(a);
    strings.add(b);
    strings.add(c);
    // [[1,2,3,4],[1,2,3],[1,2,3]]
    List<Stream<String>> collect = strings.stream()
        .map(s -> codePoints(s))
        .collect(Collectors.toList());
    // [1,2,3,4,5,6,7]
    List<String> collect1 = strings.stream()
        .flatMap(s -> codePoints(s))
        .collect(Collectors.toList());
}

private static Stream<String> codePoints(String str) {
    List<String> result = new ArrayList<>();
    int i = 0;
    while (i < str.length()) {
        int j = str.offsetByCodePoints(i, 1);
        result.add(str.substring(i, j));
        i = j;
    }
    return result.stream();
}

抽取子流和组合流

调用stream.limit(n)会返回一个新的流,它在n个元素之后结束,这个方法对于裁剪无限流的尺寸特别有用。

Stream<BigInteger> iterates = Stream.iterate(BigInteger.ZERO,n -> n.add(BigInteger.ONE)).limit(100L);

调用stream.skip(n)正好相反,它会丢弃前n个元素。

List<String> collect2 = collect1.stream().skip(1).collect(Collectors.toList());

stream.takeWhile(prdicate)调用会在谓词为真时获取流中的所有元素,然后停止。(JDK9提供)

Stream<String> test = list.stream().takeWhilte(s -> "0123".contains(s));

其他的流转换

distinct方法会返回一个流,它的元素是从原有流中产生的,即原来的元素按照同样的顺序剔除重复元素后产生的。这些重复元素并不一定是毗邻的

Stream.of("qwe", "qwe1", "qwe", "qwe1")
    .distinct()
    .forEach(System.out::println);

对于流的排序,有多种sorted方法的变体可用。其中一种用于操作Comparable元素的流,而另一种可以接收一个Comparator

Stream.of("qwe1", "qwe12", "qwe123", "qwe")
    .sorted(Comparator.comparing(String::length).reversed())
    .forEach(System.out::println);

与所有的流转换一样,sorted方法会产生一个新的流,它的元素是原有流中按照排序排列的元素。

peek方法会产生另一个流,它的元素与原来流中的元素相同,但是在每次获取一个元素时,都会调用一个函数。

Object[] objects = Stream.iterate(1.0, p -> p * 2)
    .peek(e -> System.out.println("Fetching " + e))
    .limit(20).toArray();

当实际访问一个元素时,就会打印出来一条消息。通过这种方式,你可以验证iterate返回的无限流时被惰性处理的。

简单约简

约简是一种终结操作,它们会将流约简为可以在程序中使用的非流值

**count方法会返回流中元素的数量,这是一种简单约简。**其他简单约简还有maxmin,它们分别返回最大值和最小值。这些方法返回的是一个类型的Optional<T>的值,它要么在其中包装了答案,要么表示没有任何值。在过去,碰到这种情况返回null是很常见的,但是这样做会导致在未做完备测试的程序中产生空指针异常。

如果获取流中的最大值:(min方法同理)

Optional<Double> max = Stream.iterate(1.0, p -> p * 2)
    .limit(3)
    .max(Double::compareTo);
System.out.println(max.orElse(0.0));

**findFirst返回的是非空集合中的第一个值,它通常在与filter组合使用时很有用。**例如如何找到第一个以字母Q开头的单词

Optional<String> q = list.stream()
    .filter(s -> s.startsWith("Q"))
    .findFirst();

如果不强调使用第一个匹配,而是使用任意的匹配都可以,那么就可以使用findAny方法。这个方法在执行并行流时很有效。

Optional<String> optionalString = list.stream()
    .filter(s -> s.startsWith("Q"))
    .findFirst();

如果只想知道是否存在匹配,那么可以使用anyMatch。这个方法会接受一个断言引元,因此不需要使用filter

boolean q = list.stream().parallel().anyMatch(s -> s.startsWith("Q"));

还有allMatchnoneMatch方法,它们分别在所有元素没有任何元素匹配谓词的情况下返回true。这些方法也可以通过并行运行而获益。

Optional类型

Optional<T>对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。对于第一种情况,我们称这种值是存在的。

获取Optional

有效地使用Optional的关键是要使用这样的方法:它在值不存在的情况下会产生一个可替代物,而只有在值存在的情况下才会使用这个值。

optionalString.orElse("");

你还可以调用代码来计算默认值

optionalString.orElseGet(() -> String.valueOf(1));

或者在没有任何值时抛出异常:

optionalString.orElseThrow(RuntimeException::new);

消费Optional

ifPresent方法会接受一个函数。如果可选值存在,那么它会被传递给该函数。否则,不会发生任何事情。

optionalString.ifPresent(s -> list.add(s));

如果可选值存在时执行一种动作,不存在时执行另一种动作,可以使用ifPresentOrElseJDK1.9提供):

ifPresentOrElse(v -> System.out.println("f" + v), () -> logger.warning("NO match"));

管道化Optional

保持Optional完整,使用map方法来转换Optional内部的值:如果optionalString为空,那么s也为空。

Optional<String> s = optionalString.map(String::toUpperCase);

我们将一个结果添加到列表中,如果它存在的话就添加,不存在则什么也不会发生

optionalString.map(list::add);

类似地,我们可以使用filter方法来只处理那些在转换它之前或之后满足某种特定属性的Optional值。如果不满足该属性,那么管道会产生空的结果:

Optional<String> transformed = optionalString.filter(s -> s.length() > 4)
    .map(String::toUpperCase);

你也可以使用or方法将空Optional替换为一个可替代的Optional。这个可替代值将以惰性方式计算。(JDK1.9提供)

optionalString.or(() -> {});

不适合使用Optional值的方式

如果没有正确地使用Optional值,那么相比以往得到某物或null的方式,你并没有任何好处。

创建Optional

Optional.empty();
Optional.of(obj);

收集结果

当处理完流之后,通常会想要查看其结果。因此可以调用iterator方法打,它会产生用来访问元素的旧式风格的迭代器,或者使用forEach方法,将某个函数应用于每个元素;

Stream.of("qwe1", "qwe12", "qwe123", "qwe")
    .forEach(System.out::println);

我们可以使用toArray()方法来创建指定类型的数组

String[] strs = stream.toArray(String::new);

针对将流中的元素收集到另一个目标中,有一个便捷方法collect可用,它会接受一个Collector接口的实例。

List<String> q = list.stream().filter(s -> s.startsWith("Q")).collect(Collectors.toList());
Set<String> q1 = list.stream().filter(s -> s.startsWith("Q")).collect(Collectors.toSet());
String q2 = list.stream().filter(s -> s.startsWith("Q")).collect(Collectors.joining());
String q3 = list.stream().filter(s -> s.startsWith("Q")).collect(Collectors.joining(","));

收集到映射表中

Collectors.toMap(key, value)方法会收集为Map集合

List<User> list = new ArrayList<>();
list.add(new User("qwe", 1));
list.add(new User("qwe1",2));
list.add(new User("qwe2",3));
list.add(new User("qwe3",4));
Map<String, Integer> collect = list.stream().collect(Collectors.toMap(User::getName, User::getNum));

群组和分区

使用Collectors.groupingBy()进行分组

Map<Integer, List<User>> collect1 = list.stream().collect(Collectors.groupingBy(User::getNum));

输出结果

1=[eight.User@723279cf]
2=[eight.User@10f87f48, eight.User@b4c966a]
4=[eight.User@2f4d3709]

当分类函数是断言函数(返回Boolean值)时,流的元素可以分为两个列表:该函数返回true的元素和其他元素。这种情况下,使用partitioningBy比使用groupingBy更高效。

Map<Boolean, List<User>> collect = list.stream().collect(Collectors.partitioningBy(s -> s.getNum() > 2));
collect.entrySet().stream().forEach(System.out::println);

输出结果

false=[eight.User@12edcd21, eight.User@34c45dca, eight.User@52cc8049]
true=[eight.User@5b6f7412]

下游收集器

counting会产生收集到的元素的个数。如

Map<Integer, Long> collect2 = list.stream().collect(Collectors.groupingBy(User::getNum, Collectors.counting()));
collect2.entrySet().stream().forEach(System.out::println);

summing(Int/Long/Double)会接受一个函数作为引元,将该函数应用到下游元素中,并产生它们的和。如

Map<Integer, Integer> collect = list.stream().collect(Collectors.groupingBy(User::getNum, Collectors.summingInt(User::getNum)));
collect.entrySet().stream().forEach(System.out::println);

maxByminBy会接收一个比较器,并分别产生下游元素中的最大值和最小值。如

Map<Integer, Optional<User>> collect = list.stream()
    .collect(Collectors.groupingBy(User::getNum, Collectors.maxBy(Comparator.comparing(User::getNum))));

collectingAndThen收集器在收集器后面添加了一个最终处理步骤。如:

Map<Integer, Integer> collect2 = list.stream()
    .collect(Collectors.groupingBy(
        User::getNum, 
        Collectors.collectingAndThen(Collectors.toSet(), Set::size)));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值