Java8实战 — 使用流

筛选和切片

用谓词筛选

    Streams接口支持filter方法。该操做会接收一个谓词做为参数,并返回一个包含全部符合谓词的元素的流。例如,要获得一个素食的菜单:java

menus.stream().filter(Dish::isVegetarian).forEach(System.out::println);

筛选各异的元素

    流还支持一个叫作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)方法,该方法会返回一个不超过给定长度的流。所需的长度做为参数传递给limit方法。若是流是有序的,则最多会返回前n个元素。好比,选出热量超过300卡路里的前三道菜:dom

menus.stream().filter(d -> d.getCalories() > 300).limit(3).forEach(System.out::println);

跳过元素

    流还支持skip(n)方法,返回一个扔掉了前n个元素的流。若是流中元素不足n个,则返回一个空流,例如,跳过超过300卡路里的头两道菜,并返回剩下的:ide

menus.stream().filter(d -> d.getCalories() > 300).skip(2).forEach(System.out::println);

映射

    一个很是常见的数据处理套路就是从某些对象中选择信息。好比在SQL里,你能够从表中选择一列。Stream API也经过map和flatMap方法提供了相似的工具。函数

对流中每个元素应用函数

    流支持map方法,他会接受一个函数做为参数。这个函数会被应用到每一个元素上,并将其映射成一个新的元素。例如,下面的代码把方法引用Dish::getName传给了map方法,来提取流中菜肴的名称:工具

menus.stream().map(Dish::getName).map(String::length).forEach(System.out::println);

流的扁平化

    流支持flatmap方法,让你把一个流中的每一个流都换成另外一个流,而后把全部的流链接起来成为一个流。例如,给定两个数字列表,返回全部的数对:this

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(4, 5);
        
numbers1.stream().flatMap(
i -> numbers2.stream().map(j -> new int[] {i, j}))
.forEach(n -> System.out.println(n[0] + "," + n[1]));

查找和匹配

    另外一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。spa

检查谓词是否至少匹配一个元素

    anyMatch方法能够回答“流中是否有一个元素能匹配给定的谓词”。好比,你能够用它来查看菜单里面是否有素食能够选择:.net

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

检查谓词是否匹配全部元素

    allMatch方法的工做原理和anyMatch相似,但它会看看流中的元素是否都匹配给定的谓词。好比,你能够用它来看看菜品是否有利健康(即全部菜品的热量都低于500卡路里):code

boolean isHealthy = menus.stream().allMatch(d -> d.getCalories() < 500);

查找元素

    findAny方法将返回当前流中的任意元素。它能够与其余流操做结合使用。好比,你可能想找到一道素食菜肴。你能够结合使用filter和findAny方法来实现:

menus.stream().filter(Dish::isVegetarian).findAny().ifPresent(System.out::println);

    ifPresent方法是Optional<T>类的方法,这个类是一个容器,表明一个值存在或不存在。

查找第一个元素

    有些流有一个出现顺序来指定流中项目出现的逻辑顺序。对于这种流,你可能想要找到第一个元素。为此有一个findFirst方法,它的工做方式相似与findAny方法。例如,给定一个数字列表,找出第一个平方能被3整除的数:

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst().ifPresent(System.out::println);

归约

    到目前为止,你见到过的终端操做都是返回一个boolean、void或Optional对象。在本节中,你将看到如何把一个流中的元素组合起来,使用reduce操做来表达更复杂的查询。

元素求和

    能够想下面这样对流中全部的元素求和:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

    也能够很容易的把全部元素相乘:

int product = numbers.stream().reduce(1, (a, b) -> a * b);

    在Java8中,Integer类如今有了一个静态的sum方法来对两个数求和:

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

最大值和最小值

    还能够用归约计算最大值和最小值:

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

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

付诸实践

    将学到的流知识付诸实践,换个例子来看看流如何使用,如今建立两个类,交易和交易员:

package cn.net.bysoft.chapter5;

public class Trader {
    
    private final String name;
    private final String city;

    public Trader(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public String getCity() {
        return city;
    }
    
    @Override
    public String toString() {
        return name;
    }

}
package cn.net.bysoft.chapter5;

public class Transaction {

    private final Trader trader;
    private final int year;
    private final int value;

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return trader;
    }

    public int getYear() {
        return year;
    }

    public int getValue() {
        return value;
    }

    @Override
    public String toString() {
        return year + ": " + value;
    }
}

    而后实例化一些交易员和交易:

Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

        List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000), 
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710), 
                new Transaction(mario, 2012, 700), 
                new Transaction(alan, 2012, 950));

    接下来进行8个练习:

  1. 找出2011年全部交易并按交易额排序(从低到高)
  2. 交易员都在那些不一样的城市工做
  3. 查找全部来自于剑桥的交易员,并按照姓名排序
  4. 返回全部交易员的姓名字符串,并按字母顺序排序
  5. 没有没交易员是在米兰工做的
  6. 打印生活在剑桥的交易员的全部交易额
  7. 全部交易中,最高的交易额是多少
  8. 找到交易额中最小的交易
package cn.net.bysoft.chapter5;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.joining;

public class Example5 {
    public static void main(String[] args) {

        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

        List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000), 
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710), 
                new Transaction(mario, 2012, 700), 
                new Transaction(alan, 2012, 950));
        
        // 找出2011年全部交易并按交易额排序(从低到高)
        System.out.println("找出2011年全部交易并按交易额排序(从低到高)");
        List<Transaction> tr2011 = transactions.stream()
                .filter(transaction -> transaction.getYear() == 2011)
                .sorted(comparing(Transaction::getValue))
                .collect(toList());
        tr2011.stream().forEach(System.out::println);
        
        // 交易员都在那些不一样的城市工做
        System.out.println("\n交易员都在那些不一样的城市工做");
        List<String> cities = transactions.stream()
                .map(transaction -> transaction.getTrader().getCity())
                .distinct()
                .collect(toList());
//        另外一种去掉重复的方法
//        Set<String> cities_set = transactions.stream()
//                .map(transaction -> transaction.getTrader().getCity())
//                .collect(toSet());
        cities.stream().forEach(System.out::println);
        
        // 查找全部来自于剑桥的交易员,并按照姓名排序
        System.out.println("\n查找全部来自于剑桥的交易员,并按照姓名排序");
        List<Trader> traders = transactions.stream()
                .map(transaction -> transaction.getTrader())
                .filter(trader -> "Cambridge".equals(trader.getCity()))
                .distinct()
                .sorted(comparing(Trader::getName))
                .collect(toList());
        traders.forEach(System.out::println);
        
        // 返回全部交易员的姓名字符串,并按字母顺序排序
        System.out.println("\n返回全部交易员的姓名字符串,并按字母顺序排序");
        String traderStr = transactions.stream()
                .map(transaction -> transaction.getTrader().getName())
                .distinct()
                .sorted()
                .collect(joining());
        System.out.println(traderStr);
        
        // 没有没交易员是在米兰工做的
        System.out.println("\n没有没交易员是在米兰工做的");
        boolean milanBased = transactions.stream()
                .anyMatch(transaction -> "Milan".equals(transaction.getTrader().getCity()));
        System.out.println(milanBased);
        
        // 打印生活在剑桥的交易员的全部交易额
        System.out.println("\n打印生活在剑桥的交易员的全部交易额");
        transactions.stream()
                .filter(transaction -> "Cambridge".equals(transaction.getTrader().getCity()))
                .map(Transaction::getValue)
                .forEach(System.out::println);
        
        // 全部交易中,最高的交易额是多少
        System.out.println("\n全部交易中,最高的交易额是多少");
        Optional<Integer> highestValue = transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer::max);
        System.out.println(highestValue.get());
        
        // 找到交易额中最小的交易
        System.out.println("\n找到交易额中最小的交易");
        Optional<Transaction> smallestTransaction = 
                transactions.stream()
                .min(comparing(Transaction::getValue));
        System.out.println(smallestTransaction.get().getValue());
    }
}

数值流

    在前面,使用reduce方法计算流中元素的总和。例如:

int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);

    这段代码的问题是,它有一个暗含的装箱成本。每一个Integer都必须被装箱成一个原始类型,在进行求和。

    Java8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每一个接口都带来了进行经常使用数值归约的新方法:

    映射到数据流,将流转换为特化版本的经常使用方法是mapToInt、mapToDouble和mapToLong:

// 映射到数据流
int calories = menus.stream().mapToInt(Dish::getCalories).sum();

    一样,一旦有了数值流,还能够把它转换回对象流:

// 转换回对象流
Stream<Integer> stream = menus.stream().mapToInt(Dish::getCalories).boxed();

    要找到IntStream中的最大元素,能够调用max方法,若是没有最大值的话,能够显示处理OptionalInt方法去定义一个默认值:

// 默认值OptionalInt
OptionalInt maxCalories = menus.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1);

    另外,若是要生成1和100之间的全部数字,可使用range和rangeClosed方法,这两个方法都接收2个参数,第一个是起始值,第二个是结束值,range不包含结束值,rangeClosed包含结束值,来看一个例子:

// 数值范围1-100,并得到其中的偶数
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
// 输出个数
System.out.println(evenNumbers.count());

构件流

    建立流的方法有许多,能够从值序列、数组和文件来建立流,还能够建立无限流。

由值建立流

    使用静态方法Stream.of,经过显示值建立一个流。它能够接受任意数量的参数。例如,建立一个字符串流,将全部字符串转换成大写,在打印出来:

// 由值建立流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
String word = stream.map(String::toUpperCase).collect(joining());
System.out.println(word);

由数组建立流

    使用静态方法Arrays.stream从数组建立流。例如,能够将一个原始类型int的数组转换成一个IntStream:

// 由数组建立流
int[] numbers = { 2, 3, 5, 7, 11, 13 };
int sum = Arrays.stream(numbers).sum();
System.out.println(sum);

由文件生成流

    Java中用于处理文件等I/O的操做的NIO API(非阻塞I/O)已更新,以便利用Stream API。

    java.nio.file.Files中的不少静态方法都会返回一个流。例如,一个颇有用的方法是Files.lines,它返回一个由指定文件中的各行构成的字符串流。能够编写一段程序看看一个文件中有多少不相同的词:

// 由文件生成流
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("c:\\data.txt"), Charset.defaultCharset())) {
    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();
} catch (IOException e) {}
System.out.println(uniqueWords);

建立无限流

    Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。这两个操做均可以建立无限流:不像从固定集合建立的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需建立值,所以能够无穷无尽的计算下去!通常来讲,应该使用limit(n)来对这种流加以限制。

迭代

    接受一个初始值,还有一个依次应用在每一个产生的新值上的Lambda,返回一个正偶数流:

Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);

生成

    生成一个流,其中有五个0到1之间的随机双精度数:

Stream.generate(Math::random).limit(5).forEach(System.out::println);
相关文章
相关标签/搜索