Creating unnecessary objects leads to memory waste, especially when using primitive wrapper classes like String, Boolean, or regular expression patterns.
Minimize unnecessary object creation by using String literals, utilizing Boolean.valueOf(), caching Patterns, and considering defensive copying when reusing mutable objects.
Autoboxing can cause performance degradation when primitive and wrapper types are mixed, so it's preferable to use primitive types first and avoid unnecessary object pool creation.
Cases of Creating Unnecessary Objects
Using new String()
Strings a, b, and c all contain the string “hi”. However, since the addresses referenced by these three strings are all different, they allocate different memory for the same data, leading to waste.
Therefore, when declaring a string, you should declare it as a literal instead of using the new keyword.
The above source code uses only one instance. Furthermore, using this method ensures that all code within the same JVM that uses the “hi” string literal reuses the same object. This is due to the nature of the Java constant pool.
Using new Boolean()
The above code creates a Boolean instance through a constructor that takes a string as a parameter. Boolean can only be true or false, and creating instances every time is a waste of memory. Therefore, it is better to use the static factory method Boolean.valueOf().
Using String.matches()
It is good to cache and reuse objects with high creation costs, but we don't always know the cost of the objects we create. For example, if you want to write a method to check if a given string is a valid Roman numeral, using regular expressions is the easiest way.
However, String.matches() is a method with performance issues. The Pattern instance for regular expressions created internally by this method is used once and then discarded, becoming immediately subject to garbage collection. As the frequency of repeated use of the regular expression increases, the cost of creating and discarding the same Pattern instance increases. Therefore, it is better to cache the Pattern instance in advance and reuse it whenever the isRomanNumeral() method is called later.
Caution
In all the examples above, when caching unnecessary objects, they were all made immutable. This is because it is safe to reuse them even if they are. However, there are cases where this contradicts the intuition of reusing immutable objects.
An adapter (view) is an object that delegates actual work to a backend object and acts as a secondary interface. Since the adapter only needs to manage the backend object, only one adapter needs to be created for each backend object.
For example, the keySet() method of the Map interface returns a Set view containing all the keys within the Map object. Users might think that a new Set instance is created every time the keySet() method is called, but if you look at the actual JDK implementation, it returns the same mutable Set instance every time.
This is because even though the returned Set instance is mutable, the functions performed are all the same, and all Set instances represent the Map instance. Therefore, even if keySet() creates multiple view objects, there is no problem or benefit in doing so.
Therefore, if you modify the names1 instance as above, the names2 instance is also affected.
However, personally, I believe that the return value of the keySet() method should use defensive copying to return a new object every time. If the Set instance received by the keySet() method is also being used elsewhere and there is code that changes the state of this instance, you can no longer be sure of the values of the currently used Set instance and the Map instance.
Also, unless the keySet() is used excessively, the performance is not critically affected by the Set interface being created every time. Rather, I think it is better to make the Set interface immutable for stable maintenance.
Autoboxing
Autoboxing is a technique that automatically converts between primitive types and wrapper types when programmers mix them. However, autoboxing only blurs the distinction between primitive types and wrapper types, not eliminating it entirely.
Logically, there is no problem, but the code is very inefficient in terms of performance. The cause of this is the types of sum and i within the for loop.
The type of sum is Long, and the type of i is long. In other words, the long-type i creates a new Long instance every time it is added to sum as it iterates through the loop. As a result, you should use primitive types rather than wrapper types and be careful not to use unintentional autoboxing.
Parts Not to Misunderstand
Don't misunderstand the advice to avoid unnecessary object creation as simply meaning that you should avoid it because the cost of object creation is high.
Especially in today's JVMs, creating and reclaiming small objects that are unnecessarily created is not a burdensome task. Therefore, unless it is a very expensive object like a database connection, don't create a custom object pool.
Furthermore, remember that the damage caused by reusing objects when defensive copying is necessary is much greater than the damage caused by repeatedly creating unnecessary objects. The side effects of repeated creation only affect the code form and performance, but if defensive copying fails, it leads directly to bugs and security issues.