- 37. Do not shadow or obscure identifiers in subscopes
- 38. Do not declare more than one variable per declaration
- 39. Use meaningful symbolic constants to represent literal values in program logic
- 40. Properly encode relationships in constant definitions
- 41. Return an empty array or collection instead of a null value for methods that return an array or collection
- 42. Use exceptions only for exceptional conditions
- 43. Use a try-with-resources statement to safely handle closeable resources
- 44. Do not use assertions to verify the absence of runtime errors
- 45. Use the same type for the second and third operands in conditional expressions
- 46. Do not serialize direct handles to system resources
- 47. Prefer using iterators over enumerations
- 48. Do not use direct buffers for short-lived, infrequently used objects
- 49. Remove short-lived objects from long-lived container objects
49. Remove short-lived objects from long-lived container objects
Always remove short-lived objects from long-lived container objects when the task is over. For example, objects attached to a java.nio.channels.SelectionKey object must be removed when they are no longer needed. Doing so reduces the likelihood of memory leaks. Similarly, use of array-based data structures such as ArrayList can introduce a requirement to indicate the absence of an entry by explicitly setting ArrayList’s individual array element to null.
This guideline specifically addresses objects referred to from containers. For an example where nulling out objects does not aid garbage collection, see Guideline 75, “Do not attempt to help the garbage collector by setting local reference variables to null.”
Noncompliant Code Example (Removing Short-Lived Objects)
In this noncompliant code example, a long-lived ArrayList contains references to both long- and short-lived elements. The programmer marks elements that have become irrelevant by setting a “dead” flag in the object.
class DataElement { private boolean dead = false; // Other fields public boolean isDead() { return dead; } public void killMe() { dead = true; } } // ... Elsewhere List<DataElement> longLivedList = new ArrayList<DataElement>(); // Processing that renders an element irrelevant // Kill the element that is now irrelevant longLivedList.get(someIndex).killMe();
The garbage collector cannot collect the dead DataElement object until it becomes unreferenced. Note that all methods that operate on objects of class DataElement must also check whether the instance in hand is dead.
Compliant Solution (Set Reference to null)
In this compliant solution, rather than use a dead flag, the programmer assigns null to ArrayList elements that have become irrelevant:
class DataElement { // Dead flag removed // Other fields } // Elsewhere List<DataElement> longLivedList = new ArrayList<DataElement>(); // Processing that renders an element irrelevant // Set the reference to the irrelevant DataElement to null longLivedList.set(someIndex, null);
Note that all code that operates on the longLivedList must now check for list entries that are null.
Compliant Solution (Use Null Object Pattern)
This compliant solution avoids the problems associated with intentionally null references by using a singleton sentinel object. This technique is known as the Null Object pattern (also known as the Sentinel pattern).
class DataElement { public static final DataElement NULL = createSentinel(); // Dead flag removed // Other fields private static final DataElement createSentinel() { // Allocate a sentinel object, setting all its fields // to carefully chosen "do nothing" values } } // Elsewhere List<DataElement> longLivedList = new ArrayList<DataElement>(); // Processing that renders an element irrelevant // Set the reference to the irrelevant DataElement to // the NULL object longLivedList.set(someIndex, DataElement.NULL);
When feasible, programmers should choose this design pattern over the explicit null reference values, as described in Guideline 41, “Return an empty array or collection instead of a null value for methods that return an array or collection.”
When using this pattern, the null object must be a singleton and must be final. It may be either public or private, depending on the overall design of the DataElement class. The state of the null object should be immutable after creation; immutability can be enforced either by using final fields or by explicit code in the methods of the DataElement class. See Chapter 8, “Behavioral Patterns, the Null Object,” of Patterns in Java, Volume 1, Second Edition [Grand 2002], for additional information on this design pattern, and also The CERT® Oracle® Secure Coding Standard for Java™ [Long 2012], “ERR08-J. Do not catch NullPointerException or any of its ancestors.”
Applicability
Leaving short-lived objects in long-lived container objects may consume memory that cannot be recovered by the garbage collector, leading to memory exhaustion and possible denial of service attacks.
Bibliography
[Grand 2002] |
Chapter 8, “Behavioral Patterns, the Null Object” |
[Long 2012] |
ERR08-J. Do not catch NullPointerException or any of its ancestors |