Skip to content

VOL 01 / CH 09 / LESSON 01

9.1 String Basics ​

Prerequisites: Objects, References, Method Calls

Estimated time: 25 minutes

Runtime Environment: Java 17

You are ready to continue when: You can explain immutability, value comparison, interning, and UTF-16 indexing

First Separate References from String Values ​

toUpperCase() does not modify a string in place, while two String objects with equal content may still produce false under ==. Both observations require the same first question: what does the variable reference, and what value does the object hold?

String behaves like a built-in type but is a class. The first boundary to clarify: variables store references, string objects store values; variables can refer to a different object, but string values don't change in place.

1. String is a Reference Type ​

java
String name = "a developer";
String alias = name;

name and alias can point to the same object. The Java specification does not require you to understand programs in terms of "references being on the stack and objects being on the heap." The JVM can also perform escape analysis and other optimizations. During the initial learning phase, focus on the observable semantics:

  • String variables can be null;
  • Methods access objects through references;
  • == checks if two references point to the same object;
  • equals checks if two strings contain the same sequence of characters.

2. Objects Are Immutable, Not Variables ​

java
String message = "hello";
String upper = message.toUpperCase(java.util.Locale.ROOT);

System.out.println(message);
System.out.println(upper);

Output:

text
hello
HELLO

toUpperCase() returns the converted string, the original "hello" remains unchanged. The variable itself can still be reassigned:

java
message = upper;

This line modifies the reference saved in message, not the original object. String methods can also reuse existing objects when the result is unchanged. Immutability guarantees that "old values will not be rewritten," but it does not guarantee that "a new object is created on every call."

String is a final class, and its public API does not include methods for in-place modification of character content. How the JDK internally stores characters is an implementation detail; application code should not rely on a specific version using char[] or byte[].

Immutable values bring several direct benefits: the same object can safely be shared; strings are suitable as Map keys; the JVM can pool literals. However, immutability does not automatically make an entire object thread-safe if it contains a String field, and it does not imply that sensitive text will be promptly cleared from memory.

3. Literals, new String and String Pool ​

java
String first = "hello";
String second = "hello";
String copied = new String("hello");

System.out.println(first == second);
System.out.println(first == copied);
System.out.println(first.equals(copied));

Output:

text
true
false
true

String literals and the results of string constant expressions are interned. Such constants with identical content are guaranteed to reference the same pooled object. Explicit new String("hello") creates a different object, so identity comparison is false, while content comparison remains true.

This demonstration is meant to illustrate identity, not to teach you to rely on pooling. Whether text from a file, network, database, or runtime concatenation refers to the same literal object should not be part of business logic.

java
String fromInput = new String("admin");

if ("admin".equals(fromInput)) {
    System.out.println("Content matching");
}

intern() returns a canonical reference to the pooled representation of the same content, but it is rarely the correct tool for ordinary business code. First, use analytical data to prove that a large number of duplicate strings indeed cause memory issues, then evaluate the pooling strategy and lifecycle.

4. Value Comparison, Identity Comparison, and null ​

RequirementSyntaxExplanation
Compare text contentleft.equals(right)left cannot be null
Both sides may be nullObjects.equals(left, right)Two null return true
Compare with known constants"admin".equals(input)Return false when input is null
Compare if the same objectleft == rightUse only when identity itself is meaningful
java
import java.util.Objects;

String input = null;
System.out.println("admin".equals(input));
System.out.println(Objects.equals(input, null));

equalsIgnoreCase uses Java's Unicode case-comparison rules. It does not provide locale-sensitive sorting or Unicode normalization. If a protocol restricts identifiers to ASCII or defines different case rules, validate its allowed character set first instead of treating this method as a universal equivalence test. User-visible text requires more explicit rules, such as specifying Locale, using Collator, or normalizing within domain boundaries.

5. length() Counts UTF-16 Code Units ​

Java's String uses UTF-16 semantics for addressing. Common Chinese characters typically occupy one char, while certain characters require a pair of surrogate units:

java
String badge = "A😀B";

System.out.println(badge.length());
System.out.println(badge.codePointCount(0, badge.length()));
System.out.printf("U+%X%n", badge.codePointAt(1));

Output:

text
4
3
U+1F600

length() returns 4 because it counts UTF-16 code units; this particular example contains three code points and displays as three characters. charAt(1) accesses one half of the surrogate pair and is not suitable for processing characters from the supplementary plane individually.

To iterate through code points, you can write:

java
badge.codePoints().forEach(codePoint ->
        System.out.printf("U+%X ", codePoint));

Code points are not equivalent to what the user perceives as "one character." Combining accents, skin-tone modifiers, and family emoji can join multiple code points into one perceived character. Java's standard library can handle code points; to truncate by user-perceived characters, use Unicode grapheme-cluster segmentation and check the Unicode version supported by the library. For example, "e\u0301" has two code points but usually appears as one accented character. It still compares unequal to the single-code-point "é" with equals unless you first apply an explicit normalization policy.

6. Compile-Time and Runtime Concatenation ​

String constant expressions have values determined at compile time and follow the interning rules:

java
String folded = "hel" + "lo";
System.out.println(folded == "hello"); // true

Runtime concatenation strategies depend on the compiler and JVM version. Modern compilers may use invokedynamic. You cannot derive a fixed number or exact allocation count of StringBuilder from a single + in the source code.

When choosing a writing style, consider the intent: small, fixed fragments are clearest with +; loops or stepwise construction of long text use StringBuilder; joining collection elements can use either String.join or Collectors.joining. Performance conclusions must be based on the target JDK and real load measurements.

7. Complete Working Example ​

First, prepare a temporary directory:

bash
chapter9_semantics_root=$(mktemp -d)
readonly chapter9_semantics_root
mkdir -p "$chapter9_semantics_root/out"
cd "$chapter9_semantics_root"

Save as StringSemanticsDemo.java:

java
import java.util.Objects;

public class StringSemanticsDemo {
    public static void main(String[] args) {
        String original = "hello";
        String upper = original.toUpperCase(java.util.Locale.ROOT);
        checkEquals("hello", original);
        checkEquals("HELLO", upper);

        String literal = "hello";
        String sameLiteral = "hel" + "lo";
        String copied = new String("hello");
        check(literal == sameLiteral, "Compile-time constants should reuse literals");
        check(literal != copied, "new String should create a distinct object");
        check(literal.equals(copied), "The content should be equal");

        String nullable = null;
        check(!"admin".equals(nullable), "Constants on the left can prevent NPE");
        check(Objects.equals(nullable, null), "Objects.equals should accept two null values");

        String badge = "A😀B";
        checkEquals(4, badge.length());
        checkEquals(3, badge.codePointCount(0, badge.length()));
        checkEquals(0x1F600, badge.codePointAt(1));
        String combined = "e\u0301";
        checkEquals(2, combined.codePointCount(0, combined.length()));
        check(!combined.equals("é"), "similar appearance does not imply equal code units");

        System.out.println("String semantics check passed");
        System.out.println("UTF-16 code unit=" + badge.length()
                + ", code point=" + badge.codePointCount(0, badge.length()));
    }

    static void check(boolean condition, String message) {
        if (!condition) throw new AssertionError(message);
    }

    static void checkEquals(Object expected, Object actual) {
        if (!Objects.equals(expected, actual)) {
            throw new AssertionError("expected=" + expected + ", actual=" + actual);
        }
    }
}

Run:

bash
javac --release 17 -encoding UTF-8 -Xlint:all -d out StringSemanticsDemo.java
java -cp out StringSemanticsDemo

Expected output:

text
String semantics check passed
UTF-16 code unit=4, code point=3

Clean up after saving the required experiment records:

bash
cd
ls -ld -- "$chapter9_semantics_root"
rm -r -- "$chapter9_semantics_root"

Professional Lookup Entry ​

  • Java Language Specification 3.10.5: String Literals;
  • Java Language Specification 15.18.1: String Concatenation Operator;
  • String API: equals, codePoints, offsetByCodePoints, intern;
  • Unicode Standard Annex #29: Segmentation Rules for Handling User-Visible Characters When Processing Grapheme Clusters.

Before Continuing ​

Draw the process of string variable reassignment and the process of keeping string objects unchanged. Compare literals, new String, and runtime input using both identity and content checks. For A😀B, also provide the number of UTF-16 code units and code points. If the requirement states "a character as seen by the user," ask whether it should be processed as a grapheme cluster. A single + in the source code cannot be directly inferred to have a fixed number of allocations.

→ Next: String Operations and Construction

References: JLS §3.10.5: String Literals, Java 17 String API, Unicode UAX #29.

Built with VitePress | Software Systems Atlas