当前位置: 代码迷 >> java >> 优化从列表中获取匹配项
  详细解决方案

优化从列表中获取匹配项

热度:45   发布时间:2023-08-02 11:02:27.0

假设我有以下课程:

class Person { 
   private String firstName;
   private String lastName;
   public Person(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
   }
   // getters and setters
}

假设我有一个Person对象列表,我想在列表中找到一个名字为'John' 的Person 对象。

我能想到的最短代码是:

personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .collect(Collectors.toList())
    .get(0);

如您所见,它并没有那么短。 你能想到更短的方法吗?


编辑:有些人建议使用findFirst() 在这种情况下,代码将是:

personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .findFirst()
    .get()

它只是短了一点。

流#findAny

您可以为此使用Stream#findAny 它返回一个Optional<Person> ,如果没有命中则为空。

Optional<Person> person = personStream.filter(p -> p.getFirstName().equals("John")).findAny();

从它的:

返回一个Optional描述流的一些元件,或一个Optional如果流是空的。

还有findFirst ,但findAny少两个字符,并且可能有更快的实现。


方法参考

如果将过滤器移动到专用方法中,代码会变短:

public static boolean isJohn(Person p) {
    return "John".equals(p.getFirstName());
}

然后代码得到

Optional<Person> person = personStream.filter(MyClass::isJohn).findAny();

谓词

如果将过滤器移动到专用谓词中,它甚至会变得更短:

Predicate<Person> isJohn = p -> "John".equals(p.getFirstName());

然后我们得到

Optional<Person> person = personStream.filter(isJohn).findAny();

尽可能短

为了短代码而牺牲可读性(永远不要这样做),我们可以替换变量名并得到:

Optional<Person> p = s.filter(j).findAny();

笔记

更喜欢将"John"与人名进行比较,而不是相反。 这样,它是null 您还可以使用Objects#equals来获得null安全性。 IE

// Not null-safe
p.getFirstName().equals("John");

// Null safe
"John".equals(p.getFirstName());
Objects.equals("John", p.getFirstName());

不要在Optional上调用get()除非证明调用不会崩溃。 不遵循这一点就违背了Optional的目的。 相反,更喜欢orElseXXX方法。 或者至少用if (result.isEmpty())保护访问。

标准库

仅使用标准库,使用其他答案提供的内容。 它比较简短,清晰,标准,每个人都会理解。 它迫使您明确处理元素不存在的情况,这是一件好事。 唯一“不必要”的样板文件是stream()调用,这也有其微妙的语义原理。

Person john = personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .findFirst()
    .get();

第三方库

如果您可以使用库,则有更短(虽然不一定更好)的解决方案。 使用 :

Person john = Iterables.find(personList, person -> person.getFirstName().equals("John"));

(请注意,如果没有找到该元素,这将引发异常。考虑使用其他的find()方法用默认值,或tryFind()方法。)

许多其他库中也有同样的内容,例如 : 。

上面提到的 Guava 也有收集器:

Person john = personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .collect(MoreCollectors.onlyElement());

不同的方法

在列表中搜索特定元素是一个线性或 O(n) 操作。 如果您将重复执行此操作,很明显您应该使用不同的数据结构。 也许从人名到人的地图会更有用?

Map<String, Person> personsByFirstName = personList.stream()
    .collect(toMap(Person::getFirstName, Function.identity()));
Person john = personsByFirstName.get("John");

(请注意,这只会为每个名字保留一个人。可能不是您想要的,是吗?)

更短不一定更好。 使用更高效或更易读/可维护的代码。

这是更短的,但在很多方面更糟:

personList.removeIf(p -> !"John".equals(p.getFirstName()));

Stream.findFirst通过在找到第一个元素后立即停止流Stream.findFirst提高效率:

personList.stream()
          .filter(person -> person.getFirstName.equals("John"))
          .findFirst() //<-- This will stop your stream when the first match is found
          .get();

但是当您需要避免在空的Optional上调用get时,您将不得不延长它。

这只是 2 个示例,表明您可以通过不惜一切代价缩短代码质量来降低代码质量。

这是一种权衡,取决于开发人员(就像一切一样)。 在我看来,在效率/可读性和代码简洁性之间做出选择很容易。

只需使用Stream.findFirst(),但不要以最短的方式思考。 而是始终创建最容易理解的代码(在本例中为 findFirst)。 而且我不认为代码中的 2 或 3 个字符很重要。

    List<Person> test = new ArrayList<>();

    test.stream().filter(s -> s.getFirstName().equals("John")).findFirst();

我个人建议查看 apache commons 库,它提供了许多有用的实用程序。 Collections-package ( ) 有一个IterableUtils -utility 类,它允许您执行以下操作:

Person john = IterableUtils.find(personList, person -> person.getFirstName().equals("John"));

除此之外,它还提供了许多其他有用的东西。 或者更多到你的例子:

Predicate<Person> isJohn = p -> "John".equals(p.getFirstName());
Person john = IterableUtils.find(personList, isJohn);
  相关解决方案