专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java修炼终极指南:195 通过 Collector.of() 创建自定义收集器

temp10 2024-12-18 17:21:38 java教程 14 ℃ 0 评论

创建自定义收集器是我们在《Java Coding Problem, First Edition》第 9 章,问题 193 中详细涵盖的主题。更具体地说,在那个问题中,你看到了如何通过实现 java.util.stream.Collector 接口来编写自定义收集器。在这个问题中,我们将继续这个旅程,我们将创建几个自定义收集器。这次,我们将依赖于两个具有以下签名的 Collector#of() 方法:

static <T,R> Collector<T,R,R> of(
  Supplier<R> supplier,
  BiConsumer<R,T> accumulator,
  BinaryOperator<R> combiner,
  Collector.Characteristics... characteristics)
static <T,A,R> Collector<T,A,R> of(
  Supplier<A> supplier,
  BiConsumer<A,T> accumulator,
  BinaryOperator<A> combiner,
  Function<A,R> finisher,
  Collector.Characteristics... characteristics)

在这里,T、A 和 R 代表以下内容(从《Java Coding Problems, First Edition》中挑选):

Java修炼终极指南:195 通过 Collector.of() 创建自定义收集器

T 代表 Stream 中的元素类型(将被收集的元素) A 代表在收集过程中使用的类型的对象,称为累加器,用于在可变的结果容器中累积流元素。 R 代表收集过程后的对象类型(最终结果) 此外,收集器由四个函数和一个枚举定义。再次,这里是从《Java Coding Problems, First Edition》中的简短挑选:这些函数共同工作,将条目累积到一个可变的结果容器中,并可选地对结果执行最终转换。它们如下:

创建一个新的空的可变结果容器(供应商参数) 将一个新的数据元素并入可变结果容器(累加器参数) 将两个可变结果容器合并为一个(组合器参数) 对可变结果容器执行可选的最终转换以获得最终结果(整理器参数) 此外,我们有 Collector.Characteristics... 枚举,它定义了收集器的行为。可能的值是 UNORDERED(无序)、CONCURRENT(更多线程累积元素)和 IDENTITY_FINISH(整理器是恒等函数,因此不会发生进一步的转换)。

在这种情况下,让我们尝试启动一些示例。但是,首先,让我们假设我们有以下模型:

public interface Vehicle {}
public class Car implements Vehicle {
  private final String brand;
  private final String fuel;
  private final int horsepower;
  ...
}
public class Submersible implements Vehicle {
   
  private final String type;
  private final double maxdepth;
  ...
}

以及一些数据:

Map<Integer, Car> cars = Map.of(
  1, new Car("Dacia", "diesel", 100),
  ...
  10, new Car("Lexus", "diesel", 300)
);

接下来,让我们在名为 MyCollectors 的助手类中有一些收集器。

编写一个收集到 TreeSet 的自定义收集器 在收集到 TreeSet 的自定义收集器中,我们有供应商是 TreeSet::new,累加器是 TreeSet#add(),组合器依赖于 TreeSet#addAll(),整理器是恒等函数:

public static <T>
    Collector<T, TreeSet<T>, TreeSet<T>> toTreeSet() {
  return Collector.of(TreeSet::new, TreeSet::add,
    (left, right) -> {
       left.addAll(right);
       return left;
  }, Collector.Characteristics.IDENTITY_FINISH);
}

在以下示例中,我们使用此收集器将所有电动品牌收集到 TreeSet<String> 中:

TreeSet<String> electricBrands = cars.values().stream()
  .filter(c -> "electric".equals(c.getFuel()))
  .map(c -> c.getBrand())
  .collect(MyCollectors.toTreeSet());

那很容易!

编写一个收集到 LinkedHashSet 的自定义收集器 在收集到 LinkedHashSet 的自定义收集器中,我们有供应商是 LinkedHashSet::new,累加器是 HashSet::add,组合器依赖于 HashSet#addAll(),整理器是恒等函数:

public static <T> Collector<T, LinkedHashSet<T>, LinkedHashSet<T>> toLinkedHashSet() {
  return Collector.of(LinkedHashSet::new, HashSet::add,
    (left, right) -> {
       left.addAll(right);
       return left;
  }, Collector.Characteristics.IDENTITY_FINISH);
}

在以下示例中,我们使用此收集器收集排序后的汽车马力:

LinkedHashSet<Integer> hpSorted = cars.values().stream()
  .map(c -> c.getHorsepower())
  .sorted()
  .collect(MyCollectors.toLinkedHashSet());

完成!LinkedHashSet<Integer> 包含按升序排列的马力值。

编写一个排除另一个收集器中元素的自定义收集器 本节的目标是提供一个自定义收集器,它接受一个 Predicate 和一个 Collector 作为参数。它将给定的谓词应用于要收集的元素,以便从给定的收集器中排除失败项。

public static <T, A, R> Collector<T, A, R> exclude(
    Predicate<T> predicate, Collector<T, A, R> collector) {
  return Collector.of(
    collector.supplier(),
    (l, r) -> {
       if (predicate.negate().test(r)) {
         collector.accumulator().accept(l, r);
       }
    },
    collector.combiner(),
    collector.finisher(),
    collector.characteristics()
     .toArray(Collector.Characteristics[]::new)
  );
}

自定义收集器使用给定收集器的供应商、组合器、整理器和特性。它只影响给定收集器的累加器。基本上,它只对通过给定谓词的元素显式调用给定收集器的累加器。例如,如果我们想通过这个自定义收集器获得小于 200 的排序马力,那么我们可以这样调用它(谓词告诉我们要排除什么):

LinkedHashSet<Integer> excludeHp200 = cars.values().stream()
  .map(c -> c.getHorsepower())
  .sorted()
  .collect(MyCollectors.exclude(c -> c > 200,
           MyCollectors.toLinkedHashSet()));

在这里,我们使用了两个自定义收集器,但我们也可以很容易地将 toLinkedHashSet() 替换为内置收集器。挑战自己编写这个自定义收集器的对应部分。编写一个收集通过给定谓词的元素的收集器。

编写一个按类型收集元素的自定义收集器 假设我们有以下 List<Vechicle>:

Vehicle mazda = new Car("Mazda", "diesel", 155);
Vehicle ferrari = new Car("Ferrari", "gasoline", 500);
       
Vehicle hov = new Submersible("HOV", 3000);
Vehicle rov = new Submersible("ROV", 7000);
       
List<Vehicle> vehicles = List.of(mazda, hov, ferrari, rov);

我们的目标是只收集汽车或只收集潜水器,而不是两者都收集。为此,我们可以编写一个按类型收集到给定供应商的自定义收集器,如下所示:

public static
  <T, A extends T, R extends Collection<A>> Collector<T, ?, R>
    toType(Class<A> type, Supplier<R> supplier) {
  return Collector.of(supplier,
      (R r, T t) -> {
         if (type.isInstance(t)) {
           r.add(type.cast(t));
         }
      },
    (R left, R right) -> {
       left.addAll(right);
       return left;
    },
    Collector.Characteristics.IDENTITY_FINISH
  );
}

现在,我们可以将 List<Vechicle> 中的汽车收集到 ArrayList 中,如下所示:

List<Car> onlyCars = vehicles.stream()
  .collect(MyCollectors.toType(
    Car.class, ArrayList::new));

并且,我们可以将潜水器收集到 HashSet 中,如下所示:

Set<Submersible> onlySubmersible = vehicles.stream()
  .collect(MyCollectors.toType(
    Submersible.class, HashSet::new));

最后,让我们为自定义数据结构编写一个自定义收集器。

为 Splay Tree 编写自定义收集器 在第 5 章,问题 120 中,我们实现了一个 Splay Tree 数据结构。现在,让我们编写一个能够将元素收集到 Splay Tree 中的自定义收集器。显然,供应商是 SplayTree::new。此外,累加器是 SplayTree#insert(),而组合器是 SplayTree#insertAll():

public static
    Collector<Integer, SplayTree, SplayTree> toSplayTree() {
  return Collector.of(SplayTree::new, SplayTree::insert,
    (left, right) -> {
       left.insertAll(right);
       return left;
  },
  Collector.Characteristics.IDENTITY_FINISH);
}

以下是将汽车马力收集到 SplayTree 的示例:

SplayTree st = cars.values().stream()
  .map(c -> c.getHorsepower())
  .collect(MyCollectors.toSplayTree());

完成!挑战自己实现一个自定义收集器。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表