关于java:effective-java-第七章-Lambda和Stream

2次阅读

共计 6449 个字符,预计需要花费 17 分钟才能阅读完成。

第 42 条 Lambda 优先于匿名类

函数对象:只带有单个形象办法的接口或者抽象类,其实例称之为函数对象。

  1. 删除所有 lambda 参数的类型,除非其存在可能使程序更加清晰。
  2. 与办法和类不同,Lambda 没有名称和文档,如果一个计算自身不是不言自明的,或者该计算超出了三行的限度,那么就不要把它放入一个 Lambda 中。
  3. 尽可能不要序列化一个 Lambda 或者匿名外部类实例。
  4. 不要为函数式接口或者函数式抽象类去创立匿名外部类对象。

Java 中减少了 Lambda 后,许多中央能够应用函数对象了,如下第 34 条的 Operation 枚举类型,能够从旧版本优化为新版本:

// Enum type with constant-specific class bodies & data (Item 34)
public enum Operation {PLUS("+") {public double apply(double x, double y) {return x + y;}
    },
    MINUS("-") {public double apply(double x, double y) {return x - y;}
    },
    TIMES("*") {public double apply(double x, double y) {return x * y;}
    },
    DIVIDE("/") {public double apply(double x, double y) {return x / y;}
    };
    private final String symbol;

    Operation(String symbol) {this.symbol = symbol;}

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

    public abstract double apply(double x, double y);
}

应用 Lambda 后的版本

// Enum with function object fields & constant-specific behavior
public enum Operation {PLUS("+", (x, y) -> x + y),
    MINUS("-", (x, y) -> x - y),
    TIMES("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);
    private final String symbol;
    private final DoubleBinaryOperator op;

    Operation(String symbol, DoubleBinaryOperator op) {
        this.symbol = symbol;
        this.op = op;
    }

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

    public double apply(double x, double y) {return op.applyAsDouble(x, y);
    }
}

第 43 条 办法援用优先于 Lambda

  1. 当办法援用更简洁时,应用办法援用;反之,还是应用 Lambda。
办法援用类型 范例 Lambda 等式
动态援用 Integer::parseInt str -> Integer.parseInt(str)
无限实例援用 Instant.now()::isAfter Instant then = Instant.now();t -> then.isAfter(t)
有限实例援用 String::toLowerCase str -> str.toLowerCase()
类结构器 TreeMap<K,V>::new () -> new TreeMap<K,V>
数组结构器 int[]::new len -> new int[len]
  • 无限办法援用和动态援用类似,都是所提供的办法援用与函数式接口的参数列表以及返回值类型统一,无限实例办法援用其实不须要实例对象。
  • 有限办法援用,则须要一个特定的实例对象,该实例对象加上援用办法中的参数列表,才与函数式接口的参数列表统一。这个实例对象也就是原书上所说的an additional parameter

注:书中的 receiving object 其实指的是依据办法援用所创立的函数对象。

附上 Java 官网中的分类及其示例:

Kind Example
Reference to a static method ContainingClass::staticMethodName
Reference to an instance method of a particular object containingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
Reference to a constructor ClassName::new

静态方法援用

public class Person {

    public enum Sex {MALE, FEMALE}

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {// ...}

    public LocalDate getBirthday() {return birthday;}

    public static int compareByAge(Person a, Person b) {return a.birthday.compareTo(b.birthday);
    }
}
//Person::compareByAge 就是静态方法援用

无限实例办法援用

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

有限实例办法援用

class ComparisonProvider {public int compareByName(Person a, Person b) {return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

结构器办法援用

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {DEST result = collectionFactory.get();
        for (T t : sourceCollection) {result.add(t);
        }
        return result;
}
// 类结构器办法援用
Set<Person> rosterSet = transferElements(roster, HashSet::new);
// 等价于
Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

第 44 条 保持应用规范的函数接口

  • 只有规范的函数接口可能满足需要,通常应优先思考吗,而不是再专门构建一个新的函数接口。
  • 千万不要用带包装类型的根底函数接口来代替根底类型的函数接口
  • 必须始终用 @FunctionalInterface 注解对本人编写的函数接口进行规范
  1. 在 Java9 中,java.util.Function 一共有 43 个接口,其中有 6 个根底接口:
  • 参数类型和返回类型统一的有 2 个,别离是 1 个参数和两个参数的;
  • 返回类型为 boolean 的有 1 个;
  • 参数类型和返回类型不统一的有 1 个;
  • 无返回值和有形参的各一个,共 2 个;
接口 函数签名 范例
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T,R> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println
  1. 其中每个根底接口各自有 3 种变体,别离作用于将 T 的类型替换为根本类型 intlongdouble,共 6*3 种。
接口 Int 变种 Long 变种 Double 变种
UnaryOperator<T> IntUnaryOperator LongUnaryOperator DoubleUnaryOperator
BinaryOperator<T> IntBinaryOperator LongBinaryOperator DoubleBinaryOperator
Predicate<T> IntPredicate LongPredicate DoublePredicate
Function<T,R> IntFunction<R> LongFunction<R> DoubleFunction<R>
Supplier<T> IntSupplier LongSupplier DoubleSupplier
Consumer<T> IntConsumer LongConsumer DoubleConsumer
  1. 针对于 Function<T,R> 还有9 种变体,其中 6 种用于源类型和后果类型都是根底类型,但又互不雷同的状况,还有三种是将源类型为根底类型,后果类型是援用类型的(即返回了个对象):
Int Long Double Obj
Int IntToLongFunction IntToDoubleFunction IntFunction<R>
Long LongToIntFunction LongToDoubleFunction LongFunction<R>
Double DoubleToIntFunction DoubleToLongFunction DoubleFunction<R>
  1. Predicate<T> Function<T,R> Consumer<T> 还有各自有一个带有两个参数的版本,共 3 个;

    BiFunction<T,U,R> 还有返回相干的三个根底类型的变种共 3 个;

    Consumer<T>接口还有带有 1 个援用类型参数和 1 个根底类型参数的变种,针对三种根底类型,共 3 个。

源类型 变种 1 变种 2 变种 3
Bi BiPredicate<T,U> BiFunction<T,U,R> BiConsumer<T,U>
BiFunction<T,U,R> ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction<T,U>
Consumer<T> ObjDoubleConsumer<T> ObjIntConsumer<T> ObjLongConsumer<T>
  1. 最初有 Supplier<T> 的一个变种BooleanSupplier,如下:
@FunctionalInterface
public interface BooleanSupplier {
    /**
     * Gets a result.
     *
     * @return a result
     */
    boolean getAsBoolean();}

第 45 条 审慎应用 Stream

  1. 滥用 Stream 会使程序代码难以读懂和保护
  2. 在没有显式类型的状况下,认真命名 Lambda 参数,这对 Stream pipeline 的可读性至关重要
  3. 为了可读性,须要时在 Stream Pipline 中应用 helper 办法
  4. 防止利用 Stream 解决 char 值
  5. 重构现有代码来应用 Stream,且只在必要的时候才在新代码中应用
  6. 不确定迭代和 Stream 哪种比拟好的状况下,两种都试下比拟下

stream 的定义及形象原理

在 Java8 中新增了 Stream API,用于简化串行或者并行的大批量操作。这个 API 提供了两个要害的形象:

  • stream(流),表白了一个无限或者有限的数据元素序列。
  • stream pipeline(流管道),表白了这些数据元素的多级计算。

stream 的起源包含汇合、数组、文件、正则表达式模式匹配器、伪随机数生成器以及其余 stream。其中的数据元素类型能够是援用类型和根底类型(int、long、double)。一个 stream pipeline 中蕴含了一个源 stream,接着是零至多个两头操作和一个终止操作。每个两头操作都会以某种形式对接管到的 stream 转换为另一个 stream,终止操作会在最初一个两头操作产生的 stream 上执行一个最终的计算。

stream pipeline 通常是 lazy 的,直到调用终止操作时才会开始计算,对于实现终止操作不须要的数据元素,将永远不会计算。这种计算使得有限 stream 变为可能。而没有终止操作的 stream pipeline 将是一个静默的无操作指令,千万不能遗记终止操作。

在默认状况下,stream pipeline 是依照程序执行的,要使其并发执行,只需在该 pipeline 的任何 stream 上调用 parallel 办法即可。但通常不倡议这么做。

补充阐明

helper 办法指的是对 pipeline 中的简单计算流程,能够独自封装进去作为一个函数。

public class Anagrams {public static void main(String[] args) throws IOException {Path dictionary = Paths.get(args[0]);
        int minGroupSize = Integer.parseInt(args[1]);
        try (Stream<String> words = Files.lines(dictionary)) {words.collect(groupingBy(word -> alphabetize(word)))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupSize)
                    .forEach(g -> System.out.println(g.size() + ":" + g));
        }
    }

    //helper 办法
    private static String alphabetize(String word) {String s = word.chars().sorted()
                .collect(StringBuilder::new,
                        (sb, c) -> sb.append((char) c),
                        StringBuilder::append).toString();
        return s;
    }
}

两头操作 flatMap 能够将 Stream 中的每个元素都映射产生一个 Stream,而后将这些新的 Stream 全副合并到一个 Stream 中。

// Iterative Cartesian product computation
private static List<Card> newDeck() {List<Card> result = new ArrayList<>();
    for (Suit suit : Suit.values())
        for (Rank rank : Rank.values())
            result.add(new Card(suit, rank));
    return result;
}

第 46 条 优先选择 Stream 中无副作用的函数

正文完
 0