0

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;
 }
 }
}
Manuel Jordan
16.5k25 gold badges113 silver badges186 bronze badges
asked Aug 6, 2014 at 23:57
3
  • 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. Commented 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? Commented Aug 7, 2014 at 0:44
  • 1
    I think you are looking for something like stackoverflow.com/a/19710021/348975 Commented Aug 7, 2014 at 0:53

5 Answers 5

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.

answered Aug 7, 2014 at 0:06
3

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.

answered Aug 7, 2014 at 0:04
0

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.

answered Aug 7, 2014 at 0:05
4
  • 1
    I think this will throw a ConcurrentModificationException because java does not like it when you add/remove items from a collection it is iterating over. Commented 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. Commented Aug 7, 2014 at 0:07
  • str is merely a reference to a String - what does this mean? if you used some other object then obj is a reference to a Object and this has got nothing to do with String being immutable. Commented 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. Commented Aug 7, 2014 at 0:17
0

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

answered Aug 7, 2014 at 0:39
2
  • 1
    Mutating a StringBuilder instance within a Set could lead to unpredictable results. Examples are contains returning incorrect data, duplicates present in set, remove not working, etc. Commented 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? Commented Aug 13 at 0:02
0

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.

answered Aug 13 at 16:30

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.