Java8特性③Stream的使用

篩選和切片

  • filter 方法java

  • distinct 方法數組

  • limit 方法dom

  • skip 方法ide

謂詞篩選

Stream 接口支持 filter 方法,該操做會接受一個謂詞(一個返回 boolean的函數)做爲參數,並返回一個包括全部符合謂詞的元素的流。函數

List<Dish> dishes = Dish.menu.stream()
      .filter(Dish::isVegetarian)
      .collect(Collectors.toList());

篩選重複的元素

Stream 接口支持 distinct 的方法, 它會返回一個元素各異(根據流所生成元素的 hashCode和equals方法實現)的流。例如,如下代碼會篩選出列表中全部的偶數,並確保沒有 重複。this

List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream().filter(i -> i % 2 == 0)
      .distinct() //去重元素2
      .forEach(System.out::println);

限制元素數量

Stream 支持limit(n)方法,該方法會返回一個不超過給定長度的流。所需的長度做爲參數傳遞 給limit。若是流是有序的,則最多會返回前n個元素。spa

List<Dish> dishLimits = Dish.menu.stream()
        .filter(d -> d.getCalories() > 300)
        .limit(3) //只返回符合要求的前3個元素
        .collect(Collectors.toList());

跳過指定數量的元素

Stream 支持 skip(n) 方法,返回一個扔掉了前n個元素的流。若是流中元素不足n個,則返回一 個空流。limit(n) 和 skip(n) 是互補的。code

List<Dish> dishSkip = Dish.menu.stream()
        .filter(d -> d.getCalories() > 300)
        .skip(2) //去掉符合要求的集合中的前2個元素後返回
        .collect(Collectors.toList());
dishSkip.forEach(System.out::println);

映射

map 操做

Stream 支持 map 方法,它會接受一個函數做爲參數。這個函數會被應用到每一個元素上,並將其映 射成一個新的元素對象

List<Integer> dishNames = Dish.menu.stream()
      .map(Dish::getName)
      .map(String::length)
      .collect(Collectors.toList());
 
List<String> words = Arrays.asList("Hello", "World");
List<Integer> wordLens = words.stream()
      .map(String::length) //轉爲字符串長度的集合
      .collect(Collectors.toList());

flatMap 操做

flatmap 方法讓你把一個流中的每一個值都換成另外一個流,而後把全部的流鏈接起來成爲一個流。blog

//使用flatMap找出單詞列表中各不相同的字符
List<String> words = Arrays.asList("Hello", "World");
List<String> wordMap = words.stream()
      .map(word -> word.split(""))
      .flatMap(Arrays::stream)
      .distinct()
      .collect(Collectors.toList());

給定兩個數字列表,如何返回全部的數對呢?例如,給定列表[1, 2, 3]和列表[3, 4],應該返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。

List<Integer> num1 = Arrays.asList(1, 2, 3);
List<Integer> num2 = Arrays.asList(4, 5);
List<int[]> pairs = num1.stream()
        .flatMap(i -> num2.stream().map(j -> new int[]{i, j}))
        .collect(Collectors.toList());
pairs.stream().forEach( i -> {
    Arrays.stream(i).forEach(System.out::println);

查找和匹配

anyMatch

流中是否有一個元素能匹配給定的謂詞。

if (Dish.menu.stream().anyMatch(Dish::isVegetarian)) {
     System.out.println("Vegetarion");
 }

allMatch

流中是否有全部元素能匹配給定的謂詞。

if (Dish.menu.stream().allMatch(d -> d.getCalories() < 1000)) {

     System.out.println("都有利於健康");
 }

nonMatch

流中是否有沒有任何元素能匹配給定的謂詞。

if (Dish.menu.stream().noneMatch(d -> d.getCalories() >= 1000)) {

     System.out.println("都有利於健康");
 }

findAny

findAny 方法將返回當前流中的任意一個元素。

Optional<Dish> dish = Dish.menu.stream().filter(Dish::isVegetarian)
         .findAny();
 dish.ifPresent(d -> System.out.println(d.toString()));

findFirst

findAny 方法將返回當前流中的第一個元素。

List<Integer> num1 = Arrays.asList(1, 2, 3, 4, 5);
 num1.stream().map(x -> x * x)
         .filter(x -> x % 3 == 0) //平方能被3整除的數
         .findFirst().ifPresent(x -> System.out.println(x));
      }

Optional

Optional<T>類(java.util.Optional)是一個容器類,表明一個值存在或不存在。Optional裏面y有幾種顯式地檢查值是否存在或處理值不存在的情形的方法:

  • isPresent()將在Optional包含值的時候返回true, 不然返回false。

  • ifPresent(Consumer<T> block))會在值存在的時候執行給定的代碼塊。

  • T get()會在值存在時返回值,不然拋出一個NoSuchElement異常。

  • T orElse(T other)會在值存在時返回值,不然返回一個默認值。

歸約(reduce)

把一個流中的元素組合起來,使用 reduce 操做來表達更復雜的查 詢,好比「計算菜單中的總卡路里」或「菜單中卡路里最高的菜是哪個」。此類查詢須要將流中全部元素反覆結合起來,獲得一個值,好比一個Integer。這樣的查詢能夠被歸類爲歸約操做 (將流歸約成一個值)。

reduce操做是如何做用於一個流的:Lambda反覆結合每一個元素,直到流被歸約成一個值。reduce方法接受兩個參數:一個初始值,這裏是0;一個 BinaryOperator<T> 來將兩個元素結合起來產生一個新值, 這裏咱們用的是 lambda (a, b) -> a + b

元素求和

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

int sum2 = numbers.stream().reduce(0,Integer::sum);
System.out.println(sum2);

最大值

int max = numbers.stream().reduce(0,Integer::max);
System.out.println(max);

最小值

//reduce不接受初始值,返回一個Optional對象(考慮流中沒有任何元素的狀況)
Optional<Integer> min = numbers.stream().reduce(Integer::min);
min.ifPresent(System.out::println);

數值流

原始類型流特化

Java 8引入了三個原始類型特化流接口來解決這個問題: IntStream 、 DoubleStream 和 LongStream,分別將流中的元素特化爲int、long和double,從而避免了暗含的裝箱成本。每 個接口都帶來了進行經常使用數值歸約的新方法,好比對數值流求和的sum,找到最大元素的max。 此外還有在必要時再把它們轉換回對象流的方法。這些特化的緣由並不在於流的複雜性,而是裝箱形成的複雜性——即相似int和Integer之間的效率差別。

  • 映射到數值流:將流轉換爲特化版本的經常使用方法是mapToInt、mapToDouble和mapToLong。這些方法和前 面說的map方法的工做方式同樣,只是它們返回的是一個特化流,而不是Stream<T>

int colories = Dish.menu.stream()
        .mapToInt(Dish::getCalories) //返回IntStream
        .sum();
  • 轉換回對象流

經過 box 方法能夠將數值流轉化爲 Stream 非特化流。

IntStream intStream = menu.stream().mapToInt(Dish::getCalories); //將Strean轉化爲數值流
Stream<Integer> stream = intStream.boxed(); //將數值流轉化爲Stream
  • 默認值 OptionalInt

Optional 能夠用 Integer、String等參考類型來參數化。對於三種原始流特化,也分別有一個Optional原始類 型特化版本:OptionalInt、OptionalDouble和OptionalLong。

Dish.menu.stream()
        .mapToInt(Dish::getCalories) //返回IntStream
        .max().ifPresent(System.out::println);

數值範圍

IntStream.rangeClosed(1, 100)
        .filter(x -> x % 10 == 0)
        .forEach(System.out::println);

Java 8引入了兩個能夠用於IntStream和LongStream的靜態方法,幫助生成這種範圍: range和rangeClosed。這兩個方法都是第一個參數接受起始值,第二個參數接受結束值。但 range是不包含結束值的,而rangeClosed則包含結束值。

數值流應用:勾股數

生成 (5, 12, 13)、(6, 8, 10)和(7, 24, 25) 這樣有效的勾股數數組集合。

Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100).boxed()
        .flatMap(a -> IntStream.rangeClosed(a,100)
                                .filter(b -> Math.sqrt(a * a + b * b) % 1 == 0).boxed()
                                .map(b -> new int[]{a,b,(int)Math.sqrt(a * a + b * b)})
        );
pythagoreanTriples.forEach(t -> System.out.println(t[0] + ";" + t[1] +";" + t[2]));

構建流

值建立流

Stream<String> streams = Stream.of("Java", "Python");
streams.map(String::toUpperCase).forEach(System.out::println);

Stream.concat(Stream.of("Java", "Python"), Stream.of("C++", "Ruby")).forEach(System.out::println);

數組建立流

int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int sum = Arrays.stream(numbers).sum();

文件生成流

String ret = Files.lines(Paths.get("/Users/liuguoquan/Java/java8/src/com/company/data.txt"), Charset.defaultCharset())
        .reduce("", (a, b) -> a + " " + b);

函數生成流:建立無限流

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

//生成
Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println);

    }

示例實戰

假設你是執行交易的交易員。你的經理讓你爲八個查詢找到答案。你能作到嗎?

  • (1) 找出2016年發生的全部交易,並按交易額排序(從低到高)。

  • (2) 交易員都在哪些不一樣的城市工做過?

  • (3) 查找全部來自於北京的交易員,並按姓名排序。

  • (4) 返回全部交易員的姓名字符串,按字母順序排序。

  • (5) 有沒有交易員是在深圳工做的?

  • (6) 打印生活在北京的交易員的全部交易額。

  • (7) 全部交易中,最高的交易額是多少?

  • (8) 找到交易額最小的交易。

交易員類

/**
 * 交易人
 * Created by liuguoquan on 2017/4/28.
 */
public class Trader {

    private String name;
    private String city;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

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

    @Override
    public String toString() {
        return "Trader{" +
                "name='" + name + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

交易類

/**
 * 交易單
 * Created by liuguoquan on 2017/4/28.
 */
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 trader;
    }

    public void setTrader(Trader trader) {
        this.trader = trader;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Transaction{" +
                "trader=" + trader +
                ", year='" + year + '\'' +
                ", value=" + value +
                '}';
    }
}

計算

public class TransactionProcess {

    public static void main(String[] args) {

        Trader liu = new Trader("Lau","Beijing");
        Trader lee = new Trader("Lee","Shanghai");
        Trader zhang = new Trader("Zhang","Guangzhou");
        Trader wang = new Trader("Wang","Beijing");

        List<Transaction> transactions = Arrays.asList(
                new Transaction(liu,2016,300),
                new Transaction(lee,2015,100),
                new Transaction(lee,2016,500),
                new Transaction(zhang,2016,9000),
                new Transaction(wang,2017,1000),
                new Transaction(liu,2016,1500)
        );

        // (1) 找出2016年發生的全部交易,並按交易額排序(從低到高)。
        transactions.stream().filter(t -> t.getYear() == 2016)
                .sorted(Comparator.comparing(Transaction::getValue))
                .collect(Collectors.toList());

        // (2) 交易員都在哪些不一樣的城市工做過?
        transactions.stream().map(t -> t.getTrader().getCity())
                .distinct()
                .collect(Collectors.toList());

        // (3) 查找全部來自於北京的交易員,並按姓名排序。
        transactions.stream().map(t -> t.getTrader())
                .filter(t -> t.getCity().equals("Beijing"))
                .distinct()
                .sorted(Comparator.comparing(Trader::getName))
                .collect(Collectors.toList());

        // (4) 返回全部交易員的姓名字符串,按字母順序排序。
        transactions.stream().map(t -> t.getTrader())
                .map(t -> t.getName())
                .distinct()
                .sorted()
                .collect(Collectors.toList());

        // (5) 有沒有交易員是在深圳工做的?
        boolean isExist = transactions.stream().anyMatch(t -> t.getTrader().getCity().equals("Shenzhen"));
        if (isExist) {
            System.out.println("有在深圳工做的");
        } else {
            System.out.println("沒有在深圳工做的");
        }

        // (6) 打印生活在北京的交易員的全部交易額。
        int sum = transactions.stream().filter(t -> t.getTrader().getCity().equals("Beijing"))
                .map(t -> t.getValue())
                .reduce(0,Integer::sum);
        System.out.println(sum);

        // (7) 全部交易中,最高的交易額是多少?
        int max = transactions.stream().map(t -> t.getValue())
                .reduce(0,Integer::max);
        System.out.println(max);

        // (8) 找到交易額最小的交易。
        int min = transactions.stream().map(t -> t.getValue())
                .reduce(0,Integer::min);
        System.out.println(min);
    }
}

小結

  • 中間操做表

操做 類型 返回類型 目的
filter 中間操做 Stream<T> 過濾元素
distinct 中間操做 Stream<T> 過濾重複的元素
skip 中間操做 Stream<T> 跳過指定數量的元素
limit 中間操做 Stream<T> 限制元素的數量
map 中間操做 Stream<T> 流的轉化
flatmap 中間操做 Stream<T> 流的扁平化
sorted 中間操做 Stream<T> 元素排序
  • 終端操做表

操做 類型 返回類型 目的
forEach 終端操做 void 消費流中的每一個元素,返回void
count 終端操做 long 返回流中元素的個數,返回long
collect 終端操做 R 把流歸約爲一個集合
anyMatch 終端操做 boolean 流中是否有符合要求的元素
noneMatch 終端操做 boolean 流中是否沒有任何符合要求的元素
allMatch 終端操做 boolean 流中是否全部元素都是符合要求的
findAny 終端操做 Optional<T> 查找符合要求的元素
findFirst 終端操做 Optional<T> 查找第一個符合要求的元素
reduce 終端操做 Optional<T> 歸約
相關文章
相關標籤/搜索