Modifying elements while iterating Java Set - java

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 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.

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

Related

How to avoid unintended Hash-Key manipulation

I noticed that adding an element to a list does change its hash-key value and therefore it cannot be accessed again since .contains(modifiedObject) won't get a collision here. I did not expect that behavior to be honest. Makes me wonder how HashSet does its hashing .. So how can I make sure to not destroy my HashSet as I modify e.g. a list of strings as shown below. Is there a method to do that safe or is that just something I have to look out as a programmer?
private HashSet<List<String>> bagOfWordsMap = new HashSet<List<String>>();
private void createBagOfWordsList(UnifiedTag[] invalidTags) {
for(List<String> sentences : getSentenceList()) {
List<String> sentenceStemWords = new ArrayList<String>();
// Not what you would want to do since sentenceStemWords is
// modified right after and bagOfWordsMap.contains(sentenceStemWords)
// won't collide again:
// bagOfWordsMap.add(sentenceStemWords);
for(String word : sentences) {
String stem = Stemmer.getStem(word);
sentenceStemWords.add(stem);
}
bagOfWordsMap.add(sentenceStemWords);
}
}
Never use a mutable object as key in a map or set
Implement a frozen type that cannot be modified anymore if you want to prevent accidential modification!
fine print: it's technically okay to have mutable attributes on an object if they don't change the key, but you won't be able to access them easily by key in a java set, as there is no HashSet.get to get the current member, only a contains. Also, it's bad style and fragile. It's much better to split such objects into key, value.
One way is to use an UnmodifiableList<String> instead of a List<String> in your HashSet.
Another option is to use a HashMap<String,List<String>> instead of your HashSet<List<String>>, provided that you can associate some unique String key with each of your Lists.

Java list set method

Okay, I have had a question, my code has seemed to work, but I have not tested it particuarly well.
I'm trying to set an element in an ArrayList.
ArrayList<StringBuilder> g=new ArrayList<StringBuilder>();
//set the array contents
g.get(2).append("Something");
I know that doing something like
StringBuilder q=g.get(2);
q.append("something else?");
g.set(2,q);
works, and is probably the right way to do it, but it seems like such long way of doing it.
Am I doing this right, if I'm not, then is the second way I've mentioned the only way?
If you have a list of StringBuilder, which are modified in place, then the first method is fine. If you have a list of some immutable type (like String), then since you cannot change the object that you get out, you have to use a variant of the second method, because the result is not the same object.
e.g.
List<StringBuilder> widgets = new ArrayList<StringBuilder>();
// ...
StringBuilder widget = widgets.get(0);
widget.append(" version 2");
vs
List<String> widgets = new ArrayList<String>();
// ...
String widget = widgets.get(0);
widgets.set(2, widget + " version 2");
When calling List#get() method you are retrieving a reference to your StringBuilder object. StringBuilder is a mutable object. So if you want to modify your StringBuilder's contents at index 2 then you don't need to set a reference again after you modify it. Hence the following is enough:
g.get(2).append("Something");
g.get(2).append("Something"); works if you initialized the list values with StringBuilder instances. Otherwise you will get a NullPointerException.
Note that in your code you define an ArrayList of type StringBuilder but then you try to assign an ArrayList of type Integer to it, which does not work

Input stored in Arraylist or String array? (New programmer) Java

It seems to me like ArrayList would be easier to use in nearly every scenario, it being very versatile. Is there an instance where a String[] would be used to store inputted data? If there is such a case, there must be a drawback in ArrayList, what would that be?
Only thing that comes to mind off the top of my head would be the variety of String methods like, substring() and split(), etc.
EDIT: New to StackOverflow as well. I apologize if this was a re-post. And thanks for the formatting.
The short answer is: don't use an array, always use an array list. The only exception to this is if you absolutely need to control the amount of memory used or you have some specific performance constraint that only String[] can support.
In general, though, arrays are terrible to work with in an object oriented language, and almost always for the same reason: they make it very easy to break encapsulation. For example:
public class ExampleArray {
private final String[] strings;
public ExampleArray(String... strings) { this.strings = strings; }
public String[] getStrings() { return strings; }
}
See any problems? Yea, you need to write getStrings() like this:
// ...
public String[] getStrings() { return Arrays.copy(strings); }
// ...
Otherwise, some caller can get a hold of your class' internal data and start modifying it. The only way to prevent this is to copy it every time a caller wants to see it. As opposed to the right way:
public class ExampleList {
private final List<String> strings;
// ...
public List<String> getStrings() { return Collections.unmodifiableList(strings); }
}
Now you're not copying the data, you're just sticking it behind an API that doesn't allow changes. If you use the Guava Immutable* classes, even better.
Of course there are situations where you want to use String[] instead. If you know in advance how long the list will be, for instance, and the list might be large.
The main disadvantage of using ArrayList is that as the list grows, the array has to be reallocated, which isn't a free operation. You can mitigate that by preallocating it to be the size (or larger) you expect using the constructor that accepts an initialCapacity.
ArrayList is dynamic grow-able array in size, Where as string array or any type of array is static in size.
Obviously this dynamic grow features cause some cost, it reallocate the array with new size and copy element to it.
You can initialize Java arrays at compile time, like:
String data[] = { "a", "b", "c" };
In old versions of Java there was also the case for type safety. ArrayList elements had to be casted to the original type whereas Java arrays where type safe.
Java arrays are part of the language and you will not be able to change them. ArrayList is part of the Java API. If you need (I do not recommend it though) you could substitute your own library to implement the ArrayList job.
Check out these questions asked by others in stackoverflow:
Array or List in Java. Which is faster?
Java Performance - ArrayLists versus Arrays for lots of fast reads
The only case that comes to my mind when array is used to hold some values is when there's a method taking variable number of arguments like:
void doSth(int i, String... strings){
if(strings.length>0){
//some stuff
}
Otherwise I hardly ever intentionally create a situation, when array needs to be used.

Can I make the name of a string change each time a loop repeats?

Basically I need a different string created each time a loop repeats. I need a new string each time the loop repeats so I can store a whole bunch of different names and recall them as needed.
Thanks for any help I can get, I know its a little vague sorry bout that.
Use the java Collections framework:
List<String> strings = new ArrayList<String>();
while(someCondition()) {
// Call add() as much as you like - the List will grow as needed
strings.add("some new string"); // create whatever String you like
}
Now variable strings contains a bunch of String objects.
Later, you can iterate over the strings to do something with them:
for(String string : strings) {
System.out.println(string); // or whatever
}
Create an array or a list outside the loop and add a new string every time.
I guess you'll have to store your strings in a Map, or similar.
What you need is an array.
An array is like a list of variables. So you can make a new array called mystring, and then mystring[0], mystring[1], mystring[2] are all different variables. In your loop you can use your index value i to do mystring[i] and get a new variable for each time the loop repeats.
http://download.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html

Use of contains in Java ArrayList<String>

If I have an ArrayList of String forming part of a class in Java like so:
private ArrayList<String> rssFeedURLs;
If I want to use a method in the class containing the above ArrayList, using ArrayList contains to check if a String is contained in this ArrayList, I believe I should be able to do so as follows:
if (this.rssFeedURLs.contains(rssFeedURL)) {
Where rssFeedURL is a String.
Am I right or wrong?
You are right. ArrayList.contains() tests equals(), not object identity:
returns true if and only if this list
contains at least one element e such
that (o==null ? e==null : o.equals(e))
If you got a NullPointerException, verify that you initialized your list, either in a constructor or the declaration. For example:
private List<String> rssFeedURLs = new ArrayList<String>();
Yes, that should work for Strings, but if you are worried about duplicates use a Set. This collection prevents duplicates without you having to do anything. A HashSet is OK to use, but it is unordered so if you want to preserve insertion order you use a LinkedHashSet.
You are right that it should work; perhaps you forgot to instantiate something. Does your code look something like this?
String rssFeedURL = "http://stackoverflow.com";
this.rssFeedURLS = new ArrayList<String>();
this.rssFeedURLS.add(rssFeedURL);
if(this.rssFeedURLs.contains(rssFeedURL)) {
// this code will execute
}
For reference, note that the following conditional will also execute if you append this code to the above:
String copyURL = new String(rssFeedURL);
if(this.rssFeedURLs.contains(copyURL)) {
// code will still execute because contains() checks equals()
}
Even though (rssFeedURL == copyURL) is false, rssFeedURL.equals(copyURL) is true. The contains method cares about the equals method.
Perhaps you need to post the code that caused your exception. If the above is all you have, perhaps you just failed to actually initialise the array.
Using contains here should work though.
Your question is not very clear.
What's your code exactly doing? Give more code.
What's the error you're getting?
You say you get a null-pointer. You cannot get a null pointer as a value returned by contains().
However you can get a NullPointerException if your list has not been initialized. By reading your question now, I'd say that what you show here is correct, but maybe you just didn't instantiate the list.
For this to work (to add a feed URL if it isn't already in the list):
if (!this.rssFeedURLs.contains(rssFeedURL)) {
this.rssFeedURLs.add(rssFeedUrl);
}
then this declaration would do:
private ArrayList<String> rssFeedURLs = new ArrayList<String>();
or initialize your list later on, but before trying to access its methods:
rssFeedUrls = new ArrayList<String>();
Finally... Do you really need a List? Maybe a Set would be better if you don't want duplicates. Use a LinkedHashSet if preserving the ordering matters.
Right...with strings...the moment you deviate from primitives or strings things change and you need to implement hashcode/equals to get the desired effect.
EDIT: Initialize your ArrayList<String> then attempt to add an item.
You're correct. As others said according to your comments, you probably did not initialize your ArrayList.
My point is different: you claimed that you're checking for duplicates and this is why you call the contains method. Try using HashSet. It should be more efficient - unless you need to keep the order of URLs for any reason.
Thanks to you all for answering so quickly. I could always use a set but I have the ArrayList working now. The problem was that in the constructor of the class containing the ArrayList, I was not saying:
public RSS_Feed_Miner() {
...
this.rssFeedURLs = new ArrayList<String>();
...
}
D'Oh! for a Friday afternoon.
ArrayList<String> newlyAddedTypes=new ArrayList<String>();
.....
newlyAddedTypes.add("test1");
newlyAddedTypes.add("test1");
newlyAddedTypes.add("test2");
if(newlyAddedTypes.contain("test"){
//called here
}
else{
}

Categories

Resources