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;
        }
    }
}
clwen
  • 20,004
  • 31
  • 77
  • 94
  • 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 Aug 07 '14 at 00: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 Aug 07 '14 at 00:44
  • I think you are looking for something like http://stackoverflow.com/a/19710021/348975 – emory Aug 07 '14 at 00:53

4 Answers4

4

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.

Mshnik
  • 7,032
  • 1
  • 25
  • 38
2

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.

Community
  • 1
  • 1
Brett Okken
  • 6,210
  • 1
  • 19
  • 25
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.

11th Hour Worker
  • 337
  • 3
  • 14
  • 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. – emory Aug 07 '14 at 00: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 Aug 07 '14 at 00: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. – Scary Wombat Aug 07 '14 at 00: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 Aug 07 '14 at 00: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

Nuno Pinheiro
  • 320
  • 2
  • 8
  • 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. – Brett Okken Aug 07 '14 at 01:37