Stream APIをポンプするか、さらに砂糖が必要です

少し前まで、私が取り組んでいるプロジェクトの1つをJava 8に移行することができました。 もちろん、最初は、Stream APIを使用するときの構造のコンパクトさと表現力に陶酔感がありましたが、時間が経つにつれて、より短く、より柔軟で表現力豊かに書きたいと思いました。 最初は、ユーティリティクラスに静的メソッドを追加しましたが、これによりコードが悪化しました。 最後に、ストリームインターフェイス自体を拡張する必要があるという結論に達しました。その結果、小さなStreamExライブラリが生まれました

Java 8には、オブジェクトStreamと、3つのプリミティブなIntStreamLongStream、およびDoubleStreamの 4つのストリームインターフェイスがあります。 完全に交換するには、標準スレッドですべてをラップする必要があります。 したがって、 StreamExIntStreamExLongStreamExおよびDoubleStreamExDoubleStreamEx 。 元のインターフェイスを保存するには、次のような退屈なメソッドを大量に作成する必要がありました。

 public class IntStreamEx implements IntStream { private final IntStream stream; @Override public <U> StreamEx<U> mapToObj(IntFunction<? extends U> mapper) { return new StreamEx<>(stream.mapToObj(mapper)); } ... } 

また、静的コンストラクターを作成する必要がありました。これは、元のクラスに既にあるものだけでなく、他のものもあります( random.ints()を置き換えるために、 IntStreamEx.of(random)メソッドがあります)。 しかし、その後、自分の裁量で拡大できるストリームがありました。 以下は、追加機能の簡単な概要です。

人気のコレクターの減少


標準のStream APIでは、多くの場合、 .collect(Collectors.toSet())または.collect(Collectors.toList())を記述する必要があります。 Collectors静的にインポートしても、冗長に見えます。 StreamExメソッドtoSettoListtoCollectiontoMapgroupingByをいくつかの署名とともに追加しました。 これがIDである場合、キーの関数からtoMapを省略toMapことができます。 いくつかの例:

 List<User> users; public List<String> getUserNames() { return StreamEx.of(users).map(User::getName).toList(); } public Map<Role, List<User>> getUsersByRole() { return StreamEx.of(users).groupingBy(User::getRole); } public Map<String, Integer> calcStringLengths(Collection<String> strings) { return StreamEx.of(strings).toMap(String::length); } 

joiningメソッドもコレクターに対応しますが、その前に、ストリームのコンテンツはString::valueOf介して渡されます。

 public String join(List<Integer> numbers) { return StreamEx.of(numbers).joining("; "); } 

検索とフィルタリングを削減


ストリーム内の特定のクラスのオブジェクトのみを選択する必要がある場合があります。 .filter(obj -> obj instanceof MyClass)書くことができ.filter(obj -> obj instanceof MyClass) 。 ただし、これではストリームのタイプが明確にならないため、要素のタイプを手動でキャストするか、別のステップ.map(obj -> (MyClass)obj)追加する必要があります。 StreamExを使用するStreamExこれはselectメソッドを使用して簡潔に行われます。

 public List<Element> elementsOf(NodeList nodeList) { return IntStreamEx.range(0, nodeList.getLength()).mapToObj(nodeList::item).select(Element.class).toList(); } 

ちなみに、 selectメソッドの実装では、マップステップは使用されませんが、フィルタリングの直後にストリームタイプの安全でない変換が適用されるため、パイプラインは再び長くなりません。

多くの場合、ストリームからnullをスローする必要があるため、 nonNull()メソッドを追加してfilter(Objects::nonNull)を置き換えfilter(Objects::nonNull) 。 また、ストリームから述語を満たす要素をremove(Predicate)メソッドもあります(逆にfilter )。 メソッド参照をより頻繁に使用できます。

 public List<String> readNonEmptyLines(Reader reader) { return StreamEx.ofLines(reader).map(String::trim).remove(String::isEmpty).toList(); } 

findAny(Predicate)およびfindFirst(Predicate) filter(Predicate).findAny()およびfilter(Predicate).findFirst()ます。 hasメソッドを使用すると、ストリームに特定の要素があるかどうかを確認できます。 同様のメソッドがプリミティブストリームに追加されます。

追加および追加


多くの場合、1つまたは2つの特別な値をストリームに追加するか、2つのストリームを接着する必要があります。 標準のStream.concatを使用することは、ネストされたブラケットを追加し、プログラムを左から右に読むという考えを台無しにするため、あまり良くStream.concatません。 concatを置き換えるためにconcat appendおよびprependを作成しました。これにより、現在のストリームの末尾または先頭に別のストリームまたは特定の値セットを追加できます。

 public List<String> getDropDownOptions() { return StreamEx.of(users).map(User::getName).prepend("(none)").toList(); } 

次のように配列を展開できます。

 public int[] addValue(int[] arr, int value) { return IntStreamEx.of(arr).append(value).toArray(); } 

コンパレータ


Java 8では、 Comparator.comparingIntなどのキー抽出メソッドを使用してコンパレーターを作成する方がはるかに簡単です。 最も一般的な並べ替えの状況を減らすために、1つのキーの最大値と最小値を検索し、 sortingBymaxBy 、およびminByメソッドのファミリーをminBy

 public User getMostActiveUser() { return StreamEx.of(users).maxByLong(User::getNumberOfPosts).orElse(null); } 

ところで、コンパレータによる並べ替えがプリミティブストリームに追加されました(便利な場合もあります)。 確かに、ボンネットの下には余分なボクシングがありますが、JITコンパイラーの積極的な最適化に頼ることができます。

反復可能


多くの人は、 Iterable iterator()メソッドが含まれているため、 Stream IterableインターフェイスStream実装することをIterableます。 特に、 Iterable再利用性を意味し、イテレータはストリームから1回しか取得できないため、これは行われていません。 Stack Overflowは、JDKにはすでにこの規則DirectoryStreamの例外があることを指摘しています。 何らかの方法で、ターミナルforEach代わりに通常のforループを使用したい場合があります。 これには多くの利点があります。効果的に最終的なだけでなく、任意の変数を使用できる、例外をスローできる、デバッグが簡単、短いスタックトレースなどです。一般に、ストリームを作成してすぐに使用する場合、大きな罪はないと思いますforループで。 もちろん、 Iterableを受け入れ、それを数回バイパスできるメソッドに渡さないように注意する必要があります。 例:

 public void copyNonEmptyLines(Reader reader, Writer writer) throws IOException { for(String line : StreamEx.ofLines(reader).remove(String::isEmpty)) { writer.write(line); writer.write(System.lineSeparator()); } } 

あなたがそれを好きなら、それを使用しますが、注意してください。

キーと値をマップする


多くの場合、値が特定の条件を満たす、またはその逆のすべてのMapキーを処理する必要があります。 これを直接書くのは少し退屈です: Map.Entryをいじる必要がありMap.Entry 。 これを静的メソッドofKeys(map, valuePredicate)およびofValues(map, keyPredicate)のフードの下に隠しました。

 Map<String, Role> nameToRole; public Set<String> getEnabledRoleNames() { return StreamEx.ofKeys(nameToRole, Role::isEnabled).toSet(); } 

エントリーストリーム


より複雑なMap処理シナリオのために、別個のEntryStreamクラスがEntryStreamEntryStreamオブジェクトのストリーム。 StreamEx機能を部分的に繰り返しますが、キーと値を個別に処理する追加のメソッドも含まれています。 場合によっては、これにより、新しいMap生成と既存のMap逆アセンブルの両方が簡単になります。 たとえば、これはMap-Listを反転する方法です(値のリストからの行はキーに分類され、キーは新しい値のリストを形成します)。

 public Map<String, List<String>> invert(Map<String, List<String>> map) { return EntryStream.of(map).flatMapValues(List::stream).invert().grouping(); } 

flatMapValues使用しflatMapValues 。これにより、ストリームEntry<String, List> Entry<String, String> , invert , , groupingMap .

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .
flatMapValuesEntry<String, List> Entry<String, String> , invert , , groupingMap .

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .
Entry<String, List> Entry<String, String> , invert , , groupingMap .

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .
 Entry<String, List>  Entry<String, String> ,  invert ,      ,    groupingMap . 

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .
Entry<String, List> Entry<String, String> , invert , , groupingMap .

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .
 Entry<String, List>  Entry<String, String> ,  invert ,      ,    groupingMap . 

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .
Entry<String, List> Entry<String, String> , invert , , groupingMap .

Map :

public Map<String, String> stringMap(Map<Object, Object> map) { return EntryStream.of(map).mapKeys(String::valueOf).mapValues(String::valueOf).toMap(); }
, :

Map<String, Group> nameToGroup; public Map<String, List<User>> getGroupMembers(Collection<String> groupNames) { return StreamEx.of(groupNames).mapToEntry(nameToGroup::get).nonNullValues().mapValues(Group::getMembers).toMap(); }
mapToEntry EntryStream .

. , - . — GitHub , Maven Central . JavaDoc , . , , - .

Source: https://habr.com/ru/post/J255659/


All Articles