java流-Stream API

1,448 阅读3分钟

行为参数化传递

1.首先看一个小的例子

  • 我们要将一个库存排序,再进行苹果重量比较
// inventory 库存 
        Collections.sort(inventory, new Comparator<Apple>() {
            public int compare(Apple a1,Apple a2){
                return a1.getWeight().compareTo(a2.getWeight());
            }
        });

而用Stream来了做的话,是通过传递代码 将操作行为实现参数化的思想

inventory.sort(comparing(Apple::getWeight));

与1如出一辙的例子

  • 想要筛选一个目录中的所有隐藏文件
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
       public boolean accept(File file) {
           return file.isHidden();
       }
})

我们已经有了isHidden方法,那么我们将此方法传入函数中

File[] hiddenFiles = new File(".").listFiles(File::isHidden);   
  • 在Java 8里写下File::isHidden的时候,就创建了一个方法引用,同样可以传递它。

继续看一个例子

苹果类

public static class Apple {
        private int weight = 0;
        private String color = "";

        public Apple(int weight, String color){
            this.weight = weight;
            this.color = color;
        }

        public Integer getWeight() {
            return weight;
        }

        public void setWeight(Integer weight) {
            this.weight = weight;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }

        public String toString() {
            return "Apple{" +
                    "color='" + color + '\'' +
                    ", weight=" + weight +
                    '}';
        }
    }
  • 找出重量大于150的苹果
  • 找出苹果的颜色是绿色的苹果 不推荐的写法如下
List<Apple> inventory = Arrays.asList(new Apple(80,"green"),
            new Apple(155, "green"),
            new Apple(120, "red"));

    public static List<Apple> filterGreenApples(List<Apple> inventory){  //颜色
        List<Apple> res = new ArrayList<>();
        for(Apple apple : inventory){
            if("green".equals(apple.getColor())){
                res.add(apple);
            }
        }
        return res;
    }

    public static List<Apple> filterHeavyApples(List<Apple> inventory){ //重量
        List<Apple> result = new ArrayList<>();
        for (Apple apple: inventory){
            if (apple.getWeight() > 150) {
                result.add(apple);
            }
        }
        return result;
    }

推荐写法

public interface Predicate<T>{
        boolean test(T t);
    }

    public static boolean isGreenApple(Apple apple) {
        return "green".equals(apple.getColor());
    }
    
    public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }


public static void main(String []args){
        List<Apple> inventory = Arrays.asList(new Apple(80,"green"),
                new Apple(155, "green"),
                new Apple(120, "red"));
        List<Apple> greenA1 = filterApples(inventory,FilteringApples::isGreenApple);
        List<Apple> greenA2 = filterApples(inventory,(Apple a) -> "green".equals(a.getColor()));

        System.out.println("------" + greenA1 + "------");
        System.out.println("···" + greenA2 + "···");
    }

  • 运行结果
  • Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型T 对象
  • 并返回一个boolean 。这恰恰和你先前创建的一样,现在就可以直接使用了 ==例如可以这样传参==
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
  • ※泛型Predicate接口里面的方法是boolean类型,所以可以使用行为参数化传递

Stream小例子

  • 从一个列表中筛选金额较高的交易,然后按货币分组
for (Transaction transaction : transactions) {
        if(transaction.getPrice() > 1000){
            Currency currency = transaction.getCurrency();
            List<Transaction> transactionsForCurrency =
                    transactionsByCurrencies.get(currency);
            if (transactionsForCurrency == null) {
                transactionsForCurrency = new ArrayList<>();
                transactionsByCurrencies.put(currency,
                        transactionsForCurrency);
            }
            transactionsForCurrency.add(transaction);
        }
    }

上面的代码转化为

Map<Currency, List<Transaction>> transactionsByCurrencies =
                transactions.stream()
                .filter((Transaction t) -> t.getPrice() > 1000)
                .collect(groupingBy(Transaction::getCurrency));

Stream的强大

  • 有这样一个需求
  • 选出400卡路里以下的菜肴,按照卡路里排序
List<String> lowCaloricDishesName =
            menu.stream()
            .filter(d -> d.getCalories() < 400)    //400以下
            .sorted(comparing(Dish::getCalories))     //排序
            .map(Dish::getName)          // 提取菜肴的名称
            .collect(toList());       //  保存
  • 把几个基础操作链接起来,来表达复杂的数据处理流水线
  • 在 filter 后面接上sorted 、 map 和 collect 操作

  • 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。

  • 源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。

  • 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作。

  • 如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。

  • 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。

  • 内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

  • filter——接受Lambda,从流中排除某些元素。

  • map——接受一个Lambda,将元素转换成其他形式或提取信息。

  • limit ——截断流,使其元素不超过给定数量。

  • collect ——将流转换为其他形式。

lambda小特性

public interface Consumer<T>{
        void accept(T t);
    }

    public static <T> void forEach(List<T> list,Consumer<T> c){
        for (T i : list){
            c.accept(i);
        }
    }

    public static void main(String[] args) {
        forEach(
                Arrays.asList(1,2,3,4,5),
                (Integer i) -> System.out.println(i)
        );
    }
  • Consumer定义了一个名叫accept的抽象方法,它接受泛型T的对象
  • 没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口
  • 创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作

筛选与切片

谓词筛选filter

筛选出所有素菜,创建一张素食菜单

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

筛选各异的元素

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

截短

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

跳过元素

返回一个扔掉了前n个元素的流

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

映射

  • map它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素
List<String> dishNames = menu.stream()
                .map(Dish::getName)
                .collect(toList());

因为getName方法返回一个String,所以map方法输出的流的类型就是Stream。

例2

给定一个单词列表,你想要返回另一个列表,显示每个单词中有几个字母

List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
        List<Integer> wordLen = words.stream()
                .map(String::length)
                .collect(toList());

##查找元素 找到一道素食菜肴

Optional<Dish> dish =
          menu.stream()
             .filter(Dish::isVegetarian)
             .findAny();
  • Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在
  • isPresent() 将在Optional包含值的时候返回true,否则返回false。
  • ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。
  • T get() 会在值存在时返回值,否则抛出一个 NoSuchElement 异常。
  • T orElse(T other) 会在值存在时返回值,否则返回一个默认值。
// 如果找到蔬菜那么就输出它的名字
        menu.stream()
                .filter(Dish::isVegetarian)
                .findAny()
                .ifPresent(d -> System.out.println(d.getName());