Copying sets Java - java

Is there a way to copy a TreeSet? That is, is it possible to go
Set <Item> itemList;
Set <Item> tempList;
tempList = itemList;
or do you have to physically iterate through the sets and copy them one by one?

Another way to do this is to use the copy constructor:
Collection<E> oldSet = ...
TreeSet<E> newSet = new TreeSet<E>(oldSet);
Or create an empty set and add the elements:
Collection<E> oldSet = ...
TreeSet<E> newSet = new TreeSet<E>();
newSet.addAll(oldSet);
Unlike clone these allow you to use a different set class, a different comparator, or even populate from some other (non-set) collection type.
Note that the result of copying a Set is a new Set containing references to the objects that are elements if the original Set. The element objects themselves are not copied or cloned. This conforms with the way that the Java Collection APIs are designed to work: they don't copy the element objects.

Starting from Java 10:
Set<E> oldSet = Set.of();
Set<E> newSet = Set.copyOf(oldSet);
Set.copyOf() returns an unmodifiable Set containing the elements of the given Collection.
The given Collection must not be null, and it must not contain any null elements.

With Java 8 you can use stream and collect to copy the items:
Set<Item> newSet = oldSet.stream().collect(Collectors.toSet());
Or you can collect to an ImmutableSet (if you know that the set should not change):
Set<Item> newSet = oldSet.stream().collect(ImmutableSet.toImmutableSet());

Java 8+:
Set<String> copy = new HashSet<>(mySet);

The copy constructor given by #Stephen C is the way to go when you have a Set you created (or when you know where it comes from).
When it comes from a Map.entrySet(), it will depend on the Map implementation you're using:
findbugs says
The entrySet() method is allowed to return a view of the underlying
Map in which a single Entry object is reused and returned during the
iteration. As of Java 1.6, both IdentityHashMap and EnumMap did so.
When iterating through such a Map, the Entry value is only valid until
you advance to the next iteration. If, for example, you try to pass
such an entrySet to an addAll method, things will go badly wrong.
As addAll() is called by the copy constructor, you might find yourself with a Set of only one Entry: the last one.
Not all Map implementations do that though, so if you know your implementation is safe in that regard, the copy constructor definitely is the way to go. Otherwise, you'd have to create new Entry objects yourself:
Set<K,V> copy = new HashSet<K,V>(map.size());
for (Entry<K,V> e : map.entrySet())
copy.add(new java.util.AbstractMap.SimpleEntry<K,V>(e));
Edit: Unlike tests I performed on Java 7 and Java 6u45 (thanks to Stephen C), the findbugs comment does not seem appropriate anymore. It might have been the case on earlier versions of Java 6 (before u45) but I don't have any to test.

Related

Is there any data structure that has no duplicates but can have elements added to it while being iterated over?

I know a set has no duplicates but the issue is that I can't add elements to it while iterating over it using an iterator or for each loop. Is there any other way? Thank you.
The ConcurrentHashMap class can be used for this. For example:
Set<T> set = Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>());
(You can replace <T, Boolean> with <> and let the compiler infer the types. I wrote it as above for illustrative purposes.)
The Collections::newSetFromMap javadoc says:
Returns a set backed by the specified map. The resulting set displays the same ordering, concurrency, and performance characteristics as the backing map. In essence, this factory method provides a Set implementation corresponding to any Map implementation.
Since ConcurrentHashMap allows simultaneous iteration and updates, so does the Set produced as above. The catch is that an iteration may not see the effect of additions or removals made while iterating.
The concurrency properties of iteration can be inferred from the javadoc for ConcurrentHashMap.
Is there any other way.
It depends on your requirements, but there are potentially ways to avoid the problem. For example, you could:
copy the set before iterating it, OR
add the new element to another new set and add the existing elements to the new set to the new set after ... or while ... iterating.
However, these these are unlikely to work without a concurrency bottleneck (e.g. 1.) or a differences in behavior (e.g. 2.)
Not sure whether below approach fixes your problem but you can try it:
HashSet<Integer> original = new HashSet<>();
HashSet<Integer> elementsToAdd = new HashSet<>();
elementsToAdd.add(element); //while iterating original
original.addAll(elementsToAdd); //Once done with iterating, just add all.

Java: How to take static snapshot of ConcurrentHashMap?

Java doc says that return values of method values() and entrySet() are backed by the map. So changes to the map are reflected in the set and vice versa. I don't want this to happen to my static copy. Essentially, I want lots of concurrent operations to be done on my DS. But for some cases I want to iterate over its static snapshot. I want to iterate over static snapshot, as I am assuming iterating over static snapshot will be faster as compared to a version which is being updated concurrently.
Just make a copy, and it wont be changed.
Set<K> keySetCopy = new HashSet<>(map.keySet());
List<V> valuesCopy = new ArrayList<>(map.values());
All collection implementations have a copy constructor which will copy the entire data of the supplied collection to the newly created one, without being backed by the original.
Note: this won't work with entrySet(), as the actual Map Entries will still "belong" to the original Map and changes to the original entries will be reflected in your copies. In case you need the entrySet(), you should copy the entire Map first, with the same technique.
Set<Entry<K,V>> entrySetCopy = new HashMap<>(map).entrySet();
Note that all of these will require a full iteration ONCE (in the constructor) and will only then be static snapshots. There is no way around this limitation, to my knowledge.
Simply make a copy, new HashMap would be independent of the original one.
Set<K> keySetCopy = new HashSet<>(map.keySet());
List<V> valuesCopy = new ArrayList<>(map.values());
However mind that this will take a full iteration over the concurrentStructure, once but will only then be static snapshots. So you will need time equivalent to one full iteration.

Best way to addAll items from a Collection of Sets

What is the best, cheapest, performantest way to achive the following:
I do have a Collection of Objects myObject, which provide a method returning a Set of Integers. I want to add all the items inside the sets into a new set.
LinkedList<myObject> ll = new LinkedList<>();
//fill the list bla bla
Set<Integer> result = ll.stream()
.map(f -> f.getTheSet())
.flatMap(Set::stream)
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(result.toString());
is there a better way of getting a resulting set containing all integers from all objects?
I would like to avoid "unpacking the set" with the flatMap command. Instead i think about something like .addAll or does it in the end not matter, because .addAll unpacks anyway?
You can collect using addAll with a 3-argument collect:
Set<Integer> result = ll.stream()
.map(MyObject::getTheSet)
.collect(HashSet::new, Set::addAll, Set::addAll);
HashSet::new makes a new container, Set::addAll adds each set into the container, 2nd Set::addAll merges two containers if this stream were to be run in parallel.
(or use TreeSet::new if you specifically want a TreeSet)
Instead i think about something like .addAll or does it in the end not matter, because .addAll unpacks anyway?
TreeSet.addAll() has an optimized codepath for adding Collections that implement SortedSet assuming they have the same sort order.
Caveat: This is an undocumented implementation detail and thus subject to change.
You could easily have found this information yourself simply looking at the JDK source code, which is included in the src.zip shipped with the JDK.

java.lang.UnsupportedOperationException when combining two Sets

I have 2 different instances of HashMap
I want to merge the keysets of both HashMaps;
Code:
Set<String> mySet = hashMap1.keySet();
mySet.addAll(hashMap2.keySet());
Exception:
java.lang.UnsupportedOperationException
at java.util.AbstractCollection.add(AbstractCollection.java:238)
at java.util.AbstractCollection.addAll(AbstractCollection.java:322)
I don't get a compile warning or error.
From java doc this should work. Even if the added collection is also a set:
boolean addAll(Collection c)
Adds all of the elements in the specified collection to this set if
they're not already present (optional operation). If the specified
collection is also a set, the addAll operation effectively modifies
this set so that its value is the union of the two sets. The behavior
of this operation is undefined if the specified collection is modified
while the operation is in progress.
If you look at the docs of the HashMap#keySet() method, you'll get your answer(emphasis mine).
Returns a Set view of the keys contained in this map. The set is
backed by the map, so changes to the map are reflected in the set, and
vice-versa. If the map is modified while an iteration over the set is
in progress (except through the iterator's own remove operation), the
results of the iteration are undefined. The set supports element
removal, which removes the corresponding mapping from the map, via the
Iterator.remove, Set.remove, removeAll, retainAll, and clear
operations. It does not support the add or addAll operations.
Therefore, you need to create a new set and add all the elements to it, instead of adding the elements to the Set returned by the keySet().
The result of keySet() does not support adding elements to it.
If you are not trying to modify hashMap1 but just want a set containing the union of the two maps' keys, try:
Set<String> mySet = new HashSet<String>();
mySet.addAll(hashMap1.keySet());
mySet.addAll(hashMap2.keySet());
Doesn't support by nature of Set which is from map.keySet(). It supports only remove, removeAll, retainAll, and clear operations.
Please read documentation
All the above answers are correct. If you still wants to know the exact implementation detail (jdk 8)
hashMap1.keySet() returns a KeySet<E> and
KeySet<E> extends AbstractSet<E>
AbstractSet<E> extends AbstractCollection<E>
In AbstractCollection,
public boolean add(E e) {
throw new UnsupportedOperationException();
}
addAll() calls add() and thats why you are getting an UOException
Just create your own Set with the keys of the Map like this:
Set set = new HashSet(map.keySet());
Then you can add whatever you want to it.

How to copy a java.util.List into another java.util.List

I have a List<SomeBean> that is populated from a Web Service. I want to copy/clone the contents of that list into an empty list of the same type. A Google search for copying a list suggested me to use Collections.copy() method. In all the examples I saw, the destination list was supposed to contain the exact number of items for the copying to take place.
As the list I am using is populated through a web service and it contains hundreds of objects, I cannot use the above technique. Or I am using it wrong??!! Anyways, to make it work, I tried to do something like this, but I still got an IndexOutOfBoundsException.
List<SomeBean> wsList = app.allInOne(template);
List<SomeBean> wsListCopy=new ArrayList<SomeBean>(wsList.size());
Collections.copy(wsListCopy,wsList);
System.out.println(wsListCopy.size());
I tried to use the wsListCopy=wsList.subList(0, wsList.size()) but I got a ConcurrentAccessException later in the code. Hit and trial. :)
Anyways, my question is simple, how can I copy the entire content of my list into another List? Not through iteration, of course.
Just use this:
List<SomeBean> newList = new ArrayList<SomeBean>(otherList);
Note: still not thread safe, if you modify otherList from another thread, then you may want to make that otherList (and even newList) a CopyOnWriteArrayList, for instance -- or use a lock primitive, such as ReentrantReadWriteLock to serialize read/write access to whatever lists are concurrently accessed.
This is a really nice Java 8 way to do it:
List<String> list2 = list1.stream().collect(Collectors.toList());
Of course the advantage here is that you can filter and skip to only copy of part of the list.
e.g.
//don't copy the first element
List<String> list2 = list1.stream().skip(1).collect(Collectors.toList());
originalArrayList.addAll(copyArrayofList);
Please keep on mind whenever using the addAll() method for copy, the contents of both the array lists (originalArrayList and copyArrayofList) references to the same objects will be added to the list so if you modify any one of them then copyArrayofList also will also reflect the same change.
If you don't want side effect then you need to copy each of element from the originalArrayList to the copyArrayofList, like using a for or while loop. for deep copy you can use below code snippet.
but one more thing you need to do, implement the Cloneable interface and override the clone() method for SomeBean class.
public static List<SomeBean> cloneList(List<SomeBean> originalArrayList) {
List<SomeBean> copyArrayofList = new ArrayList<SomeBean>(list.size());
for (SomeBean item : list) copyArrayofList.add(item.clone());
return copyArrayofList;
}
I tried to do something like this, but I still got an IndexOutOfBoundsException.
I got a ConcurrentAccessException
This means you are modifying the list while you are trying to copy it, most likely in another thread. To fix this you have to either
use a collection which is designed for concurrent access.
lock the collection appropriately so you can iterate over it (or allow you to call a method which does this for you)
find a away to avoid needing to copy the original list.
Starting from Java 10:
List<E> oldList = List.of();
List<E> newList = List.copyOf(oldList);
List.copyOf() returns an unmodifiable List containing the elements of the given Collection.
The given Collection must not be null, and it must not contain any null elements.
Also, if you want to create a deep copy of a List, you can find many good answers here.
There is another method with Java 8 in a null-safe way.
List<SomeBean> wsListCopy = Optional.ofNullable(wsList)
.map(Collection::stream)
.orElseGet(Stream::empty)
.collect(Collectors.toList());
If you want to skip one element.
List<SomeBean> wsListCopy = Optional.ofNullable(wsList)
.map(Collection::stream)
.orElseGet(Stream::empty)
.skip(1)
.collect(Collectors.toList());
With Java 9+, the stream method of Optional can be used
Optional.ofNullable(wsList)
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList())
I tried something similar and was able to reproduce the problem (IndexOutOfBoundsException). Below are my findings:
1) The implementation of the Collections.copy(destList, sourceList) first checks the size of the destination list by calling the size() method. Since the call to the size() method will always return the number of elements in the list (0 in this case), the constructor ArrayList(capacity) ensures only the initial capacity of the backing array and this does not have any relation to the size of the list. Hence we always get IndexOutOfBoundsException.
2) A relatively simple way is to use the constructor that takes a collection as its argument:
List<SomeBean> wsListCopy=new ArrayList<SomeBean>(wsList);
I was having the same problem ConcurrentAccessException and mysolution was to:
List<SomeBean> tempList = new ArrayList<>();
for (CartItem item : prodList) {
tempList.add(item);
}
prodList.clear();
prodList = new ArrayList<>(tempList);
So it works only one operation at the time and avoids the Exeption...
You can use addAll().
eg : wsListCopy.addAll(wsList);
re: indexOutOfBoundsException, your sublist args are the problem; you need to end the sublist at size-1. Being zero-based, the last element of a list is always size-1, there is no element in the size position, hence the error.
I can't see any correct answer. If you want a deep copy you have to iterate and copy object manually (you could use a copy constructor).
You should use the addAll method. It appends all of the elements in the specified collection to the end of the copy list. It will be a copy of your list.
List<String> myList = new ArrayList<>();
myList.add("a");
myList.add("b");
List<String> copyList = new ArrayList<>();
copyList.addAll(myList);
just in case you use Lombok:
mark SomeBean with the following annotation:
#Builder(toBuilder = true, builderMethodName = "")
and Lombok will perform a shallow copy of objects for you using copy constructor:
inputList.stream()
.map(x -> x.toBuilder().build())
.collect(Collectors.toList());
subList function is a trick, the returned object is still in the original list.
so if you do any operation in subList, it will cause the concurrent exception in your code, no matter it is single thread or multi thread.

Categories

Resources