0

I have a loop thats constantly setting new values to a few string and looking into it, i thought that i could improve it. At first it was easy (this is how it actually looks) but i am looking for a way to avoid doing that lot of "ToString" and then resetting that string to "".

private void obtenerProfCita(JSONArray catalogoDatos) {
        String areaConsumer = "";
        String resourceConsumer = "";
        String activityConsumerid = "";
        for(int i = 0; i < catalogoDatos.length(); i++) {
            areaConsumer = catalogoDatos.getJSONObject(i).get("area_consumerid").toString();
            resourceConsumer = catalogoDatos.getJSONObject(i).get("resource_consumerid").toString();
            activityConsumerid = catalogoDatos.getJSONObject(i).get("activity_consumerid").toString();
            if(StringUtils.isNotBlank(areaConsumer) && StringUtils.isNotBlank(resourceConsumer) && StringUtils.isNotBlank(activityConsumerid)) {
                log.error("REFERENCIA TUOTEMPO:\nModulo: " + i + "\nCONSUL: " + areaConsumer + "\nIDPROF: " + resourceConsumer);
                TuotempoDAO.guardarActivityId(catalogoDatos.getJSONObject(i));//Guardar en BBDD el Activity_ID
            }
            areaConsumer = "";
            resourceConsumer = "";
        }
    }

As you can see, is a basic for loop but taking in account that the JSONArray is quite big, i was looking for a way to use StringBuilders or StringBuffers and avoid the "ToStrings" in the JSON array.

Actual code with stringBuilders:

private void obtenerProfCita(JSONArray catalogoDatos) {
        StringBuilder areaConsumer = new StringBuilder();
        StringBuilder resourceConsumer = new StringBuilder();
        StringBuilder activityConsumerid = new StringBuilder();
        for(int i = 0; i < catalogoDatos.length(); i++) {
            areaConsumer.append(catalogoDatos.getJSONObject(i).get("area_consumerid").toString());
            resourceConsumer.append(catalogoDatos.getJSONObject(i).get("resource_consumerid").toString());
            activityConsumerid.append(catalogoDatos.getJSONObject(i).get("activity_consumerid").toString());
            if(StringUtils.isNotBlank(areaConsumer) && StringUtils.isNotBlank(resourceConsumer) && StringUtils.isNotBlank(activityConsumerid)) {
                log.error("REFERENCIA TUOTEMPO:\nModulo: " + i + "\nCONSUL: " + areaConsumer + "\nIDPROF: " + resourceConsumer);
                TuotempoDAO.guardarActivityId(catalogoDatos.getJSONObject(i));//Guardar en BBDD el Activity_ID
            }
            areaConsumer.setLength(0);
            resourceConsumer.setLength(0);
        }
    }

Also i have doubts about how efficient is to use StringBuilder instead of String, if someone could explain it to me it would be great.

EDIT. Thanks to OH GOD SPIDERS below, i have improved a little my code. here it´s how it looks right now:

private void obtenerProfCita(JSONArray catalogoDatos) {
        StringBuilder areaConsumer = new StringBuilder();
        StringBuilder resourceConsumer = new StringBuilder();
        StringBuilder activityConsumerid = new StringBuilder();
        for(int i = 0; i < catalogoDatos.length(); i++) {
            areaConsumer.append(catalogoDatos.getJSONObject(i).get("area_consumerid"));
            resourceConsumer.append(catalogoDatos.getJSONObject(i).get("resource_consumerid"));
            activityConsumerid.append(catalogoDatos.getJSONObject(i).get("activity_consumerid"));
            if (StringUtils.isNotBlank(areaConsumer) 
                    && StringUtils.isNotBlank(resourceConsumer) 
                      && StringUtils.isNotBlank(activityConsumerid)) {
                log.error("REFERENCIA TUOTEMPO:\nModulo: " + i + "\nCONSUL: " + areaConsumer + "\nIDPROF: " + resourceConsumer);
                TuotempoDAO.guardarActivityId(catalogoDatos.getJSONObject(i));//Guardar en BBDD el Activity_ID
            }
            areaConsumer.setLength(0);
            resourceConsumer.setLength(0);
            activityConsumerid.setLength(0);
        }
    }

Any help is appreciatted

EDIT2:

Thank you for all the help, i will modify my code taken in account the last comment and answers.

6
  • 1
    The above examples aren't equal and don't necessarily produce the same result. In the first example you just assign the Strings for activityConsumerid new every loop iteration overriding the old values while in the example with a StringBuilder you keep appending to activityConsumerid. The result of those 2 code blocks could therefor be very different. Commented Jun 10, 2022 at 10:02
  • Yeah thanks, good eye, in the String example it wasn´t important to empty my activityConsumerId but with the StringBuilder i will have to do it. Commented Jun 10, 2022 at 10:06
  • 1
    IMHO your "improved" code is worse. Not only does the "improved" code still need to call toString() on the result of (for example) catalogoDatos.getJSONObject(i).get("area_consumerid"), it also needs to copy the contents of that String into the StringBuilder by calling areaConsumer.append(). More method calls and more copying will lead to worse performance. Commented Jun 10, 2022 at 10:21
  • Well... thats why i posted the question, but thanks to @OHGODSPIDERS i was able to improve it because i found that appends doesn´t need the .toString() in my case. I will add an edit to the question with the optimized code. Any help is appreciated Commented Jun 10, 2022 at 10:24
  • 1
    @Grismak It still has to call toString to get to a string representation of your objects, only now the StringBuilder.append methods do it. hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/… Commented Jun 10, 2022 at 10:38

3 Answers 3

3

Your loop is not appending to the strings and therefore using StringBuilder gives no performance improvements.

I would only do two things:

  • move the declaration of the variables into the loop
  • only fetch catalogoDatos.getJSONObject(i) once and store that reference in a local variable

This gives the following code:

private void obtenerProfCita(JSONArray catalogoDatos) {
    for (int i = 0; i < catalogoDatos.length(); i++) {
        JSONObject o = catalogoDatos.getJSONObject(i);
        String areaConsumer = o.get("area_consumerid").toString();
        String resourceConsumer = o.get("resource_consumerid").toString();
        String activityConsumerid = o.get("activity_consumerid").toString();
        if (StringUtils.isNotBlank(areaConsumer) && StringUtils.isNotBlank(resourceConsumer) && StringUtils.isNotBlank(activityConsumerid)) {
            log.error("REFERENCIA TUOTEMPO:\nModulo: " + i + "\nCONSUL: " + areaConsumer + "\nIDPROF: " + resourceConsumer);
            TuotempoDAO.guardarActivityId(o);//Guardar en BBDD el Activity_ID
        }
    }
}

About the idea that StringBuilder.append() somehow works around the need for toString():

What do you think how StringBuilder.append() does append the string representation of an object?

Great surprise: it uses toString() internally.

Actually, if you look at the internals of StringBuilder.append(Object):

public StringBuilder append(Object obj) {
    return append(String.valueOf(obj));
}

it calls String.valueOf(obj) which is

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

Effectively the only difference between

stringBuilder.append(obj);

and

stringBuilder.append(obj.toString());

is that first version correctly handles the case where obj is null whereas in the second case it will throw a NullPointerException.

Sign up to request clarification or add additional context in comments.

2 Comments

thank you this is excellent as reference, will share with my companions
It’s worth noting that the behavior is even specified. append(Object): “The overall effect is exactly as if the argument were converted to a string by the method String.valueOf(Object)”, and String.valueOf(Object): “Returns: if the argument is null, then a string equal to "null"; otherwise, the value of obj.toString() is returned.
3

The difference, when concatenating lots of Strings in a loop is that:

  • concatenating Strings in a loop with e.g. += creates lots of new intermediate Strings
  • StringBuilder doesn't, it collects all the Strings, then builds a new one at the end (actually I think it has an array/array list of chars that gets filled, but the details don't matter here). Less memory used, less copying characters around.

Doesn't matter much for small amounts of Strings, but if your array is big it should be noticeable.

4 Comments

Thanks for the answer. The setLength(0) clean memory in a StringBuilder? if it does, it´s worth it to work with StringBuilders wherever it´s possible instead of strings?
@Grismak I wouldn't rely on it. But it does make is so that what you get out is an empty String. Internally, it only ever expands the internal char array, it never shrinks it: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…
@Grismak If you don't actually need to concatenate the Strings as I understand from the comments, then there is no point in using the StringBuilder. StringBuilder is useful if you need to concatenate lots of stuff.
well, the main point was that this loop can be quite big so i though that in memory having a lot of String a = "" could be troublesome and i wanted to keep the memory clean if possible, but not only your answer helped me to clarify some doubts that i had, i think that being able to avoid .toString everywhere should be noticeable, i will run tests around it to check the best solution
1

stringbuilder create one object but for '+',every time you use will create a new Stringbuilder object and use tostring() once,as the byteCodes show

for test1,loop body contains: one StringBuilder Object init,two append method,one tostring method

for test2,loop body contains: one append method

public class Test6 {
    String test1(){
        String a = "";
        for (int i = 0; i < 10; i++) {
            a += "a";
        }
        return a;
    }
    String test2(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            sb.append("a");
        }
        return sb.toString();
    }
}

test1

 0 ldc #2
 2 astore_1
 3 iconst_0
 4 istore_2
 5 iload_2
 6 bipush 10
 8 if_icmpge 37 (+29)
11 new #3 <java/lang/StringBuilder>
14 dup
15 invokespecial #4 <java/lang/StringBuilder.<init> : ()V>
18 aload_1
19 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
22 ldc #6 <a>
24 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
27 invokevirtual #7 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
30 astore_1
31 iinc 2 by 1
34 goto 5 (-29)
37 aload_1
38 areturn

test2

 0 new #3 <java/lang/StringBuilder>
 3 dup
 4 invokespecial #4 <java/lang/StringBuilder.<init> : ()V>
 7 astore_1
 8 iconst_0
 9 istore_2
10 iload_2
11 bipush 10
13 if_icmpge 29 (+16)
16 aload_1
17 ldc #6 <a>
19 invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
22 pop
23 iinc 2 by 1
26 goto 10 (-16)
29 aload_1
30 invokevirtual #7 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
33 areturn

2 Comments

Low quality answer. You should provide code samples to illustrate your point. A high quality answer would show StringBuilder impl details to support your claim
@Chris Maggiulli as you wish , i didnt post code is because "+" do not have code,so this is byteCode

Your Answer

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.