views:

284

answers:

11

Ok, so I'm tyring to iterate through an ArrayList and remove a specefic element. However, I am having some trouble using the For-Each like structure. When I run the following code:

ArrayList<String> arr = new ArrayList<String>();
//... fill with some values (doesn't really matter)

for(String t : arr)
{
  t = " some other value "; //hoping this would change the actual array
}

for(String t : arr)
{
  System.out.println(t); //however, I still get the same array here
}

My question in, how can I make 't' a pointer to 'arr' so that I am able to change the values in a for-each loop? I know I could loop through the ArrayList using a different structure, but this one looks so clean and readable, it would just be nice to be able to make 't' a pointer.

All comments are appreciated! Even if you say I should just suck it up and use a different construct.

A: 

Strings are immutable. If you had a mutable type like StringBuilder/Buffer, you could change the string in your iteration. You do have references, remember.

Stefan Kendall
This has nothing to do with `String` or `StringBuilder` or immutability, this is about how object references work in Java.
skaffman
this answer is 100% wrong...
TofuBeer
The code assigns to local variable `t`, this won't affect the reference inside the ArrayList. To change that you need to call arr.set() as per MB's answer below.
SimonJ
...what? Iterating through an ArrayList<StringBuilder> will certainly let you change the object inline, as per the OP's defined specification. He said he wanted style, not exact pointer functionality.You're all stooges.
Stefan Kendall
People, the immutability point is because of OPs comment "//hoping this would change the actual array". So this answer isn't any worse than the highly voted MB's answer that replaces object in the array element's position with a new object.
Murali VP
@Stefan You cannot change the reference which is what the question was asking though. StringBuilder = is going to be the same as String =. Your suggestion may actually be dangerous as it could modify a variable outside of the list.
TofuBeer
@Tofu: Did you even read the OP's question? He asked about changing VALUE, not pointer reference. There are plenty of situations in which a variable is placed in a list which is then modified en masse. It's not "potentially dangerous." It's "functions as designed."
Stefan Kendall
@TofuBeer Stefan is correct, you wouldn't assign but use StringBuilder's replace method. So you are saying one cannot modify an arraylist element at all? And, what do you mean "modify outside of the list"? You cannot modify a mutable object inside of the list anyway (whatever that means). This IS the right answer to OP's question, but I guess not many people think like I think.
Murali VP
I didn't say you cannot modify the entry, I said you cannot replace it with an assignment. This solution only works for mutable items, and clearly the example given was a replacement not a modification.
TofuBeer
+1  A: 

For-each doesn't give you an index pointer, so you just can't use it to change an immutable value.

Either use a for-loop with an index or use a mutable type (like StringBuffer, not String)

Bill K
+13  A: 

I think the best approach may be to use a for loop.

    ArrayList<String> arr = new ArrayList<String>();

    for (int i = 0; i < arr.size(); i++) {

        String t = arr.get(i);

        if (// your condition is met) {
            arr.set(i, "your new value");
        }
    }
Michael Bobick
Does it work? No ConcurrentModificationException this way?
Andreas_D
I think you'll only get that exception if you add/remove items from the array while iterating
Don
Yes, sure, you're not using the iterator anymore, just a normal for loop. Didn't pay attention ;)
Andreas_D
I guess it is better to put arr.size() in a local variable instead of calculating it every time over and over again.
Alfred
The JIT compiler will most likely hoist the size() invocation out of the loop.
Steve Kuo
size() will simply return the .length of the backing array... and, as Steve points points out, that will likely be inlined at runtime. In the case of something like Android where there is no JIT making a temp variable will have an improvement.
TofuBeer
size() does not return the length of the backing array. You might be thinking of Arrays.asList().
Kevin Bourrillion
A: 

Basically you want to remove the String t from the list arr. Just do a arr.remove(t) and you could be done. But you can't do it while iterating over the same list. You'll get an Exception if you try to modify the list this way.

You have two options:

  1. clone your list, iterate through the clone and remove the 'specific' String from the original list
  2. create a list for delete candidates, add all 'specific' Strings to that list and, after iterating through the original list, iterate through the wastebin and remove everything you've collected here from the original list.

Option 1 is the easist, the clone can be made like:

List<String> clone = new ArrayList<String>(arr);
Andreas_D
A: 

An array of objects (like strings) in Java is a contiguous block containing an ordered series of references. So, when you have an array of 4 strings, what you really have is 4 references stored IN the array, and 4 string objects that are outside of the array but are referenced by its 4 elements.

What the for-each construct in Java does is create a local variable and, for each iteration, copy into that local variable the reference from the array cell that corresponds to that iteration. When you set the loop variable (t = " some other value") you are putting a reference to a new string, "some other value", into the local variable t, not into the array.

The contrasts with some other languages (like Perl) where the loop variable acts like an alias to the array/list element itself.

Nate C-K
A: 

You seem to misunderstand how objects/references work in Java, which is pretty fundamental to using the language effectively. However, this code here should do what you want (apologies for the lack of explanation):

ArrayList<String> arr = new ArrayList<String>();
//... fill with some values (doesn't really matter)

for(int i = 0; i < arr.size(); i++)
{
  arr.set(i, " some other value "); // change the contents of the array
}

for(String t : arr)
{
  System.out.println(t); 
}
Don
+3  A: 

The problem is that you're trying to change the loop-scoped reference t to let it point to a new String instance. This ain't going to work. It does not refer the actual entry in the arraylist. You need to change the actual value of the reference. If String was mutable and provided a fictive set() method for that, you could in theory do

for (String t : arr) {
    t.set("some other value");
}

or so, but that's not possible as it is immutable. Better get a handle of the entrypoint in the array itself using the normal for loop:

for (int i = 0; i < arr.size(); i++) {
    arr.set(i, "some other value");
}

If you insist in using the enhanced for loop, then you need to replace String by StringBuilder, which is mutable:

for (StringBuilder t : arr) {
     t.delete(0, t.length()).append("some other value");
}

Remember, Java is pass-by-value, not pass-by-reference.

BalusC
Even worse - his first sentence tells us that he wants to remove something (makes sense, seems to be the actual assignement for the example.buab-user-group ;) ) but his code shows a replacement... this is going to be a loooooong night :)
Andreas_D
Wtf, he wanted to **remove** it. There you need the holy Iterator for.
BalusC
A: 

Your code is re-written by the compiler as something like this:

ArrayList < String > arr = new ArrayList < String > (); //... fill with some values (doesn't really matter)

for(final Iterator < String > i = arr.iterator(); i.hasNext();) { String t;

 t = i.next();
 t = " some other value "; // just changes where t is pointing

}

To do what you want you would have to write the for loop like this:

for(final ListIterator < String > i = arr.iterator(); i.hasNext();)
{
     final String t;

     t = i.next();
     i.set("some other value");
}

Iterator does not have the set method, only ListIterator does.

TofuBeer
A: 

I believe, this is not related to immutable or mutable.

 t = " some other value "; //hoping this would change the actual array

t does not hold the reference to actual object. Java copies the value from arraylist and puts that value into t so array list value does not get affect.

HTH

Thillakan
that is actually more along the lines of what I was thinking originally. I was wondering if I could get a reference to the object, but since it's immutable I guess it doesn't matter anyways.
John
A: 

This has been answered well. Still here is my suggestion. The var t inside loop is only visible there. It will not be seen outside the loop. You could do t.set() if it was not String.

fastcodejava
A: 

Use a StringBuffer rather than plain strings. This way the string within it is mutable.

PL