【问题标题】:Guava Collection Utils vs. plain Java 7Guava Collection Utils 与普通 Java 7
【发布时间】:2016-05-19 09:51:20
【问题描述】:

我经常想使用 Guava 的集合实用程序编写代码,并在查看代码片段后发现自己用纯 Java 7 重写了代码,因为恕我直言,使用纯旧 Java 更简洁且更易于阅读。

例如,使用 Guava 将对象列表放入具有唯一索引的地图中,看起来像这样:

 List<A> myList = ...
 Map<A, B> mappings = Maps.uniqueIndex(myList, new Function<A, B>() {
     @Override
     public CustomerFinance apply(final A input) {
         return input.getB();
     }
 });

而在原版 Java 7 中则是:

List<A> myList = ...
Map<A, B> mappings = new HashMap<>(myList.size());
for (A a : myList) {
    mappings.put(a.getB(), a);
}

Guava 中的映射有 5 个 LOC(甚至不包括 @Override 行),而普通 Java 中有 4 个 LOC。

我为什么要在这里使用 Guava 工具?最初创建实用程序的动机是什么?我是否错过了番石榴为我提供的一些额外好处?

【问题讨论】:

  • Java 8 已经推出 2 年多了,它可以在 1 行中完成任务。你为什么关心 Java 7?
  • 我真的没有,我想尽快升级。不幸的是,参与我正在从事的当前项目的重要利益相关者不同意。
  • 您的 Java 7 只是更短,因为它是 Guava 代码的不完整“翻译”。 Guavas 生成的 Map 是不可变的,并且不允许 null 作为值之一。您的代码完全忽略了这一点。
  • @Tom 不变性是一个问题,是的。但在我的实际用例中,我什至需要一个 mutable 映射。所以 Guava 版本更长,因为我必须将生成的地图输入 HashMap 以获得可变版本。
  • FWIW,Guava doesn't recommend 如果替代方案更简洁,那么您将竭尽全力在 Java 7 中使用其功能性惯用语。当函数是您可能想要重用的东西(或者您可以使用 Guava 提供的函数之一)或者当操作本身足够复杂以至于自己编写容易出错或使您的代码更复杂时,它们会更有用.

标签: java collections guava


【解决方案1】:

我是否错过了番石榴为我提供的一些额外好处?

是的。

Guava 中的一些功能在您的翻译代码中遗漏了 (JavaDoc of Maps#uniqueIndex):

  • null 禁止取值 (#)
  • 禁止使用null 键(键函数不应为给定值返回null
  • 禁止重复键(不能意外覆盖现有键)
  • 生成的地图是不可变的

如果您也要进行所有这些检查,那么您的代码应该比 Guavas 版本长。

(#) 请注意,您的翻译版本目前也禁止null,因为您在值对象上调用#getB()。这将导致 NullPointerException 像番石榴一样。如果您要从其他地方(例如mappings.put(generateArtificalKey(), a);)获取密钥,那么null 对于 Java 7 版本就可以了。


另一个优点是 key 函数的代码更简洁。它可以很容易地移动到自己的类中并被重用。一个例子可能是一个函数,它提取数据库实体的 id 来创建一个映射。但这更多是一种观点,而不是事实,因为您的翻译代码(for 循环)也可以被提取并用于其他情况。

【讨论】:

  • 其实你关于null的说法是不正确的:如果a.getB()返回null,香草版本不会抛出NPE,而Guava版本会。
  • @StefanHaberl 你的版本会抛出 NPE,因为如果 anull,那么 a.getB() 将不起作用。如果您不会在 a 上调用某些东西并使用其他东西作为键,那么 yes 香草版本不会抛出 NPE。但我用你的版本作为参考。这就是为什么我将该点移到底部(我首先将它放在列表中)。我希望我的答案现在更清楚一点。
  • 当然,但是如果a != nulla.getB() == null,香草版本将工作,而番石榴版本将抛出一个 NPE :D
  • @StefanHaberl 但是您的代码不会像那样检查null ;P.
  • 完全同意并明确声明:原版版本禁止 null 作为结果值,但允许 null 作为结果键,而 Guava 将阻止 null 值和 nullkeys。
【解决方案2】:

您可能会争辩说,是的,就 LOC 而言并没有太大的收获。不过番石榴风格更多的是functional

transform 函数可以传递和使用/重用。

 Function<A,B> map = new Function<A, B>() {
 @Override
 public CustomerFinance apply(final A input) {
     return input.getB();
 }

现在您可以传递地图并在不同的地方重复使用它。

Map<A, B> mappings = Maps.uniqueIndex(myList,map);
Map<C, D> mappings2 = Maps.uniqueIndex(myList,map);

但话又说回来,Guava 仅限于 Java 1.6 构造,它不能为声明式风格提供最佳体验。

好消息是 Guava 中的 Functionfunctional interface,因此如果您迁移到 Java 1.8,您可以从第一天开始使用 lambda,您的代码将如下所示,

Map<A, B> mappings = Maps.uniqueIndex(myList, input-> input.getB());

或者

Map<A, B> mappings = Maps.uniqueIndex(myList, A::getB);

【讨论】:

  • 我知道我可以在 Java 8 中使用 lambda。但是,对于当前项目,我坚持使用 Java 7。我最初关心的是我应该在“功能”方面走多远Java 7 的道路。如果我不共享或重用该功能,您基本上是在说这是一个品味问题(或者只是一种更“时尚”的方法)?
  • @StefanHaberl 我认为我的回答中没有可以被解读为“时尚”的东西。我向你展示了一些例子来展示它的好处和简洁性。
  • 同意。所以简洁的好处是当我开始分享我的功能时。我没有。 (不幸的是)升级到 Java 8 不是一种选择。所以没有任何好处。
【解决方案3】:

当我在 Java 8 之前的版本中编程时,我通常会创建一个关联的实用程序类,以实体命名但以复数形式命名,并将所有实用程序方法添加到它。通过这种方式,我可以保持实体清洁,并且可以访问大量实用方法。我没有系统地创建实用程序类,但仅在需要减少噪音时创建。

public class MyEntity {
  private String text;
  public String getText() { return text; }
  public void setText (int text) { this.text = text; }
}

public final class MyEntities {
  private MyEntities() {}

  private enum MyEntityToString implements Function<MyEntity,String> {
    TEXT_GETTER {
      @Override public String apply(MyEntity input) { return input.getText(); }
    };
  }
  public static Function<MyEntity,String> textGetter() { return MyEntityToString.TEXT_GETTER; }
}

然后,使用变得像在需要时使用方法MyEntities.textGetter()一样简单。如果你只使用一次,是的,你会失去 LOC,但如果你多次使用它,你就赢了。此外,如果您遇到错误,您只需修复一次,而不是找到所有用法来修复它。

【讨论】:

  • 我完全同意。干燥。当我需要两次确切的代码片段时(不过,主要是使用谓词),我正在做完全相同的事情。但通常情况下,我的代码只针对一个用例。然后我不想将逻辑外部化为一个单独的函数,恕我直言,它更难阅读(再次,当我必须坚持使用 Java 7 时)
  • 如果它特定于一个用例,您可以在使用它的类的不同位置编写函数。它仍然更容易阅读。如果出现更多用例,则将其移至实用程序类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-04
  • 2020-01-28
  • 2014-05-08
  • 2011-01-20
相关资源
最近更新 更多