方法和 Lambda作为一等公民
方法引用
在Java8以前,如果我们要筛选一个目录中的隐藏文件,我们需要这样做:
File[] hiddenFiles = new File("").listFiles(new FileFilter() { @Override public boolean accept(File file) { return file.isHidden(); } });
Java8里,我们可以重写成:
File[] hiddenFiles2 = new File("").listFiles(File::isHidden);
:: 这个语法的意思是把这个方法作为值传递给listFiles方法。
传递方法
public class Apple { String color; int weight; public Apple(String color, int weight) { this.color = color; this.weight = weight; } public static boolean isGreen(Apple apple) { return "green".equals(apple.getColor()); } public static boolean isHeavy(Apple apple) { return apple.getWeight() > 20; } public static List<Apple> filterApples(List<Apple> apples, Predicate<Apple> p) { List<Apple> result = new ArrayList<>(); for (Apple apple : apples) { if (p.test(apple)) { result.add(apple); } } return result; } public static void main(String[] args) { List<Apple> apples = List.of(new Apple("green", 21), new Apple("black", 19)); List<Apple> apples1 = filterApples(apples, Apple::isGreen); List<Apple> apples2 = filterApples(apples, Apple::isHeavy); apples1.stream().forEach(e -> System.out.println(e)); } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } }
前面的代码传递了方法Apple::isGreenApple(它接受参数Apple并返回一个 boolean)给 filterApples,后者则希望接受一个Predicate<Apple>参数。谓词(predicate) 在数学上常常用来代表一个类似函数的东西,它接受一个参数值,并返回true或false。你 在后面会看到,Java 8也会允许你写Function<Apple,Boolean>——在学校学过函数却没学 过谓词的读者对此可能更熟悉,但用Predicate<Apple>是更标准的方式,效率也会更高一 点儿,这避免了把boolean封装在Boolean里面。
实际上并不需要单独写比较过滤方法,可以用这种匿名Lambda形式替代:
List<Apple> apples2 = filterApples(apples, (Apple a) -> a.getWeight() > 20);
但是对于实际开发来讲,这种并非是一种很好的方式,毕竟维护稍微困难了些,如果你写了很长的lambda,还不如老老实实定义方法然后call.
甚至还有一种更简便的方式,直接不需要filterApples()方法:
static <T> Collection<T> filter(Collection<T> c, Predicate<T> p);
这就是基于流化实现
apples.stream().filter(a -> a.getWeight() > 20).forEach(System.out::println);
流Strem
List<Apple> apples = List.of(new Apple("green", 21), new Apple("black", 19)); Map<String, List<Apple>> groupForApples = new HashMap<>(); for (Apple apple : apples) { if (apple.getWeight() > 20) { String color = apple.getColor(); List<Apple> appleList = groupForApples.get(color); if (appleList == null) { groupForApples.put(color, appleList); } appleList.add(apple); } }
这段复杂的代码描述的意思很简单,就是将重量大于20的apple按照颜色分组。但描述还是不够清晰的。现在用流转换一下。
Map<String, List<Apple>> groupForApples2 = apples.stream(). filter((Apple apple) -> apple.getWeight() > 20). collect(Collectors.groupingBy(Apple::getColor));
emm,很清晰,首先流转化,然后过滤,然后groupBy颜色分组。
总之简单来讲,利用Collection API就是外部迭代,用Stream API就是内部迭代。
并行处理
Map<String, List<Apple>> groupForApples2 = apples.parallelStream(). filter((Apple apple) -> apple.getWeight() > 20). collect(Collectors.groupingBy(Apple::getColor));
看起来是不是有点像分治,归并的思路。
语言特性的添加要考虑的一个很重要的问题是和老版本的兼容。比如我们现在可以对list.sort()这种操作,但是原先是不可以的,现在的处理方法是,我们可以对接口中加入default方法。
这就是我们在List接口中添加的。
default void sort(Comparator<? super E> c) { Collections.sort(this, c); }
这种default方法,在任何实现类都不需要显示的实现sort。
著名的NULL,万能的NULL也是万恶的NULL。如何避免出现NullPointer异常--->Optional<T>