Guavaを使用してコレクションを変換するときにnullを削除するエレガントな方法はありますか?
-
05-07-2019 - |
質問
Googleコレクションを使用する際に、コレクション処理コードの簡素化について質問があります(更新:グアバ)。
「コンピュータ」がたくさんあります。オブジェクト、および「リソースID」のコレクションになります。これは次のように行われます。
Collection<Computer> matchingComputers = findComputers();
Collection<String> resourceIds =
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
}));
今、 getResourceId()
はnullを返す可能性があります(そして、それは現在オプションではありません)が、この場合、結果のStringコレクションからnullを省略したいと思います。
nullを除外する1つの方法:
Collections2.filter(resourceIds, new Predicate<String>() {
@Override
public boolean apply(String input) {
return input != null;
}
});
このようにすべてをまとめることができます:
Collection<String> resourceIds = Collections2.filter(
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
})), new Predicate<String>() {
@Override
public boolean apply(String input) {
return input != null;
}
});
しかし、これはエレガントなものではなく、読みやすいのは言うまでもありません。事実、単純な古いJavaコード(派手なPredicateやFunctionを一切使用しない)は、間違いなくはるかにクリーンです:
Collection<String> resourceIds = Lists.newArrayList();
for (Computer computer : matchingComputers) {
String resourceId = computer.getResourceId();
if (resourceId != null) {
resourceIds.add(resourceId);
}
}
上記を使用することも確かにオプションですが、好奇心から(およびGoogleコレクションの詳細を学びたいという要望から)、 Googleコレクションを使用して、より短いまたはよりエレガントな方法でまったく同じことを行うことができます?
解決
Predicates
はここで役立ちます- Predicates.notNull()
-そして、 Iterables.filter()
を使用できますそして、 Lists.newArrayList()
が Iterable
を使用してこれをもう少しクリーンアップできるという事実。
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, yourFunction),
Predicates.notNull()
)
);
実際には Collection
を必要とせず、単に Iterable
が必要な場合は、 Lists.newArrayList()
呼び出しも廃止できますそして、あなたは再びワンステップクリーナーです!
Function
が再び便利になり、次のように宣言されると最も便利になると思うかもしれません
public class Computer {
// ...
public static Function<Computer, String> TO_ID = ...;
}
これをさらにクリーンアップします(再利用を促進します)。
他のヒント
少し「きれい」な FluentIterable
(グアバ12以降):
ImmutableList<String> resourceIds = FluentIterable.from(matchingComputers)
.transform(getResourceId)
.filter(Predicates.notNull())
.toList();
static final Function<Computer, String> getResourceId =
new Function<Computer, String>() {
@Override
public String apply(Computer computer) {
return computer.getResourceId();
}
};
返されるリストは ImmutableList
であることに注意してください。ただし、 copyInto()
メソッドを使用して、要素を任意のコレクションに注ぐことができます。
@Jon Skeetの予想よりも時間がかかりましたが、Java 8ストリームによりこれが簡単になります。
List<String> resourceIds = computers.stream()
.map(Computer::getResourceId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
必要に応じて .filter(x-&gt; x!= null)
も使用できます。 違いはごくわずかです。
まず、どこかに定数フィルターを作成します:
public static final Predicate<Object> NULL_FILTER = new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return input != null;
}
}
その後、使用できます:
Iterable<String> ids = Iterables.transform(matchingComputers,
new Function<Computer, String>() {
public String apply(Computer from) {
return from.getResourceId();
}
}));
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(ids, NULL_FILTER));
コード内のどこでも同じnullフィルターを使用できます。
同じコンピューティング関数を他の場所で使用する場合、それも定数にして、次のように残すことができます。
Collection<String> resourceIds = Lists.newArrayList(
Iterables.filter(
Iterables.transform(matchingComputers, RESOURCE_ID_PROJECTION),
NULL_FILTER));
確かにC#の同等のものほど良いものではありませんが、これはすべて、クロージャーと拡張メソッドを備えたJava 7でより良い多くを得るでしょう:)
次のように独自のメソッドを作成できます。これにより、applyメソッドからnullを返す関数のnullが除外されます。
public static <F, T> Collection<T> transformAndFilterNulls(List<F> fromList, Function<? super F, ? extends T> function) {
return Collections2.filter(Lists.transform(fromList, function), Predicates.<T>notNull());
}
このメソッドは、次のコードで呼び出すことができます。
Collection c = transformAndFilterNulls(Lists.newArrayList("", "SD", "DDF"), new Function<String, Long>() {
@Override
public Long apply(String s) {
return s.isEmpty() ? 20L : null;
}
});
System.err.println(c);