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

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



Java 8には、オブジェクトStreamと、3つのプリミティブなIntStreamLongStream、およびDoubleStreamの 4つのストリームインターフェイスがあります。 完全に交換するには、標準スレッドですべてをラップする必要があります。 したがって、 StreamEx



IntStreamEx



LongStreamEx



およびDoubleStreamEx



DoubleStreamEx



。 元のインターフェイスを保存するには、次のような退屈なメソッドを大量に作成する必要がありました。



 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



メソッドtoSet



toList



toCollection



toMap



groupingBy



をいくつかの署名とともに追加しました。 これが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つのキーの最大値と最小値を検索し、 sortingBy



maxBy



、および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



クラスがEntryStream



EntryStream



オブジェクトのストリーム。 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



, , grouping



Map



.



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 , . , , - .




flatMapValues



Entry<String, List> Entry<String, String>



, invert



, , grouping



Map



.



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



, , grouping



Map



.



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



, , grouping



Map



.



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



, , grouping



Map



.



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



, , grouping



Map



.



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



, , grouping



Map



.



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 , . , , - .







All Articles