1. 程式人生 > >Java8學習筆記之Stream API

Java8學習筆記之Stream API

Stream是Java8引入的一個重度使用lambda表示式的API。
Stream可以用流的方式處理資料集合,在Java8之前,我們處理這些集合,是需要迭代器的,比如iterator,這是外部迭代;而Stream是內部迭代,我們不用關心集合內部元素是如何迭代的,計算機會自動幫我們選擇最適合的實現方式。

如何建立一個流

  1. 最常見的,有一個集合物件List<String> strs = Arrays.asList("Java 8 ", "Lambdas ", "In ", "Action");,直接呼叫strs.stream()就得到一個Stream<String>
    的流。
    如果想使用並行流增加效能,請使用strs.parallelStream(),或strs.stream().parallel()
  2. 由值建立:Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
  3. 由陣列建立:
        int[] numbers = {2, 3, 5, 7, 11, 13};
        int sum = Arrays.stream(numbers).sum();
  1. 由檔案建立:
        // 統計文字檔案中有多少個不同的單詞
        long
uniqueWords = 0; try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) { uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); } catch (IOException e) { }
  1. 由函式生成流:Stream.iterate()Stream.generate()可以生產無限流,即元素有無窮多個。一般來說,應該使用limit(n)來對這種流加以限制,以避免產生無窮多個元素。
    public void iterator() {
        Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
    }

    public void generate() {
        Stream.generate(Math::random).limit(5).forEach(System.out::println);
    }

Stream常用方法

Stream API 支援兩種型別的操作:中間操作(如filter或map)和終端操作(如count、findFirst、forEach和reduce)。

我用一個篩選選單的需求作為示例。

準備工作

public class Dish {
        private final String name;
        private final boolean vegetarian;
        private final int calories;
        private final Type type;

        public Dish(String name, boolean vegetarian, int calories, Type type) {
            this.name = name;
            this.vegetarian = vegetarian;
            this.calories = calories;
            this.type = type;
        }

        public String getName() {
            return name;
        }

        public boolean isVegetarian() {
            return vegetarian;
        }

        public int getCalories() {
            return calories;
        }

        public Type getType() {
            return type;
        }

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

public enum Type {MEAT, FISH, OTHER}
public List<Dish> init() {
        return Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));
    }

過濾篩選

謂詞:返回boolean的函式

  • filter():接受一個謂詞,返回符合條件的元素集合
    @Test
    public void filter() {
        List<Dish> menu = init();
        List<Dish> vegetarianMenu = menu.stream()
                .filter(Dish::isVegetarian)
                .collect(Collectors.toList());
        Assert.assertEquals(4, vegetarianMenu.size());
    }
  • distinct():返回集合中各異的元素集合(去重)
    @Test
    public void distinct() {
        List<Integer> numbers = Arrays.asList(5, 1, 2, 1, 3, 3, 2, 4);
        numbers.stream().distinct().forEach(System.out::println);
    }
  • limit():擷取流中指定數量的元素,返回一個不超過給定長度的流。如果流是有序的,則最多會返回前n個元素。
    @Test
    public void limit() {
        List<Dish> menu = init();
        menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .forEach(System.out::println);
    }
  • skip():跳過指定數量元素,返回一個扔掉了前n個元素的流。如果流中元素不足n個,則返回一個空流。
    @Test
    public void skip() {
        List<Dish> menu = init();
        menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .skip(2)
                .forEach(System.out::println);
    }

對映

  • map():接受一個函式作為引數。這個函式會被應用到每個元素上,並將其對映成一個新的元素。
    @Test
    public void map() {
        List<Dish> menu = init();
        List<String> dishNames = menu.stream().map(m -> m.getName()).collect(Collectors.toList());
    }
  • flatMap():一個流中的每個值都換成另一個流,然後把所有的流連線起來成為一個流,即扁平化為一個流。
    @Test
    public void flatMap() {
        String[] arrayOfWords = {"Goodbye", "World"};
        List<String> words = Arrays.asList(arrayOfWords);
        words.stream()
                .map(w -> w.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .forEach(System.out::println);
    }

上面例子中,split()得到的是String[] 而不是String,因此各個陣列並不是分別對映成一個流,而是對映成流的內容。所有使用map(Arrays::stream)時生成的單個流都被合併起來,變為一個流。

匹配

匹配比較簡單,返回一個boolean
- anyMatch():至少匹配一個
- allMatch():全部匹配
- noneMatch():全部不匹配,和allMatch相反

    @Test
    public void anyMatch() {
        List<Dish> menu = init();
        Assert.assertEquals(true, menu.stream().anyMatch(Dish::isVegetarian));
    }

    @Test
    public void allMatch() {
        List<Dish> menu = init();
        Assert.assertEquals(true, menu.stream().allMatch(d -> d.getCalories() < 1000));
    }

    @Test
    public void noneMatch() {
        List<Dish> menu = init();
        Assert.assertEquals(true, menu.stream().noneMatch(d -> d.getCalories() >= 1000));
    }

查詢

查詢有2個方法:findFirst()findAny(),返回一個Optional<T>集合。
如果你不關心返回的元素是哪個,請使用findAny(),因為它在使用並行流時限制較少。

    @Test
    public void findFirst() {
        List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstSquareDivisibleByThree =
                someNumbers.stream()
                        .map(x -> x * x)
                        .filter(x -> x % 3 == 0)
                        .findFirst(); // 9
        System.out.println(firstSquareDivisibleByThree.get());
    }

    @Test
    public void findAny() {
        List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstSquareDivisibleByThree =
                someNumbers.stream()
                        .map(x -> x * x)
                        .filter(x -> x % 3 == 0)
                        .findAny(); // 9
        System.out.println(firstSquareDivisibleByThree.get());
    }

歸約

歸約在彙總結合內所有資料的時候使用。比如求 max,min,sum。

    @Test
    public void reduce() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int sum = numbers.stream().reduce(0, Integer::sum);
        System.out.println(sum);
    }

原始型別流特化

流在內部迭代的過程中,對基本型別會自動裝箱和拆箱。為了避免不需要的裝箱拆箱,Java8提供了IntStreamDoubleStreamLongStream
- 普通流轉特化流:mapToInt(), mapToLong(), mapToDouble()
- 特化流轉普通流:boxed()

    public void boxedStream() {
        List<Dish> menu = init();
        // 特化
        IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
        // 轉回普通Stream
        Stream<Integer> stream = intStream.boxed();
    }

Java 8引入了兩個可以用於IntStream和LongStream的靜態方法,用於生成給定範圍的數字流:
- range(min, max):隨機生成的數字不包含max,即(min, max)
- rangeClosed(min, max):隨機生成的數字包含max,即(min, max]