优化从列表中获得一场比赛

时间:2019-02-19 09:46:07

标签: java

假设我有以下课程:

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()

只短一点。

5 个答案:

答案 0 :(得分:2)

Stream#findAny

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

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

从其documentation

  

返回描述流中某些元素的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的安全性。即

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

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

在未证明通话不会崩溃的情况下,请勿在{{1​​}}上致电get()。不遵循这一点会破坏Optional的目的。相反,最好使用Optional方法。或者至少使用orElseXXX保护访问。

答案 1 :(得分:2)

标准库

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

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

第三方库

如果可以使用库,则可以使用更短(虽然不一定更好)的解决方案。使用Google Guava's Iterables.find()

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

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

许多其他库(例如Apache Commons CollectionsIterableUtils.find()

一种不同的方法

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

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

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

答案 2 :(得分:1)

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

这是更短的代码,但在许多方面却更糟:

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

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

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

但是,当您需要避免在空的get上调用Optional时,您必须使其变长。

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

这是一个权衡,取决于开发人员(一切都是如此)。我认为,在效率/可读性和代码简洁性之间进行选择很容易。

答案 3 :(得分:0)

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

readFile1, readFile2, readFile3, ......., readFile50

答案 4 :(得分:0)

我个人建议查看apache commons库,该库提供了许多有用的实用程序。 http://commons.apache.org/ Collections-package(http://commons.apache.org/proper/commons-collections/)具有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);