Strings
String Basics
Deconstruct String creation (literals vs new objects) and the internal Compact Strings representation in modern Java.
Interview: Compares heap allocations vs String Pool registrations, char[] vs Compact Strings byte[] memory usage, and UTF-16 coder checks.
In Java, a String is an immutable reference object representing a sequence of character data. Rather than serving as a simple primitive array, the java.lang.String class is a fully encapsulated class. Starting in Java 9, it uses an optimized memory layout called Compact Strings to reduce heap space usage.
Core Idea
Strings are immutable objects. Creating them via literals utilizes the JVM String Pool, while constructors force new heap objects.
Why It Matters
Compact Strings replace 16-bit char[] arrays with 8-bit byte[] arrays for Latin-1 text, saving up to 50% heap memory.
Interview Lens
Focuses on heap allocations, pool cache checks, Compact String coder flags, and literal declaration differences.
String Creation: Literals vs Constructor
There are two distinct ways to instantiate a String object in Java:
- String Literal (e.g.,
String s1 = "Java";): The JVM checks the internal String Pool first. If the literal "Java" is already present, it returns the cached reference. Otherwise, it allocates the string in the pool and returns the reference. No duplicate heap objects are created. - New Constructor (e.g.,
String s2 = new String("Java");): The JVM always creates a new, separate String object on the heap, bypassing the pool cache check, even if "Java" already exists in the pool. This leads to duplicate strings and unnecessary memory allocations.
Compact Strings (Java 9+)
Prior to Java 9, String contents were stored as a 16-bit character array (char[]), consuming 2 bytes per character under the UTF-16 encoding. In Java 9, the backing array was refactored to an 8-bit byte array (byte[]) combined with an encoding flag field called coder:
- Latin-1 Coder (0): Used if the string contains only single-byte characters (ISO-8859-1). Elements allocate exactly 1 byte per character.
- UTF-16 Coder (1): Used if the string contains any characters requiring multi-byte representation. Elements allocate 2 bytes per character.
This optimization is fully automatic and transparent to developers, providing significant memory footprint reductions without requiring code changes.
Common Pitfalls
- Using the Constructor: Initializing variables via
new String("value"), which duplicates string objects on the heap. - Modifying contents via Reflection: Accessing private fields to modify backing byte arrays. This breaks String Pool invariants and leads to unpredictable behaviors.
- Encoding Issues: Converting strings to bytes (e.g.,
str.getBytes()) without specifying a character set, which uses the platform's default charset and can result in corrupt data on different OS platforms.
Best Practices
- Always declare strings using literals (e.g.,
"value") to leverage String Pool caching. - Specify the character set explicitly (e.g.,
StandardCharsets.UTF_8) when converting strings to byte arrays. - Use
char[]for sensitive data like passwords to allow explicit zeroing in memory, reducing exposure to heap dumps.
Interview-Relevant Information
Q1: How many String objects are created by: String s = new String("Hello");?
Answer: Two objects. The literal "Hello" is evaluated first and added to the String Pool (if it doesn't already exist). Then, the new operator instantiates a separate String object on the heap that references the pooled char/byte sequence.
Q2: What is the backing array of String in modern Java versions?
Answer: Since Java 9, it is a byte[] array instead of a char[] array, paired with a coder byte. If the text contains only Latin-1 characters, the array allocates 1 byte per character, saving up to 50% heap space.
Quick Checklist
Can you identify why constructors duplicate memory allocations, explain the Compact Strings layout, describe coder flags, and choose correct encodings? If yes, you understand string basics.
Use Cases
Declaring constant configuration labels dynamically using literals to conserve memory.
Encoding text outputs safely before transmitting them over network channels.
Common Mistakes
Using new String() constructors, creating duplicate objects on the heap.
Converting strings to byte arrays without specifying an explicit Charset, causing cross-platform conversion bugs.