Convert ArrayList<String> to Set<ScopeItem> with Java streams - java

I want to convert an ArrayList<String> to Set<ScopeItem> with Java streams.
ScopeItem is a enum;
items is an ArrayList<String>;
Set<ScopeItem> scopeItems = items.stream()
.map(scopeString -> ScopeItem.valueOf(scopeString))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
On a string that isn't in the enum this throws the following:
java.lang.IllegalArgumentException: No enum const...
Ideally, I would like to skip past any Strings that don't match.
I think maybe a using flatmap? Any ideas how to do it?

You could add the following method to your ScopeItem:
public static ScopeItem valueOfOrNull(String name) {
try {
return valueOf(name);
} catch (IllegalArgumentException e) {
// no such element
return null;
}
}
and use that to map your enum values:
.map(scopeString -> ScopeItem.valueOfOrNull(scopeString))
Subsequent .filter() on non-null values (which you already have) will filter-out those nulls that correspond to non-matching strings.

You can put a try-catch inside your map to return null instead of throwing an exception:
Set<ScopeItem> scopeItems = items.stream()
.map(scopeString ->
{
try
{
return ScopeItem.valueOf(scopeString);
}
catch (IllegalArgumentException e)
{
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
You could also use filter beforehand to check whether the array of values contains the string you're looking for:
Set<ScopeItem> scopeItems = items.stream()
.filter(scopeString -> Arrays.stream(ScopeItem.values())
.anyMatch(scopeItem -> scopeItem.name().equals(scopeString)))
.map(ScopeItem::valueOf)
.collect(Collectors.toSet());

Unlike others, I won't recommend using exceptions, as I feel they should be used for exceptional situations, and not for something that will likely to occur. A simple solution, is to have a static set of acceptable strings, and simply check, if a string you want to use valueOf with is in said set.
package test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class Test {
public static enum ScopeItem {
ScopeA,
ScopeB;
private static Set<String> castableStrings;
static {
castableStrings = new HashSet<>();
for (ScopeItem i : ScopeItem.values()) {
castableStrings.add(i.name());
}
}
public static boolean acceptable(String s) {
return castableStrings.contains(s);
}
}
public static void main(String[] args) {
List<String> items = Arrays.asList("ScopeA", "RandomString", "ScopeB");
Set<ScopeItem> scopeItems = items.stream()
.filter(ScopeItem::acceptable)
.map(ScopeItem::valueOf)
.collect(Collectors.toSet());
System.out.println(scopeItems.size());
}
}

There's a more elegant approach here. You don't have to add any new fields to your enum; you can simply run a stream against it as well and determine if there's any matches in your collection.
The below code assumes an enum declaration of:
enum F {
A, B, C, D, E
}
and looks as thus:
List<String> bad = Arrays.asList("A", "a", "B", "b", "C", "c");
final Set<F> collect = bad.stream()
.filter(e -> Arrays.stream(F.values())
.map(F::name)
.anyMatch(m -> Objects.equals(e, m)))
.map(F::valueOf)
.collect(Collectors.toSet());
Two parts to pay attention to here:
We do the internal filter on the values of our enum, and map that to a String through F::name.
We determine if there's any match on the elements of our base collection with the elements of our enum in a null-safe way (Objects.equals does The Right Thing™ with nulls here)

You could use Apache Common's commons-lang3 EnumUtils.getEnum() instead of valueOf(). This returns null if there is no matching enum entry (which you can then filter exactly as you have in your code).

The easiest and clearest method would be to filter your Enum's values() method on items.contains():
Set<ScopeItem> enumVals = Arrays.stream(ScopeItem.values())
.filter(e -> items.contains(e.name()))
.collect(Collectors.toSet());
No added functionality just to get the stream to do what you want, and it is obvious what this does at a glance.

Related

Split Java-8 Stream result into Success and Failure list

I've a List of Foo, where on each Foo I apply a processor method to get ValidItem.
If there is an error in processing, then I returned ErrorItem.
Now How to process this by Java 8 streams to get the result in form of 2 different lists
List<Foo> FooList = someList....;
class ValidItem extend Item{......}
class ErrorItem extend Item{......}
Item processItem(Foo foo){
return either an object of ValidItem or ErrorItem;
}
I believe I can do this
Map<Class,List<Item>> itemsMap =
FooList
.stream()
.map(processItem)
.collect(Collectors.groupingBy(Object::getClass));
But as List<Parent> IS NOT a List<Child> so I can't typecast the map result into List<ValidItem>
In reality ErrorItem and ValidItem are two completely different class not related at all, just for the sake of this steam processing and processItem method I kept them in same hierarchy by extending a marker Item class,.
and in many other Places in code, I cant/shouldn't refer ValidItem as Item , as It give an idea that it can be an ErroItem too.
Is there a proper way of doing it with streams, where at the end I get 2 lists. and ErrorItem and
ValidItem are not extending same Item class ?
############## Update ##############
As I said ValidItem and ErrorItem shouldn't be same, so I changed the signature of process method and passed it a list.
I know this is not how Stream shold be used. Let me know if you have better way
List<Foo> FooList = someList....;
class ValidItem {......}
class InvalidFoo{......}
ValidItem processFoo(Foo foo, List<InvalidFoo> foolist){
Do some processing on foo.
either return new ValidItem ();
OR
fooList.add(new InvalidFoo()) , and then return null;
}
List<InvalidFoo> invalidFooList = new ArrayList();
List<ValidItem> validItem =
fooList
.stream()
.map(e->processItem(e,invalidFooList))
.filter(Objects::notNull)
.collect(Collectors.toList());
now I have both invalid and valid list, but this doesn't look like a clean stream code.
With recent Java versions, you can use
for the Item processItem(Foo foo) method returning either ValidItem or ErrorItem:
Map.Entry<List<ValidItem>, List<ErrorItem>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x):null, toList()),
flatMapping(x -> x instanceof ErrorItem? Stream.of((ErrorItem)x):null, toList()),
AbstractMap.SimpleImmutableEntry::new
));
List<ValidItem> valid = collected.getKey();
List<ErrorItem> invalid = collected.getValue();
for the ValidItem processFoo(Foo foo, List<InvalidFoo> foolist):
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(foo -> {
List<InvalidFoo> invalid = new ArrayList<>(1);
ValidItem vi = processFoo(foo, invalid);
return new AbstractMap.SimpleImmutableEntry<>(
vi == null? Collections.<ValidItem>emptyList():
Collections.singletonList(vi),
invalid);
})
.collect(teeing(
flatMapping(e -> e.getKey().stream(), toList()),
flatMapping(e -> e.getValue().stream(), toList()),
AbstractMap.SimpleImmutableEntry::new
));
List<ValidItem> valid = collected.getKey();
List<InvalidFoo> invalid = collected.getValue();
The flatMapping collector has been introduced in Java 9.
In this specific case, instead of
flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x): null, toList())
you can also use
filtering(x -> x instanceof ValidItem, mapping(x -> (ValidItem)x, toList()))
but each variant requires Java 9, as filtering also does not exist in Java 8. The teeing collector even requires Java 12.
However, these collectors are not hard to implement.
This answer contains a Java 8 compatible version of the flatMapping collector at the end. If you want to use the alternative with filtering and mapping, you can find a Java 8 compatible version of filtering in this answer. Finally, this answer contains a Java 8 compatible variant of the teeing collector.
When you add these collectors to your codebase, the solutions at the beginning of this answer work under Java 8 and will being easily adaptable to future versions. Assuming an import static java.util.stream.Collectors.*; in your source file, you only have to remove the backports of these methods, to switch to the standard JDK versions.
It would be better if processItem returned an Either or Pair type instead of the two variants addressed above. If you don’t want to use 3rd party libraries, you can use a Map.Entry instance as a “poor man’s pair type”.
Having a method signature like
/** Returns an entry with either, key or value, being {#code null} */
Map.Entry<ValidItem,InvalidFoo> processItem(Foo foo){
which could be implemented by returning an instance of AbstractMap.SimpleImmutableEntry,
a JDK 9+ solution could look like
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(e -> Stream.ofNullable(e.getKey()), toList()),
flatMapping(e -> Stream.ofNullable(e.getValue()), toList()),
AbstractMap.SimpleImmutableEntry::new
));
and Java 8 compatible (when using the linked collector backports) versions:
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(e -> Stream.of(e.getKey()).filter(Objects::nonNull), toList()),
flatMapping(e -> Stream.of(e.getValue()).filter(Objects::nonNull), toList()),
AbstractMap.SimpleImmutableEntry::new
));
or
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
mapping(Map.Entry::getKey, filtering(Objects::nonNull, toList())),
mapping(Map.Entry::getValue, filtering(Objects::nonNull, toList())),
AbstractMap.SimpleImmutableEntry::new
));
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Foo> FooList = new ArrayList<>();
for(int i = 0 ; i < 100; i++){
FooList.add(new Foo(i+""));
}
Map<Class,List<Item>> itemsMap =
FooList
.stream()
.map(Main::processItem)
.collect(Collectors.groupingBy(Object::getClass));
List<ValidItem> validItems = itemsMap.get(ValidItem.class).stream().map((o -> (ValidItem)o)).collect(Collectors.toList());
}
public static Item processItem(Foo foo){
Random random = new Random(System.currentTimeMillis());
if(Integer.parseInt(foo.name) % 2== 0){
return new ValidItem(foo.name);
}else{
return new ErrorItem(foo.name);
}
}
static class ValidItem extends Item{
public ValidItem(String name) {
super("valid: " + name);
}
}
static class ErrorItem extends Item{
public ErrorItem(String name) {
super("error: "+name);
}
}
public static class Item {
private String name;
public Item(String name) {
this.name = name;
}
}
}
I suggest this solution.
You can use Vavr library.
final List<String> list = Arrays.asList("1", ",", "1", "0");
final List<Either<ErrorItem, ValidItem>> eithers = list.stream()
.map(MainClass::processData)
.collect(Collectors.toList());
final List<ValidItem> validItems = eithers.stream()
.filter(Either::isRight)
.map(Either::get)
.collect(Collectors.toList());
final List<ErrorItem> errorItems = eithers.stream()
.filter(Either::isLeft)
.map(Either::getLeft)
.collect(Collectors.toList());
...
private static Either<ErrorItem,ValidItem> processData(String data){
if(data.equals("1")){
return Either.right(new ValidItem());
}
return Either.left(new ErrorItem());
}

How to stream value of Java List (Varargs) in a method?

I have the following method:
public static List<A> getValuesExclusion(A exclusion) {
return Arrays.stream(values())
.filter(item -> item != exclusion)
.collect(Collectors.toList());
}
//this function returns enum list of A types that has no A type'exclusion'
Now I want to make it into a list as argument:
public static List<A> getValuesExclusion(A... exclusions){
return Arrays.stream(values())
.filter(???)
.collect(Collectors.toList());
}
My question is, how can I do the filter for the second case? I would like to retrieve an enum list that excludes all the values "exclusions" as input. Here are the attributes of class A:
public enum A implements multilingualA{
A("a"),
B("b"),
C("c"),
D("d");
...
}
If you want to make sure all the items are not included in the exclusions you could do:
public static List<A> getValuesExclusion(AType... exclusions){
return Arrays.stream(values())
.filter(e -> Arrays.stream(exclusions).noneMatch(c -> c == e))
.collect(Collectors.toList());
}
Which will create a Stream of exclusions and then use noneMatch() to ensure the given AType is not included in the Array
You should rethink whether List really is the appropriate data type for something containing unique elements. A Set usually is more appropriate.
Then, if you care for performance, you may implement it as
public static Set<A> getValuesExclusion(A... exclusions){
return exclusions.length == 0? EnumSet.allOf(A.class):
EnumSet.complementOf(EnumSet.of(exclusions[0], exclusions));
}
The class EnumSet is specifically designed for holding elements of an enum type, just storing a bit for each constant, to tell whether it is present or absent. This allows operations like complementOf, which just flips all bits using a single ⟨binary not⟩ operation, without the need to actually traverse the enum constants.
If you insist on returning a List, you can do it as easy as
public static List<A> getValuesExclusion(A... exclusions){
return new ArrayList<>(exclusions.length == 0? EnumSet.allOf(A.class):
EnumSet.complementOf(EnumSet.of(exclusions[0], exclusions)));
}
I would not go with Streams here but with the a (imho) more readable approach:
public static List<A> getValuesExclusion(AType... exclusions){
List<A> values = Arrays.asList(values());
values.removeAll(Arrays.asList(ex));
return values;
}

Java, Collectors.toList() creates only one element

I am having troubles getting the following to work. The point of this task is to use streams:
Map<String, List<String>> descriptions = new HashMap<>();
wordArray[2] = "D1_E1^E2^E3^E4~D2_E1^E2";
if(wordArray[2].contains("~")) {
Arrays.stream(wordArray[2].split("~"))
.forEach(s -> descriptions.put(s.split("_")[0],
s.split("_")[1].contains("^")
? Arrays.stream(s.split("_")[1].split("^"))
.collect(Collectors.toList())
: Arrays.asList(s.split("_")[1])));
}
For some reason this Collectors.toList() creates only one String "E1^E2^E3^E4" instead of seperate Strings.
My guess is that this is where I'm wrong : Arrays.stream(s.split("_")[1].split("^")), but I don't know where exactly and how to fix it. More so because this works just fine and is quite similar to the one above:
List<String> synonyms = new ArrayList<>();
wordArray[3] = "S1_S2_S3_S4";
synonyms = wordArray[3].contains("_")
? Arrays.stream(wordArray[3].split("_")).
collect(Collectors.toList())
: Arrays.asList(wordArray[3]);
Thanks!
Not sure what your expected output should be, but if it's something like this
[D1, E1, E2, E3, E4, D2, E1, E2]
then one of the way you can do it with Java streams is:
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
public class Parser {
public static void main(String[] args) {
String str = "D1_E1^E2^E3^E4~D2_E1^E2";
List<String> strings = Parser.parseString(str);
System.out.println(strings);
}
public static List<String> parseString(String str) {
return Stream.of(str)
.map(s -> s.replaceAll("[_^~]", "-").split("-"))
.flatMap(Stream::of)
.collect(toList());
}
}
From here you can factor out the regex and pass it into parseString as second argument to have more flexibility (if needed).

Java 8 collect() only isPresent() Optional values [duplicate]

This question already has answers here:
Using Java 8's Optional with Stream::flatMap
(12 answers)
Closed 6 years ago.
Is there a more elegant way of practically achieving this in Java 8?
list.stream()
.map(e -> myclass.returnsOptional(e))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
I'm talking about filter(Optional::isPresent) followed by map(Optional::get), I want to elegantly collect in a list only Optional results which have a value.
In your case you can use one flatMap instead of combinations of map filter and again map.
To Do that it's better to define a separate function for creating a Stream: public private static Stream<Integer> createStream(String e) to not have several lines of code in lambda expression.
Please see my full Demo example:
public class Demo{
public static void main(String[] args) {
List<String> list = Arrays.asList("1", "2", "Hi Stack!", "not", "5");
List<Integer> newList = list.stream()
.flatMap(Demo::createStream)
.collect(Collectors.toList());
System.out.println(newList);
}
public static Stream<Integer> createStream(String e) {
Optional<Integer> opt = MyClass.returnsOptional(e);
return opt.isPresent() ? Stream.of(opt.get()) : Stream.empty();
}
}
class MyClass {
public static Optional<Integer> returnsOptional(String e) {
try {
return Optional.of(Integer.valueOf(e));
} catch (NumberFormatException ex) {
return Optional.empty();
}
}
}
in case returnsOptional cannot be static you will need to use "arrow" expression instead of "method reference"
Not sure if its so different but you could just filter based on your optional instead of getting the optional and filtering next.
Something like this?
list.stream()
.filter(e -> myclass.returnsOptional(e).isPresent())
.collect(Collectors.toList());
Note: This will only work if returnsOptional returns the same object type as your original list item types.

Java 8 extract first key from matching value in a Map

Suppose I have a map of given name, surname pairs and I want to find the given name of the first entry in that map that has the surname matching a certain value.
How would we do this in a java 8 fashion.
In my test case example below I put two ways that would do it.
However the first one (looking for the given name of the first person with a surname of "Donkey") will throw java.util.NoSuchElementException: No value present so it is not safe.
The second one works but it is not only harder to read but it it is a bit not quite functional.
Just wondering if someone here would suggest me an easier clearer way of achieving this using either stream() or forEach() or both.
#Test
public void shouldBeAbleToReturnTheKeyOfTheFirstMatchingValue() throws Exception {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
String keyOfTheFirst = names.entrySet().stream().filter(e -> e.getValue().equals("Doe")).findFirst().get().getKey();
assertEquals("John", keyOfTheFirst);
try {
names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst().get();
} catch (NoSuchElementException e){
// Expected
}
Optional<Map.Entry<String, String>> optionalEntry = names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst();
keyOfTheFirst = optionalEntry.isPresent() ? optionalEntry.get().getKey() : null;
assertNull(keyOfTheFirst);
}
Thank you in advance.
To return a default value if there is no match, use Optional#orElse
names.entrySet().stream()
.filter(e -> e.getValue().equals("Donkey"))
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
From a similar question:
public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
return map.entrySet()
.stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
Then you can select the first, if you want to. Remember that the key is unique, the value is not.
Edit:
The whole code (thanks #Peter Lawrey)
package test;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Map<String, String> names = new LinkedHashMap<>();
names.put("John", "Doe");
names.put("Fred", "Flintstone");
names.put("Jane", "Doe");
Optional<String> firstKey = names.entrySet().stream()
.filter(entry -> Objects.equals(entry.getValue(), "Doe"))
.map(Map.Entry::getKey).findFirst();
if (firstKey.isPresent()) {
System.out.println(firstKey.get());
}
}
}
The solution provided by #Misha is the best one if you don't want to use the third-party code. My library has the special shortcut method ofKeys for such cases as I discovered that it's quite common task:
StreamEx.ofKeys(names, "Donkey"::equals).findFirst().orElse(null);
I like old fashioned:
static <K, V> K findFirstKeyByValue(Map<K, V> map, String value) {
for (Entry<K, V> e : map.entrySet())
if (e.getValue().equals(value))
return e.getKey();
return null;
}
Below is my code snippet to get key from map,
Map<String,String> pageDetails = new HashMap<String,String>();
public String getAssociatedKey(){
pageDetails.entrySet().stream().filter( e -> e.getValue().contains("John").findFirst().get().getKey();
}
In order to avoid null pointer exception if map entry exist with null value:
private String getValueByKey(Map<String, String> map, String key)
{
return map.entrySet().stream().filter(e ->
StringUtils.equalsIgnoreCase(e.getKey(), key)).findFirst().get()
.getValue();
}

Categories