Java 9: What are collection factory methods? [closed] - java

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
The arrival of Java 9 brings many new features to Java's Collections API, one of which being collection factory methods.
What are they and how can I implement them properly?

Note 1: To prevent the use of raw-types, I have opted to provide a generic type for each class that I mention below by using E, representing an element of a Collection<E>.
Note 2: This answer is subject to change; please edit this post if a typo has occurred.
What are collection factory methods?
A collection factory method in Java is a static method that provides a simple way of initializing an immutable Collection<E>.
Being immutable, no elements can be added to, removed from, or modified inside the Collection<E> after it is initialized.
With Java 9, collection factory methods are provided for the following interfaces: List<E>, Set<E>, and Map<K, V>
What do they improve?
Up until Java 9, there has been no simple, universal method to initialize a Collection<E> with initial elements/key-value entries. Previously, developers were required to initialize them as follows (assuming the generic types E, K, and V have been replaced with Integer):
List<Integer>
The following method is arguably the simplest to initialize a List<Integer> with initial elements, however the result is simply a view of a List<Integer>; we are unable to add to or remove from this List<Integer>, but we are still able to modify existing elements by using List#set.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
If we wanted our List<Integer> to be entirely mutable, then we would have to pass it to the constructor of an ArrayList<Integer>, for example:
List<Integer> mutableList = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer>
A Set<Integer> required more code to initialize with initial elements than a List<Integer> does (seeing as a List<Integer> is required to initialize a Set<Integer> with initial elements), which can be seen below.
Set<Integer> mutableSet = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Map<Integer, Integer>
A Map<Integer, Integer> is arguably the most complicated to initialize with initial key-value entries; however, there are multiple ways to go about it.
One method was to first initialize an empty Map<Integer, Integer> and simply call Map#put to add key-value entries.
Another method was to use an anonymous class with two curly braces, which would still require Map#put to be called.
Why should I use them?
I argue that collection factory methods provide the developer with a concise method of initializing a List<E>, Set<E>, or Map<K, V> with initial elements/key-value entries, which can be seen by the examples below.
What is the proper syntax to use?
For simplicity, these examples will replace the generic types E, K, and V with Integer.
List<Integer>
List<Integer> list = List.of();
Initializes an empty, immutable List<Integer>.
List<Integer> list = List.of(1);
Initializes an immutable List<Integer> with one element.
List<Integer> list = List.of(1, 2);
Initializes an immutable List<Integer> with two elements.
List<Integer> list = List.of(1, 2, 3, 4, 5, ...);
Initializes an immutable List<Integer> with a variable amount of elements.
Set<Integer>
Set<Integer> set = Set.of();
Initializes an empty, immutable Set<Integer>.
Set<Integer> set = Set.of(1);
Initializes an immutable Set<Integer> with one element.
Set<Integer> set = Set.of(1, 2);
Initializes an immutable Set<Integer> with two elements.
Set<Integer> set = Set.of(1, 2, 3, 4, 5, ...);
Initializes an immutable Set<Integer> with a variable amount of elements.
Map<Integer, Integer>
Map<Integer, Integer> map = Map.of();
Initializes an empty, immutable Map<Integer, Integer>.
Map<Integer, Integer> map = Map.of(1, 2);
Initializes an immutable Map<Integer, Integer> with one key-value entry.
Note that the key is 1 and the value is 2.
Map<Integer, Integer> map = Map.of(1, 2, 3, 4);
Initializes an immutable Map<Integer, Integer> with two key-value entries.
Note that the keys are 1 and 3 and the values are 2 and 4.
Map<Integer, Integer> map = Map.ofEntries(Map.entry(1, 2), Map.entry(3, 4), ...);
Initializes an immutable Map<Integer, Integer> with a variable amount of key-value entries.
As you can see, this new method of initialization requires less code than its predecessors.
Can I use collection factory methods to create mutable objects?
The Collection<E> created by collection factory methods are inherently immutable, however we are able to pass them to a constructor of an implementation of the Collection<E> to produce a mutable version:
List<Integer>
List<Integer> mutableList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
Set<Integer>
Set<Integer> mutableSet = new HashSet<>(Set.of(1, 2, 3, 4, 5));
Map<Integer, Integer>
Map<Integer, Integer> mutableMap = new HashMap<>(Map.of(1, 2, 3, 4));

Related

Java stream collect check if result would contain element

As I couldn't find anything related to this, I am wondering if streams even allow this.
In my answer to another question, I have following code to add elements to a result list, only if the result list doesn't already contain it:
List<Entry<List<Integer>, Integer>> list = new ArrayList<>(diffMap.entrySet());
list.sort(Entry.comparingByValue());
List<List<Integer>> resultList = new ArrayList<>();
for (Entry<List<Integer>, Integer> entry2 : list) {
if (!checkResultContainsElement(resultList, entry2.getKey()))
resultList.add(entry2.getKey());
}
checkResultContainsElement method:
private static boolean checkResultContainsElement(List<List<Integer>> resultList, List<Integer> key) {
List<Integer> vals = resultList.stream().flatMap(e -> e.stream().map(e2 -> e2))
.collect(Collectors.toList());
return key.stream().map(e -> e).anyMatch(e -> vals.contains(e));
}
Now I am wondering, if this for-loop:
for (Entry<List<Integer>, Integer> entry2 : list) {
if (!checkResultContainsElement(resultList, entry2.getKey()))
resultList.add(entry2.getKey());
}
can be realized using streams. I don't think that .filter() method would work, as it would remove data from List<Entry<List<Integer>, Integer>> list while I don't even know if an element should be considered. I guess that a custom collector could work, but I also wouldn't know how to implement one, as the result is constantly changing with each newly added element.
I am looking for something like this (can be different if something else is better):
list.stream().sorted(Entry.comparingByValue()).collect(???);
where ??? would filter the data and return it as a list.
The values of one result list may not be contained in another one. So these lists are valid:
[1, 2, 3, 4]
[5, 6, 7, 8]
[12, 12, 12, 12]
but of these, only the first is valid:
[1, 2, 3, 4] <-- valid
[5, 3, 7, 8] <-- invalid: 3 already exists
[12, 12, 2, 12] <-- invalid: 2 already exists
If we put aside for a moment the details on whether implementation will be stream-based or not, the existing implementation of how uniqueness of the values of incoming lists is being checked can be improved.
We can gain a significant performance improvement by maintaining a Set of previously encountered values.
I.e. values from each list that was added to the resulting list would be stored in a set. And in order to ensure uniqueness of every incoming list, its values would be checked against the set.
Since operations of a stream pipeline should be stateless, as well as collector shouldn't hold a state (i.e. changes should happen only inside its mutable container). We can approach this problem by defining a container that will encompass a resulting list of lists of Foo and a set of foo-values.
I've implemented this container as a Java 16 record:
public record FooContainer(Set<Integer> fooValues, List<List<Foo>> foosList) {
public void tryAdd(List<Foo> foos) {
if (!hasValue(foos)) {
foos.forEach(foo -> fooValues.add(foo.getValue()));
foosList.add(foos);
}
}
public boolean hasValue(List<Foo> foos) {
return foos.stream().map(Foo::getValue).anyMatch(fooValues::contains);
}
}
The record shown above would is used as a mutable container of a custom collector created with Colloctors.of(). Collector's accumulator make's use of tryAdd() method defined by the container. And the finisher extracts the resulting list from the container.
Note that this operation is not parallelizable, hence combiner of the collector throws an AssertionError.
public static void main(String[] args) {
Map<List<Foo>, Integer> diffMap =
Map.of(List.of(new Foo(1), new Foo(2), new Foo(3)), 1,
List.of(new Foo(1), new Foo(4), new Foo(5)), 2,
List.of(new Foo(7), new Foo(8), new Foo(9)), 3);
List<List<Foo>> result = diffMap.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.collect(Collector.of(
() -> new FooContainer(new HashSet<>(), new ArrayList<>()),
FooContainer::tryAdd,
(left, right) -> {throw new AssertionError("The operation isn't parallelizable");},
FooContainer::foosList
));
System.out.println(result);
}
Output:
[[Foo{1}, Foo{2}, Foo{3}], [Foo{7}, Foo{8}, Foo{9}]]
May be something like this:-
list.stream().
sorted(Entry.comparingByValue()).
collect(ArrayList<List<Foo>>::new,(x,y)->!checkResultContainsElement(x, y.getKey()),(x,y)->x.add(y.getKey()));

Adding to double Arraylist in HashMap

I am trying to add pairs that add up to a certain number in java and one of the ways I am trying to attempt this is to create a double ArrayList within my HashMap. If I add 1 and 2 to my list, I will get 3 as my key. For example:
HashMap<Integer, ArrayList<ArrayList<Integer>>> map = new HashMap<>();
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
map.put(list.get(0) + list.get(1), new ArrayList<>(list));
Output looks like this
Key: 3 Value: [[1,2]]
If I were to add one more pair
Key: 3 Value: [[1,2],[0,3]]
but I keep getting a 'method is not applicable in the type HashMap<Integer,ArrayList<ArrayList>> is not applicable for the arguments (int, new ArrayList<>(list))'
I've also tried
new ArrayList<>(new ArrayList<>(list))
thinking that I might need to initialize the bigger matrix first but I end up with the same error sadly.
This line:
new ArrayList<>(list)
creates a flat ArrayList<Integer>, while the HashMap is expecting ArrayList<ArrayList<Integer>>. By the same token, new ArrayList<>(new ArrayList<>(list)) also creates a flat Integer list because you are just doing the same thing twice. See the API document for ArrayList
This is one way that would work given the 2-D list setup:
HashMap<Integer, List<List<Integer>>> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
List<List<Integer>> outer = new ArrayList<>();
outer.add(list);
map.put(list.get(0) + list.get(1), outer);
You could also create some lambdas which may facilitate this. For example.
Map<Integer, List<List<Integer>>> map1 = new HashMap<>();
Create a function to sum the elements of a list.
Function<List<Integer>, Integer> sum =
list -> list.stream()
.mapToInt(Integer::intValue).sum();
Then create a BiConsumer to take the list pair and existing map and add them if need be. The computeIfAbsent, enters a value for the key if the key was null or absent. The list is returned so the the pair can be added to the newly created list.
BiConsumer<List<Integer>, Map<Integer,
List<List<Integer>>>> addToMap =
(pair, map) -> {
map.computeIfAbsent(sum.apply(pair),
v -> new ArrayList<>()).add(pair);
};
Putting it all together.
addToMap.accept(List.of(1,2), map1);
addToMap.accept(List.of(0,4), map1);
addToMap.accept(List.of(1,5), map1);
addToMap.accept(List.of(0,3), map1);
addToMap.accept(List.of(-1,5), map1);
addToMap.accept(List.of(-1,2,3),map1);
map1.entrySet().forEach(System.out::println);
prints
3=[[1, 2], [0, 3]]
4=[[0, 4], [-1, 5], [-1, 2, 3]]
6=[[1, 5]]
As you can see, this doesn't enforce any size limitations on the "pairs."
This may be overkill for what you want but there may be some elements you can put to use. Also note that List.of above is immutable.

Iteration order of java.util.HashMap.values()

Is it guaranteed that keys and values in standard implementations of java.util.Map are returned in the same order? For example, if map contains mapping x1 -> y1 and x2 -> y2, then if keySet() iteration yields x1, x2, is it guaranteed that values() iteration will yield y1, y2 and not y2, y1? I haven't seen anywhere guaranteed that this is true, but it seems to work. Could anyone give confirm or deny this premise and give counterexample?
public class MapsTest {
#Test
public void hashMapKeysAndValuesAreInSameOrder() {
assertKeysAndValuesAreInSameOrder(new HashMap<>());
}
#Test
public void treeMapKeysAndValuesAreInSameOrder() {
assertKeysAndValuesAreInSameOrder(new TreeMap<>());
}
private void assertKeysAndValuesAreInSameOrder(Map<Integer, Integer> map) {
Random random = new Random();
IntStream.range(0, 100000).map(i -> random.nextInt()).forEach(i -> map.put(i, i));
assertEquals(new ArrayList<>(map.keySet()), new ArrayList<>(map.values()));
}
}
From the doc of HashMap,
This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
If you look at its code, you will find out that once you call put(), eventually hash is calculated, entry object is created and added into a bucket array. I have over simplified the purpose of the code just to explain what happens behind the scene.
If you want guaranteed order, use LinkedHashMap or TreeMap depending on your requirements
Also check,
Difference between HashMap, LinkedHashMap and TreeMap
Interesting question. I gave it a shot. and it seems the RELATIVE ordering of the keys remains in sync with the values.
import java.util.*;
public class MapClass {
public static Map<String, Integer> map = new HashMap<>();
public static void main (String[] args){
map.put("first", 1);
map.put("second", 2);
map.put("third", 3);
map.put("fourth", 4);
map.put("fifth", 5);
System.out.println(map.keySet());
System.out.println(map.values());
map.put("sixth", 6);
System.out.println(map.keySet());
System.out.println(map.values());
map.remove("first");
System.out.println(map.keySet());
System.out.println(map.values());
}
}
I get:
[third, fifth, fourth, first, second]
[3, 5, 4, 1, 2]
[sixth, third, fifth, fourth, first, second]
[6, 3, 5, 4, 1, 2]
[sixth, third, fifth, fourth, second]
[6, 3, 5, 4, 2]
This simple example seems to show that while a HashMap will not preserve the order of insertion, it seems it does preserve the relative ordering of keys and values.
keyset() and values() does not guarantee any order though, but it seems in practice that it'll return the same order for multiple invocations. As the comment on there says, even if you could rely on it, it's questionable whether you should.
From the HashMap docs:
This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
Looking at http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java#HashMap.values%28%29, I'd guess it works because both the KeyIterator and ValueIterator extend HashIterator.
But that's only if you don't change the HashMap in the mean time, e.g. adding and removing items between calling keySet() and values(), because that can change the bucket size, and the HashIterator will behave differently.

Implementation of super MultiValue map

Is there any ready implementation of super MultiValue map?
I need a map
SuperMap<List<String>, List<String>> superMap
where key List is set of keys, and value List is set of values.
keys: {a, b, c} and values for any of these keys are values: {1, 2, 3, 4, 5}
It means that key a should have a values {1, 2, 3, 4, 5} as well as the key b and c.
Updated with requirements from comments
I need to get all keys of one group? For example I need to get collection of keys for one similar value. In that case I cannot use your approach map.put("a", value); because I need to group (a, b it is first group, and c is related to second group).
UPD2
I'm looking for a simple and much concise solution to replace this code
Map<List<String>, List<String>> ops = new HashMap<List<String>, List<String>>() {{
put(asList("a", "aaa"), asList("1", "2"));
put(asList("b", "bbb"), asList("3", "4"));
}};
public static List<String> values(String optionKey) {
for (List<String> key : ops.keySet()) {
for (String k : key) {
if (optionKey.equals(k)) {
return ops.get(key);
}
}
}
return Collections.emptyList();
}
with some smart impl form any well known lib (Google Guava or something).
I think you're overcomplicating your data structure. You can simply use HashMap or similar implementation.
Map<String, List<String>> map = new LinkedHashMap<>();
List<String> value = new ArrayList<>();
// Fill the value list here...
map.put("a", value);
map.put("b", value);
map.put("c", value);

How do I use collection literals in Java 7?

I've tried the following line:
Map<Character, Color> map={new Character('r'):Color.red,new Character('b'):Color.black};
But Netbeans 7 rejects this, with the error message '{' expected, ';' expected.
I've set the Source/Binary format as 'JDK 7'and the platform to 'JDK 1.7', is there anything else I need to do?
Neither Java 7 nor Java 8 supports collection literals, as discussed in this question: Are Project Coin's collection enhancements going to be in JDK8?
You can use Google's Guava library if you need only immutable collections. ImmutableList, ImmutableSet and ImmutableMap have several overloaded factory methods or even builders that make creating collections easy:
List<Integer> list = ImmutableList.of(1, 1, 2, 3, 5, 8, 13, 21);
Set<String> set = ImmutableSet.of("foo", "bar", "baz", "batman");
Map<Integer, String> map = ImmutableMap.of(1, "one", 2, "two", 3, "three");
EDIT
Java 9 has added collection factory methods similar to those of Guava:
List.of(a, b, c);
Set.of(d, e, f, g);
Map.of(k1, v1, k2, v2)
Map.ofEntries(
entry(k1, v1),
entry(k2, v2),
entry(k3, v3),
// ...
entry(kn, vn)
);
You need to define a concrete map implementation, optionally combined with double brace initialization:
Map<Character, Color> map = new HashMap<Character, Color>() {{
put(new Character('r'), Color.red);
put(new Character('b'), Color.black );
}};
To expand a little on Thomas's answer... Map is an interface, and must be instantiated through one of the associated concrete implementations (HashMap, TreeMap, or LinkedHashMap). It is still good practice; however, to declare your reference variable as the interface implementation rather than the specific concrete, as it provides future flexibility.
Regarding the code snippet though, I think you do still need the Key-value pairs defined in the assignment side of the declaration. So, I would change:
Map<Character, Color> map = new HashMap<>() {{
to
Map<Character, Color> map = new HashMap<Character, Color>() {{

Categories