anon_swe
anon_swe

Reputation: 9355

Repeatedly updating string in Java

So I understand that strings in Java are immutable. I'm interested in how to repeatedly update a certain string.

Ex:

public static void main(String[] args) {
    String myString = "hey";
    for (int i = 1; i <= 9; i++) {
        myString += "hey";
    }
}

Now, this won't work in Java because I've already declared and assigned myString. How do people get around Java's immutable strings (as in the above example)?

The only thing I can think to do is declare another string. Unfortunately, this just delays my problem, as the second time through the loop, I'll be reassigning an already assigned string:

public static void main(String[] args) {
    String myString = "hey";
    String secondString;
    for (int i = 1; i <= 10; i++) {
        secondString += "hey";
    }
}

Any suggestions / explanations are much appreciated!

Thanks, Mariogs

Upvotes: 2

Views: 11476

Answers (6)

indivisible
indivisible

Reputation: 5012

A Quick Answer

You should use a StringBuilder for this sort of thing. It is designed to put Strings together without constantly copying or holding on to older Strings.

public class SimpleGrowingString {
    private StringBuilder stringBuilder;

    public SimpleGrowingString() {
        this.stringBuilder = new Stringbuilder();
    }

    public void addToString(String str) {
        this.stringBuilder.append(str);
    }

    public String getString() {
        return this.stringBuilder.toString();
    }
}

A Not So Quick Answer:

Immutable?

While Strings are immutable, you can re-assign a String variable.
The variable will then reference (point to) the new String assigned to it and the old value will be marked for Garbage Collection and only hang about in RAM until the Garbage Collector gets off its arse and around to clearing it out. That is, as long as there are no other references to it (or subsections of it) still about somewhere.

Immutable means that you cannot change a String itself not that you cannot reassign what the variable that was pointing to its value now is.


eg.

String str = "string one";

The String "string one" exists in memory and can not be changed, modified, cut up, added to etc.
It is immutable.

If I then say:

str = "a different string";

Then the variable str now references a different piece of data in memory; the String "a different string".

The original String "string one" is still the exact same String that it was before we've just told the handle we had for it to point to something else. The old String is still floating around in memory but now it's headless and we no longer have any way to actually access that value.
This leads to the idea of Garbage Collection.


Garbage. Garbage Everywhere.

The Garbage Collector runs every now and again and cleans out old, unnecessary data that's no longer being used.
It decides what is and isn't useful, among other ways, by checking if there are any valid handles/variables currently pointing at the data. If there's nothing using it and there's no way for us to even access it anymore it's useless to us and it gets dumped.

But you can't really ever rely on the Garbage Collector to clean out thing on time, quickly or even get it to run when you want it to. It does its own thing, in its own time. You are better off trying to minimise its workload than assuming it's going to clean up after you all the time.

And now that you have, an admittedly very basic, grounding in Garbage Collection we can talk about why you don't add Strings together:


String con+cat+en+a+tion

One big issue with using + for Strings (and the reason that StringBuilder and StringBuffer were designed) is that it creates an awful lot of Strings. Strings all over the place! They may end up as candidates for Garbage Collection relatively quickly depending on your usage, but they still can lead to bloat if handled incorrectly, especially when loops get involved, as the Garbage Collector runs whenever it damn well feels like and we can't really control that we can't say that things are not getting out of hand unless we ourselves stop them getting that way.

Doing the simple String concatenation of:

"a String" + " another String"

actually leads to having three Strings in memory:

"a String", " another String" and "a String another String"

Expand this out to a simple, fairly short loop and you can see how things can get out of hand pretty quickly:

String str = "";
for (int i=0; i<=6; i++) {
    str += "a chunk of RAM ";
}

Which at each loops means we have in memory:

0:
  "a chunk of RAM "

1:
  "a chunk of RAM "
  "a chunk of RAM a chunk of RAM"

2:
  "a chunk of RAM "
  "a chunk of RAM a chunk of RAM"
  "a chunk of RAM a chunk of RAM a chunk of RAM"

3: 
  "a chunk of RAM "
  "a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "

4:
  "a chunk of RAM "
  "a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM"

5:
  "a chunk of RAM "
  "a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM"
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "

6:
  "a chunk of RAM "
  "a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM"
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "
  "a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM a chunk of RAM "

And so on and so on... You can see where that's going and how quickly it's getting there.


Moral of the story

If you are looping with and combining Strings use a StringBuilder or StringBuffer it's what they were made for.


"Sidenote".substring(4);

Concatenating Strings isn't the only way to end up with a lot of wasted RAM because of String's immutability.

Just as we can't add to the end of a String neither can we chop off the tail.

String moderatelyLongString = "a somewhat lengthy String that rambles on and on about nothing in particular for quite a while even though nobody's listening";

If perhaps we wanted to make use of only a part of this String we could use the String.substring() method:

String snippedString = moderatelyLongString.substring(0, 13);
System.out.println(snippedString):

>> a somewhat le

Ok, so what's wrong with that?
Well, if we wanted to dump the first, long String but hang onto the short bit you might think that we can just say:

moderatelyLongString = null;

You may think that will leave the long String abandoned and alone, crying in a corner waiting for the GC to come and kick it out into the cold but you'd be wrong.

Since we've still got a hold of a couple of characters of the longer chain in snippedString the entire moderatelyLongString stays on the heap wasting space.

If you wanted to do this but shed the useless weight what you would want to do is copy the shortened part but not retain a tie to the lengthy bit:

String aNicerMorePoliteShortString = new String(moderatelyLongString.substring(0, 13));

This makes a copy of the short String taken from the long that is its own stand alone array of characters and has nothing to do with that pestering hanger-on that is the long String.

Now doing this will, this time, mark the long String as available for Collection as we have no remaining ties to it:

moderatelyLongString = null;

However

If you just wanted to display a single, different String in a loop on every iteration what you want to do is just (re)use a single variable so that all of the older Strings in memory get released as soon as possible and become available for Garbage Collection as quickly as they can be. Declare your variable outside of the loop and then reassign it inside on every iteration:

String whatYouWantToUse;
for (int i=0; i<100; i++) {
    whatYouWantToUse = someStringyGettyMethod();
    howYouWantToUseIt(whatYouWantToUse);
}

Each time this loop loops it is assigning a new value to the variable which throws the older value onto the pile of waste for the Garbage Collector to clean up in time, or, you know, whenever it could be bothered to...

Arguably, a better way to do the above method is to never try to hold onto the String at all — just pass it straight though from where we get it from to where it's wanted:

for (int i=0; i<100; i++) {
    howYouWantToUseIt(someStringyGettyMethod());
}

But watch out for over optimising this sort of thing as readability is almost always more important than compactness.
Most compilers are smarter than we'll ever be, or than I will be at least. They can find all the great shortcuts and minifications that can be done to your code and apply their wizardry in a more magnificent way than we mortals could hope to achieve.

If you try to streamline your code too much then all you're left with is two varieties of unreadable code instead of one useful, fast and optimised version and the other maintainable and something Johnny with the off-putting habit of sniffling every 25 seconds two desks over can follow.

Upvotes: 19

NMO
NMO

Reputation: 776

Your code works perfectly fine. Although its not recommended to work on strings like you do. Have a look at Java's StringBuilder: http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html

With the aid of a StringBuilder, you can modify the string.

Upvotes: 0

Matt Ryall
Matt Ryall

Reputation: 10585

While Strings in Java are immutable, your first example above will work because it creates a new String object every time through the loop, and assigns the newly created string to myString:

public static void main(String[] args) {
    String myString = "hey";
    for (int i = 1; i <= 9; i++) {
        myString += "hey";
    }
    System.out.println(myString); // prints "heyheyheyheyheyheyheyheyheyhey"
}

While this works, it's inefficient due to the object creation. For a loop with more iterations, or when concatenating longer strings, performance might be a concern – so there are better ways to do it.

One better approach to concatenating Strings in Java is to use StringBuilder. Here's your code adapted to use StringBuilder:

public static void main(String[] args) {
    StringBuilder builder = new StringBuilder(50); // estimated length
    for (int i = 1; i <= 9; i++) {
        builder.append("hey");
    }
    String myString = builder.toString();   // convert to String when needed
    System.out.println(myString);           // prints "heyheyheyhey..."
}

StringBuilder has a backing array as a buffer, which is expanded whenever the appended length exceeds the size of the buffer. In this case, we start with an initial allocation of 50 characters.

In a real world situation, you could set the initial size of the StringBuilder buffer based on the size of the input, to minimise the need for expensive buffer expansions.

Upvotes: 1

Ed Morales
Ed Morales

Reputation: 1037

Being immutable doesnt mean that it wont work, it just means that the object you created wont be modified.. but the String variable can be assigned to another object (the new string created by concatenating the previous strings on += "hey").

If you want to do it like a mutable object, then just use StringBuilder append() method.

Upvotes: 2

Christophe De Troyer
Christophe De Troyer

Reputation: 2922

When you execute the working code in your question, you will simply create a new string in memory each time you append to it.

This means that every time you append something to your string it will be a new string object in memory, implying that it also has a new memory address.

This is because strings are immutable indeed.

If you only want to create a string object once, you should use a StringBuilder, otherwise this solution works fine.

StringBuilders are recommended for building string that you will - as you do - modify a lot. Because modifying a string a lot (i.e., creating many new strings) does a lot of reading and writing in your memory.

Upvotes: 0

Rod_Algonquin
Rod_Algonquin

Reputation: 26198

this won't work in Java because I've already declared and assigned myString

You are wrong, it will still work but each time you append to the string it will generate a new string.

If you dont want to generate new string when you append/add to it then StringBuilder is the solution.

sample:

public static void main(String args[]) {


     StringBuilder sb = new StringBuilder("hey");
        for (int i = 1; i <= 9; i++) {
            sb.append("hey");
        }

}

Upvotes: 2

Related Questions