Pretty new to Java here. I am coming from Python. There are similar questions on SO talking about remove or add element while iterating a Java Set
. What I would like to know is to modify the elements containing in the Set
. For instance, ["apple", "orange"]
to ["iapple", "iorange"]
. In addition, I would like to do it in place, i.e., not creating another set and put the modified element into the new set while iterating it.
Apparently a simple for loop doesn't work, as the following:
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
class Test {
public static void main (String[] args) {
Set<String> strs = new HashSet<String>(Arrays.asList("apple", "orange"));
for (String str : strs) {
str = "i" + str;
}
}
}
-
The functional approach would be to create a new set by applying a transformation function to the original set. The new set could be just a reference to the original set and the transformation function -- technical in place.emory– emory2014年08月07日 00:11:33 +00:00Commented Aug 7, 2014 at 0:11
-
@emory Yap. I thought of FP approach. My impression is that Java is not particularly suitable for FP. Do you mind providing code example?clwen– clwen2014年08月07日 00:44:29 +00:00Commented Aug 7, 2014 at 0:44
-
1I think you are looking for something like stackoverflow.com/a/19710021/348975emory– emory2014年08月07日 00:53:36 +00:00Commented Aug 7, 2014 at 0:53
5 Answers 5
The issue with what you've written is you don't do anything with the computed value. The for-each loop sets the value of str to each element. Within the for loop you are changing the value of str, but not doing anything else with it.
This would be easy to do in a linkedlist or any data structure which supports indexing, but with a set it can be tricky. Just removing the old element and adding the new one will likely screw up the iteration, especially because you're dealing with a hash set.
A simple way to do this is to convert to a list and back:
class Test {
public static void main (String[] args) {
Set<String> strs = new HashSet<String>(Arrays.asList("apple", "orange"));
//Convert to list
LinkedList<String> strsList = new LinkedList<String>();
strsList.addAll(strs);
//Do modification
for (int i = 0; i < strsList.size(); i++) {
String str = strsList.get(i);
strsList.set(i,"i" + str);
}
//Convert back to set
strs.clear();
strs.addAll(strsList);
}
}
This is clearly a bit more work than you would expect, but if mass-replacing is behavior you anticipate then probably don't use a set. I'm interested to see what other answers pop up as well.
You cannot modify a String
in java, they are immutable.
While it is theoretically possible to have mutable elements in a Set
and mutate them in place, it is a terrible idea if the mutation effects hashcode (and equals).
So the answer to your specific question is no, you cannot mutate a String
value in a Set
without removing then adding entries to that Set
.
The problem is that in the for loop, str is merely a reference to a String. References, when reassigned, don't change the actual object it refers to. Additionally, strings are immutable anyway, so calling any method on them will give you a new String instead of modifying the original. What you want to do is store all the new Strings somewhere, take out the old ones, and then add the new ones.
EDIT: My original code would have thrown a ConcurrentModificationException.
-
1I think this will throw a ConcurrentModificationException because java does not like it when you add/remove items from a collection it is iterating over.emory– emory2014年08月07日 00:06:35 +00:00Commented Aug 7, 2014 at 0:06
-
Yap. I know why for-loop doesn't work, that's why I said "apparently it doesnt work". :) Just curious, why does it got downvoted? EDIT: got it. Because of ConcurrentModificationException.clwen– clwen2014年08月07日 00:07:28 +00:00Commented Aug 7, 2014 at 0:07
-
str is merely a reference to a String
- what does this mean? if you used some other object thenobj is a reference to a Object
and this has got nothing to do withString
being immutable.Scary Wombat– Scary Wombat2014年08月07日 00:12:03 +00:00Commented Aug 7, 2014 at 0:12 -
Sorry I wasn't clear. I really just meant to point out reassigning references does only that -- it changes the reference but not the object the reference referred to. For example, Integer a = new Integer(5); Integer b = a; Integer a = new Integer(4); Now a = Integer(4) but b still equals Integer(3). I mentioned Strings being immutable as another separate reason the for-loop wouldn't work without calling operations on the set.11th Hour Worker– 11th Hour Worker2014年08月07日 00:17:57 +00:00Commented Aug 7, 2014 at 0:17
There are 3 problems in your approach to solve the problem.
1st - You can't modify the contents of a Set while you are iterating it. You would need to create a new Set with the new values.
"But I'm not modifying the set, I am modifying the objects within it", which leads to problem two
2nd - In your code you are modifying a reference to a string, not the string itself.
To modify the string you would need to call a method over it, like string.changeTo("this")
, or modify a field, string.value = "new value"
.
Which leads to problem three.
3rd - Strings are immutable. when you construct a string, say new String("hello")
, you can't further modify it's inner value.
The solutions:
First option is the simpler one, create a new set.
The second option is to use string builders instead of strings, which are mutable string creators/placeholders.
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html
-
1Mutating a
StringBuilder
instance within aSet
could lead to unpredictable results. Examples arecontains
returning incorrect data, duplicates present in set,remove
not working, etc.Brett Okken– Brett Okken2014年08月07日 01:37:10 +00:00Commented Aug 7, 2014 at 1:37 -
"You can't modify the contents of a Set while you are iterating it" where is the source of that statement?Manuel Jordan– Manuel Jordan2025年08月13日 00:02:19 +00:00Commented Aug 13 at 0:02
One of the things that makes this more complicated for a Set
in particular is that you might change a value to be the same as one in the Set already.
What do you do in that case?
- Do you skip the next occurrence of the updated value - it has already been processed, after all.
- Do you see all of the original values? That would mean you'd have to hold on to all of the original values and the updated values, and do some sort of consolidation at the end.
- (some other way I'm failing to imagine)
The most straightforward way to do the second way in Java 8+ is to copy the Set into a List, use the replaceAll
method on the List (which doesn't care about duplicate values), and then copy the list back into the Set:
List<String> list = new ArrayList<>(set);
list.replaceAll("i"::concat);
// or
// List<String> list = set.stream().map("i"::concat).collect(toList());
set.clear();
set.addAll(list);
The replaceAll
method doesn't exist on Set
, possibly for the ambiguity reasons I have alluded to above.