问题描述
假设我有以下课程:
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()
它只是短了一点。
1楼
流#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())
保护访问。
2楼
标准库
仅使用标准库,使用其他答案提供的内容。
它比较简短,清晰,标准,每个人都会理解。
它迫使您明确处理元素不存在的情况,这是一件好事。
唯一“不必要”的样板文件是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");
(请注意,这只会为每个名字保留一个人。可能不是您想要的,是吗?)
3楼
更短不一定更好。 使用更高效或更易读/可维护的代码。
这是更短的,但在很多方面更糟:
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 个示例,表明您可以通过不惜一切代价缩短代码质量来降低代码质量。
这是一种权衡,取决于开发人员(就像一切一样)。 在我看来,在效率/可读性和代码简洁性之间做出选择很容易。
4楼
只需使用Stream.findFirst(),但不要以最短的方式思考。 而是始终创建最容易理解的代码(在本例中为 findFirst)。 而且我不认为代码中的 2 或 3 个字符很重要。
List<Person> test = new ArrayList<>();
test.stream().filter(s -> s.getFirstName().equals("John")).findFirst();
5楼
我个人建议查看 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);