How to convert Collection<Set<String>> into String Array - java

When I am trying to convert, I am getting below exception
java.lang.ArrayStoreException: java.util.HashSet
at java.util.AbstractCollection.toArray(Unknown Source)
This is my code
Map<String, Set<String>> map = new HashMap<>();
String[] keySet = map.keySet().toArray(new String[map.size()]);
Collection<Set<String>> collections = map.values();
String[] values = collection.toArray(new String[collection.size()]);// In this line getting Exception

What you're attempting to do is not possible. This is made explicit in the documentation.
The toArray method is documented to throw a java.lang.ArrayStoreException:
if the runtime type of the specified array is not a supertype of the
runtime type of every element in this collection
instead, what you can do is create a stream from the map values, flatMap it! (i.e. collapse the nested sequences) then collect to an array:
map.values() // Collection<Set<String>>
.stream() // Stream<Set<String>>
.flatMap(Collection::stream) // Stream<String>
.toArray(String[]::new); // String[]

You can simply use Stream.flatMap as you stream over the values to collect them later into an array. This can be done as:
String[] values = map.values().stream()
.flatMap(Collection::stream)
.toArray(String[]::new);
Note: The reason why your code compiles successfully even with
toArray(new String[collection.size()])
is that Collection.toArray(T[] a) because its hard for the compiler to determine the type prior to execution for a generic type. This is the same reason why even
Integer[] values = collections.toArray(new Integer[collections.size()]);
would compile in your case, but as you can now clearly see that nowhere in your collections do you have an Integer type. Hence at runtime, a new array is allocated with the runtime type of the specified array and the size of this collection.
That is where the ArrayStoreException in your case results from since now you have a type mismatch as your collection is of type Set<String> instead of String.
Important: You cannot possibly convert to a generic array as you may further think of.

Related

Cannot resolve method 'get' in 'Object' using streams

items is a List<List<String>>. When I add reversed, I see a compile error Cannot resolve method 'get' in 'Object'. I get this on x.get(0). Why do I get this and how can I get the reverse order? I would just like to sort the list based on the item name.
List<List<String>> items = new ArrayList<>();
List<String> item2 = new ArrayList<>();
item2.add("item2");
item2.add("3");
item2.add("4");
List<String> item1 = new ArrayList<>();
item1.add("item1");
item1.add("10");
item1.add("15");
List<String> item3 = new ArrayList<>();
item3.add("item3");
item3.add("17");
item3.add("8");
items.add(item1);
items.add(item2);
items.add(item3);
items.stream().sorted(Comparator.comparing(x -> x.get(0)).reversed()).collect(Collectors.toList())
Java's type inference fails here. When running Comparator.comparing(x -> x.get(0)).reversed(), it is not able to infer the types of the Comparator from the return type because of the .reversed(). Since they cannot be inferred, Java just uses Object.
In order to fix that, you need to somehow specify the type that is compared.
This can be done explicitly with Comparator.<List<String>, String>> comparing(x->x.get(0)).reversed(). Here, List<String>, String> are type arguments for the Comparator.comparing call. This means they specify the values of the generic types at compile-time so they don't have to be inferred. The first type parameter (List<String>) is the type to compare and second parameter (String) is the Comparable that is extracted from the type to compare (the result of List#get).
Alternatively, you could also specify it in the lambda expression's parameters and let Java infer it from that: Comparator.comparing((List<String> x) -> x.get(0)).reversed(). Since Java knows that x is of the type List<String>, it is able to infer the type of the lambda (Function<List<String>,String>) and the Comparator.comparing method.
When Sequenced Collections are added to Java, you could also use use method references. If you use Comparator.comparing(List::getFirst).reversed(), Java knows that the type to compare is a List and is able to use the get method. Note that List::getFirst is not yet part of the JDK as of the time of writing this.
As #Slaw mentioned in the comments, you could also use an entirely different approach by getting rid of the .reversed and including it in the Comparator.comparing like this: Comparator.comparing(x -> x.get(0), Comparator.reverseOrder()). The second parameter is another Comparator that is used for comparing the values extracted from the lambda x -> x.get(0).
List<List<String>> sorted_list = items.stream()
.sorted(Comparator.comparing(x -> x.get(0), Comparator.reverseOrder()))
.collect(Collectors.toList());

How to convert Array to HashMap in Java with Stream API

I was trying to something pretty simple, but it fails on compilation, and I can't understand who
I have a list of headers, I need to convert it to
Map<Index, String> meaning the key (index) and the value is the header name
I know how to make it with for each, but I want to have it in Collectors.to map
any help would be appreciated
final String[] headerDisplayName = getHeaderDisplayName(harmonizationComponentDataFixRequest);
IntStream.of(0, headerDisplayName.length).collect(Collectors.toMap(Function.identity(), index-> headerDisplayName[index]));
You can use range method in combination with boxed method of IntStream.
(When you use the of method like in your example, only 0 and the size of the array are in this stream. In addition this would lead to an ArrayIndexOutOfBoundsException)
A possible solution would look like this (first parameter of the range method is included, the second parameter is excluded)
Map<Integer, String> map = IntStream.range(0, headerDisplayName.length)
.boxed()
.collect(Collectors.toMap(
Function.identity(),
i -> headerDisplayName[i])
);
Adding to the #csalmhof's answer, I think it's to explain here why using boxed is working.
If you don't use boxed() method and simply write the following:
Map<Integer, String> map = IntStream.range(0, headerDisplayName.length)
.collect(Collectors.toMap(
Function.identity(),
index -> headerDisplayName[index])
);
Java will have to take index as of type Object and there's no implicit conversion that can happen and so you'll get error.
But if you put boxed() like in the following code, you won't get error:
Map<Integer, String> map = IntStream.range(0, headerDisplayName.length)
.boxed()
.collect(Collectors.toMap(
Function.identity(),
index -> headerDisplayName[index])
);
you're not getting error here because java interprets index as an Integer and there's implicit casting happening from Integer to int.
A good IDE can help you with this type of explanation. In IntelliJ if you press ctrl + space after keeping your cursor on index (with Eclipse key-map enabled) you will get the following without boxed().
And this is what you get when you've boxed() placed.
I hope this clarifies why using boxed() is saving you from compilation error. Also, you can use this thing in future to find actual type of parameter in lambda which can be helpful in case cases (one of the case is the one that OP pointed out)

Using Comparator.thenComparing for passed string

I'm using streams to try and create an arraylist of the keys in a map sorted first by the values (integers) then sort the keys alphabetically. I have them sorted by the values, but I get an error when trying to compare them alphabetically:
return map.keySet()
.stream()
.sorted(Comparator.comparing( (k1) -> map.get(k1)).thenComparing(String::compareTo)) //ErrorHere
.toArray(String[]::new);
Coc-java gives me a The method thenComparing(Comparator<? super Object>) in the type Comparator<Object> is not applicable for the arguments (String::compareTo) error. I have used thenComparing before, but the .sorted method looked like this:
.sorted(Comparator.comparing(String::length).thenComparing(String::compareTo))
This produced no errors and worked fine. I'm supposing that it might have something to do with what the lamda returns?
You probably just need to explicitly specify the type, e.g. Comparator.comparing((String k1) -> map.get(k1)), or Comparator.<String, WhateverTheValueTypeIs>comparing(map::get).

java stream map object parameter to hashset

Im trying to create a HashSet using the .map and streams functions.
s is an object with an "id" parameter, Long type.
Here is my failed attempt:
HashSet<Long> output = s.stream()
.map(v -> v.getId())
.collect(Collectors.toSet());
In your case the result of the stream will be Set<Long> and you want to assign this to a variable of HashSet type. Since HashSet is a subtype of Set you cannot do this. Either you change the type of your output variable to Set<Long> or you explicitly cast the collect result to HashSet<Long>. Since Collectors::toSet uses HashMap by default - it should work.
EDIT
As shmosel pointed out correctly it might be a bad idea to make assumptions about the return type so if you want HashSet specifically use toCollection(HashSet::new) :
HashSet<Long> output = s.stream()
.map(v -> v.getId())
.collect(Collectors.toCollection(HashSet::new));
Now the result of collect operation will be HashSet<Long> so you will be able to assign it to HashSet<Long> or Set<Long> variable.

convert collection to array by type

I tested this code:
Collection l = new ArrayList<Object>();
l.add(5);
l.add("test");
l.add(6);
Integer[] arr= l.toArray(new Integer[2]);
I tried to get only Integers from this collection and got this exception:
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at java.util.Arrays.copyOf(Arrays.java:2248)
at java.util.ArrayList.toArray(ArrayList.java:389)
at c.Main.main(Main.java:15)
May be there is an other way to filter but I need to understand the toArray(Object[] a) method. Why can't I filter by telling type and size of the array?
Why i can't filter by telling type and size of the array?
Because that's not what toArray() does, and nowhere does it say it can be used as a filter.
Functions & API's have definitions for what they do (and sometimes how they do that). You can't expect a function to do what you want it to do if it's not designed to do that.
toArray attempts to store the entire collection in the given array. If you want to filter it, you'll have to do so yourself. E.g.:
Integer[] arr =
l.stream().filter(x -> x instanceof Integer).toArray(Integer[]::new);
Please look at the java doc of the method. Each method works as per the contract it declares to the outside world (as per the signature or java doc).
<T> T[] toArray(T[] a); Clearly says in its java doc the below statement.
Throws:ArrayStoreException - if the runtime type of the specified array is not a supertype of the runtime type of every element in this collection.
In your case type of each of your element is not Integer.

Categories

Resources