第四章:使用流

本章内容:

  筛选、切片和匹配

  查找、匹配和归约

  使用数值范围等数值流

  从多个源创建流

  无限流

4.1 筛选和切片

4.1.1 用谓词筛选

  Stream接口支持filter方法,该方法会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流

Dish.menu.stream().filter(Dish::isVegetarian).collect(toList()); // 方法引用检查菜肴是否为素食

4.1.2 筛选各异的元素

  流还支持一个叫distinct的方法,它会返回一个元素各异(根据流所产生元素的hashCode和equals方法实现)的流。例如,下面代码会筛选出列表中所有的偶数,并确保没有重复

List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream().distinct().forEach(System.out::println);

4.1.3 截短流

  流支持limit方法,该方法会返回一个不超过给定长度的流。所需要的长度作为参数传递给limit。例如下面代码选出超过300卡路里的头三道菜

List<Dish> limit3 = Dish.menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());

4.1.4 跳过元素

List<Dish> skip2 = Dish.menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());

练习:如何筛选前两个荤菜?

List<Dish> meatLimit2 = Dish.menu.stream().filter(d -> d.getType() == Dish.Type.MEAT).limit(2).collect(toList());

4.2 映射

4.2.1 对流中每一个元素应用函数

  流支持map方法,它接受一个函数作为参数。这个函数会应用到每个元素上,并将其映射成一个新的元素,下面的代码来提取菜肴的名称

List<String> mapList = Dish.menu.stream().map(Dish::getName).collect(toList());

4.2.2 流的扁平化

  如果现在想返回单词里的各不相同的字符

List<String> list = Arrays.asList("Hello", "World");
List<String[]> wordArr = list.stream().map(word -> word.split("")).distinct().collect(toList());

  1. Arrays.stream()

    Arrays.stream()接受一个数组并产生一个流

String[] array = {"Hello", "World"};
Stream<String> stream = Arrays.stream(array);
List<Stream<String>> strngStream = list.stream().map(word -> word.split("")).map(Arrays::stream).distinct().collect(toList());

  2. 使用flatMap

    flatMap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流

List<String> words = list.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());

4.3 查找与匹配

4.3.1 至少匹配一个元素(anyMatch)

Dish.menu.stream().anyMatch(Dish::isVegetarian)

4.3.2 是否匹配所有元素(allMatch)、是否不匹配任何元素(noneMatch)

Dish.menu.stream().allMatch(d -> d.getCalories() < 1000)
Dish.menu.stream().noneMatch(d -> d.getCalories() >= 1000)

4.3.3 查找任意一个元素(findAny)

// Optional类是一个容器类,代表一个值存在或者不存在,用来解决空指针异常。Optional类后面细讲
Optional<Dish> findAny = Dish.menu.stream().filter(Dish::isVegetarian).findAny();
if(findAny.isPresent()) {
    System.out.println(findAny.get().getName());
}
Dish.menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d -> System.out.println(d.getName()));

4.3.4 查找第一个元素(findFirst)

List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> findFirst = someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst();
if(findFirst.isPresent()) {
    System.out.println(findFirst.get());
}

4.4 归约

4.4.1 元素求和

Integer sum = someNumbers.stream().reduce(0, (a, b) -> a + b);

reduce接受两个参数:

  1. 一个初始值,这里是0

  2. 一个BinaryOperator<T>来将两个元素结合起来产生一个新值,这里我们用的Lambda是 (a, b) -> a + b

  如果想把所有元素相乘,只需要将另一个Lambda (a, b) -> a * b传递给reduce就行了

在java8中,Integer类提供了一个静态的sum方法来对两个数求和,那么就可以使用方法引用的写法:

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

无初始值:

  reduce还有一个重载的方法,不接受初始值,但是返回一个Optional对象

Optional<Integer> reduce = someNumbers.stream().reduce(Integer::sum);

4.4.2 最大值和最小值

Optional<Integer> max = someNumbers.stream().reduce(Integer::max);
if(max.isPresent()) {
    System.out.println(max.get());
}
someNumbers.stream().reduce(Integer::min).ifPresent(min -> System.out.println(min));;

总结:

操作 类型 返回类型 使用的类型/函数式接口 函数描述符    
filter 中间 Stream<T> Predicate<T> T -> boolean
distinct 中间 Stream<T>    
skip 中间 Stream<T> long  
limit 中间 Stream<T> long  
map 中间 Stream<R> Function<T, R> T -> R
flatMap 中间 Stream<R> Function<T, Stream<R>> T -> Stream<R>
sorted 中间 Stream<T> Comparator<T> (T, T) -> int
anyMatch 终端 boolean Predicate<T> T -> boolean
noneMatch 终端 boolean Predicate<T> T -> boolean
allMatch 终端 boolean Predicate<T> T -> boolean
findAny 终端 Option<T>    
findFirst 终端 Option<T>    
forEach 终端 void Consumer<T> T -> void
collect 终端 R Collector<T, A, R>  
reduce 终端 Option<T> BinaryOperator<T> (T, T) -> T
count 终端 long    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

练习:

  1. 找出2011年发生的所有交易,并按交易额排序(从低到高)

  2. 交易员都在哪些不同的城市工作过

  3. 查找所有来自剑桥的交易员,并按姓名排序

  4. 返回所有交易员的姓名字符串,按字母顺序排序

  5. 有没有交易员是在米兰工作的

  6. 打印生活在剑桥的交易员的所有交易额

  7. 所有交易中,最高的交易额是多少

  8. 找出交易额最小的交易

交易员与交易实体类及数据如下:

// 交易员
public  class Trader{
    
    private String name;
    private String city;

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

    public String getName(){
        return this.name;
    }

    public String getCity(){
        return this.city;
    }

    public void setCity(String newCity){
        this.city = newCity;
    }

    public String toString(){
        return "Trader:"+this.name + " in " + this.city;
    }
}
// 交易
public class Transaction{

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

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

    public Trader getTrader(){ 
        return this.trader;
    }

    public int getYear(){
        return this.year;
    }

    public int getValue(){
        return this.value;
    }
    
    public String toString(){
        return "{" + this.trader + ", " +
               "year: "+this.year+", " +
               "value:" + this.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)
 );

解答:

  1. 找出2011年发生的所有交易,并按交易额排序(从低到高)

     List<Transaction> sort1 = transactions.stream().filter(t -> t.getYear() == 2011)
                             .sorted(comparing(Transaction::getValue))
                             .collect(toList());
        // 倒序1
        List<Transaction> sort2 = transactions.stream().filter(t -> t.getYear() == 2011)
                 .sorted((t1, t2) -> ((Integer) t2.getValue()).compareTo(t1.getValue()))
                 .collect(toList());
        // 倒序2
        List<Transaction> sort3 = transactions.stream().filter(t -> t.getYear() == 2011).collect(toList());
        Comparator<Transaction> c = (t1, t2) -> ((Integer) t1.getValue()).compareTo(t2.getValue());
        sort3.sort(c.reversed());

  2. 交易员都在哪些不同的城市工作过

List<String> citys = transactions.stream().map(t -> t.getTrader().getCity()).distinct().collect(toList());

  3. 查找所有来自剑桥的交易员,并按姓名排序

List<Trader> names = transactions.stream().filter(t -> t.getTrader().getCity().equals("Cambridge"))
                             .map(Transaction::getTrader)
                             .distinct()
                             .sorted(comparing(Trader::getName))
                             .collect(toList());

  4. 返回所有交易员的姓名字符串,按字母顺序排序

String nameStr = transactions.stream().map(t -> t.getTrader().getName())
                             .distinct()
                             .sorted()
                             .reduce("", (n1, n2) -> n1 + n2);

  // 优化(reduce对字符串做相加的时候效率不高,每次迭代都会创建一个新的String对象,可以用joining(其内部用到StringBuild)),下一章会讲到
  nameStr = transactions.stream().map(t -> t.getTrader().getName())
                   .distinct()
                   .sorted()
                   .collect(Collectors.joining());

  5. 有没有交易员是在米兰工作的

boolean anyMatch = transactions.stream().anyMatch(t -> t.getTrader().getCity().equals("Milan"));

  6. 打印生活在剑桥的交易员的所有交易额 

transactions.stream().filter(t -> t.getTrader().getCity().equals("Cambridge"))
                             .map(t -> t.getValue())
                             .forEach(System.out::println);

  7. 所有交易中,最高的交易额是多少

Optional<Integer> maxT = transactions.stream().map(Transaction::getValue).reduce(Integer::max);

  8. 找出交易额最小的交易

Optional<Transaction> minT = transactions.stream().reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
// 流也支持min和max方法
minT = transactions.stream().min(comparing(Transaction::getValue));

 4.5 数值流

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

上面的代码有一个暗含的拆箱成本,每一个Integer都必须拆箱成一个原始类型,再进行求和

4.5.1 原始类型特化

  java8引入了三个原始类型特化流的接口:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、double和long,从而避免了暗含的拆箱操作

  1. 映射到数值流

int intStreamSum = Dish.menu.stream().mapToInt(Dish::getCalories).sum();

  如果流是空的,sum默认返回0。IntStream还支持其他方便的方法,如max、min、average等  

  2. 转换回对象流

    要把原始流转换成一般流(每个int都会装箱成Integer),可以用boxed方法

IntStream intStream = Dish.menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> integerStream = intStream.boxed();

  3. 默认值OptionalInt

    求和的例子很简单,因为sum有默认值为0,如果让你计算IntStream中的最大元素该怎么办

OptionalInt maxCalories = Dish.menu.stream().mapToInt(Dish::getCalories).max();
int defaultMaxCalories = maxCalories.orElse(1); // 如果没有最大值的话,显式提供一个默认的最大值

4.5.2 数值范围

   java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成数值范围:range和rangeClosed。这两个方法都是第一个接受起始值,第二个接受结束值。但range不包含结束值,rangeClosed则包含结束值

IntStream evenNumbers = IntStream.range(1, 100) // 表示[1, 100)
                                 .filter(n -> n % 2 == 0);// 一个1到100的偶数流

4.6 构建流

4.6.1 由值创建流

  你可以直接用静态方法Stream.of,通过显式值创建一个流

Stream<String> strStream = Stream.of("java8", "in", "action");
strStream.map(String::toUpperCase).forEach(System.out::println);

4.6.2 由数组创建流

  你可以使用静态方法Arrays.stream从数组创建一个流

int[] arrNumbers = {1,2,3,4,5,6};
int arrSum = Arrays.stream(arrNumbers).sum();

4.6.3 由函数生成流:无限流

  Stream API提供了两个静态方法从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流(没有固定大小的流)

 1. 迭代:

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

 2. 生成:

Stream.generate(Math::random).forEach(System.out::println);

 

备注:

摘自文献:《Java8实战》(中文版)《Java8 in Action》(英文版)

 

代码(GitHub地址): https://github.com/changlezhong/java8InAction

 

posted on 2018-06-05 22:12  changlezhong  阅读(251)  评论(0)    收藏  举报

导航