📜 ⬆️ ⬇️

We pump over Stream API, or we need more sugar.

Not so long ago, I managed to translate into Java 8 one of the projects I am working on. At first, of course, there was the euphoria of compactness and expressiveness of the structures using the Stream API, but over time I wanted to write even shorter, more flexible and expressive. At first, I added static methods to utility classes, but it only made the code worse. In the end, I came to the idea that we need to expand the streaming interfaces themselves, with the result that the small library StreamEx was born.

In Java 8, there are four streaming interfaces — an object Stream and three primitive IntStream , LongStream, and DoubleStream . To fully replace standard threads, you need to wrap them all. Thus, I have classes StreamEx , IntStreamEx , LongStreamEx and DoubleStreamEx . To save the original interface, I had to write quite a few boring methods like the following:

 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)); } ... } 

It was also necessary to create static constructors, and not only those that already exist in the original classes, but also some others (say, to replace random.ints (), there is an IntStreamEx.of(random) method). But then flows appeared that I can expand as I see fit. Below is a brief overview of the additional functionality.

Reduction in popular collectors


With the standard Stream API, you often have to write .collect(Collectors.toSet()) or .collect(Collectors.toList()) . It looks verbose, even if you import the Collectors statically. In StreamEx I added methods toSet , toList , toCollection , toMap , groupingBy with several signatures. The toMap can toMap function for keys if it is identity. A couple of examples:
')
 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); } 

The joining methods also correspond to collectors, but before that the contents of the stream are passed through String::valueOf :

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

Reduced search and filtering


Sometimes it is required to select only objects of a certain class in a stream. You can write .filter(obj -> obj instanceof MyClass) . However, this does not specify the type of stream, so you have to either manually type the elements or add one more step. .map(obj -> (MyClass)obj) . When using StreamEx this is done concisely using the select method:

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

In the implementation of the select method, by the way, the map step is not used, but simply after filtering, an unsafe stream type conversion is used, so the pipeline does not lengthen once again.

Quite often, you have to throw null from the stream, so I added the nonNull() method to replace filter(Objects::nonNull) . There is also a remove(Predicate) method that removes elements from the stream that satisfy the predicate ( filter backwards). It allows more frequent use of references to methods:

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

There are findAny(Predicate) and findFirst(Predicate) - shortcuts for filter(Predicate).findAny() and filter(Predicate).findFirst() . The has method allows you to find out if there is a specific element in the stream. Similar methods are added to primitive streams.

append and prepend


Often there is a need to add one or two special values ​​to a stream or to glue two threads together. Using the standard Stream.concat not very nice, as it adds nested brackets and spoils the idea of ​​reading the program from left to right. To replace concat I made append and prepend , which allow you to add another stream or a given set of values ​​to the end or beginning of the current thread:

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

Now you can expand the array like this:

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

Comparators


In Java 8, comparators are much easier to write using methods to extract a key like Comparator.comparingInt . To reduce the most common situations of sorting, searching for the maximum and minimum by one key, the family of methods sortingBy , maxBy and minBy :

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

By the way, sorting by comparator is added to primitive streams (sometimes it comes in handy). There, however, there is an extra boxing under the hood, but you can rely on aggressive optimizations of the JIT compiler.

Iterable


Many people want Stream implement the Iterable interface, because it contains an iterator() method. This is not done, in particular, because Iterable implies reusability, and the iterator can only be taken once from the stream. Although the Stack Overflow noted that the JDK already has an exception to this rule - DirectoryStream . Anyway, sometimes you want to use the usual for loop instead of the terminal forEach . This gives a number of advantages: you can use any variables, not only effectively final, you can throw any exceptions, it is easier to debug, shorter than the spectra, etc. In general, I think that there is no big sin if you created the stream and immediately use its in for loop. Of course, care must be taken not to pass it on to methods that take Iterable and can bypass it several times. Example:

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

If you like, use, but be careful.

Map Keys and Values


Often there is a need to process all Map keys whose values ​​satisfy a given condition, or vice versa. To write this directly is somewhat sad: you have to mess with Map.Entry . I hid it under the hood of the static methods ofKeys(map, valuePredicate) and ofValues(map, keyPredicate) :

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

EntryStream


For more complex Map processing scenarios, a separate EntryStream class has been EntryStream - Map.Entry object Map.Entry . It partially repeats the functionality of StreamEx , but also contains additional methods that allow processing keys and values ​​separately. In some cases, this makes it easier to both generate a new Map and disassemble an existing Map . For example, you can invert Map-List like this (strings from lists of values ​​fall into keys, and keys form new lists of values):

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

It uses flatMapValues , which turns the stream 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 , . , , - .
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/255659/


All Articles