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);
相關文章
相關標籤/搜索