skip to content

What are string literals and the string constant pool, and how do underscores in numeric literals work? When can you NOT use an underscore?

level: middleimportance: should knowfreq 42%

answer

  1. Double quotes = String literal -> pooled (interned)
  2. Literal == literal is true; use .equals for content
  3. new String() makes a distinct, non-pooled object
  4. Underscores group digits, compiler strips them
  5. _ must have a digit on BOTH sides
  6. No _ at ends, next to '.', suffix, or base prefix

basics

~20 s

A string literal is text in double quotes; identical literals are shared (interned) in a string pool, so "hi" == "hi" is true. Underscores group digits in numbers for readability, like 1_000_000, but can't sit at the start, end, or next to a dot or base prefix.

solid answer

~40 s

A string literal is characters in double quotes, e.g. "hello"; it produces a String object. Java interns string literals in the string constant pool, so two identical literals refer to the same object and == returns true between them, whereas new String("hi") makes a distinct object. Underscores, added in Java 7, are visual digit separators inside numeric literals: 1_000_000, 0xFF_FF, 0b1010_0101, 3.141_592. The compiler simply strips them. The restriction is that an underscore must sit strictly between two digits: not at the start or end of the number, not adjacent to the decimal point, not next to the L/f/d suffix, and not right after the 0x/0b prefix or before/after the leading-zero octal marker in the relevant spots. They affect readability only, never the value or type.

code

java · 19 lines
java
// String interning
String a = "hi";
String b = "hi";
String c = new String("hi");
System.out.println(a == b);       // true  (same pooled literal)
System.out.println(a == c);       // false (new makes a distinct object)
System.out.println(a.equals(c));  // true  (same characters)

// Underscore digit separators (compiler ignores them)
int million = 1_000_000;          // == 1000000
int hex     = 0xCAFE_BABE;
double pi   = 3.141_592;

// These do NOT compile:
// int x = _100;      // leading underscore
// int y = 100_;      // trailing underscore
// double z = 3._14;  // adjacent to the decimal point
// long w = 100_L;    // adjacent to the suffix
// int q = 0x_FF;     // right after the base prefix

go deeper

for a junior

Know strings use double quotes and that you compare them with .equals, and that 1_000_000 is just a readable way to write 1000000.

for a middle

Explain the constant pool / interning, the == vs .equals distinction including new String, and state the underscore placement rule with examples that fail.

for a senior

Discuss compile-time constant folding/pooling vs runtime concatenation, String.intern, and why == coincidentally works for literals but is a bug in general comparisons.

for a principal

Address memory and GC implications of the intern pool (historic PermGen vs heap), security of pooled secrets in strings, and conventions/linters enforcing .equals and readable digit grouping.

## String literals A **String literal** is a run of characters between **double quotes**: `"hello"`, `""` (empty), `"line1\nline2"` (escapes allowed). Evaluating a String literal yields a reference to a `String` **object** — an immutable sequence of chars. ### The string constant pool and interning Java maintains a **string constant pool**: an internal table of unique String values. When the compiler sees a String literal, it places that value in the pool, and every identical literal anywhere in the program refers to the **same** pooled object. This sharing is called **interning**. Consequences: ```java String a = "hi"; String b = "hi"; a == b; // true -> same pooled object (reference equality) String c = new String("hi"); a == c; // false -> new String(...) makes a DISTINCT object a.equals(c); // true -> same characters ``` `==` on references tests **identity** (same object); `.equals` tests **value** (same characters). Because literals are interned, `==` happens to work *between literals*, but you must always use `.equals` for String content comparison, since strings from `new`, I/O, or concatenation at runtime are generally not pooled. (You can manually pool a runtime string with `String.intern()`.) Compile-time constant concatenation like `"ab" + "c"` is folded to the literal `"abc"` and pooled, but `someVar + "c"` is computed at runtime and not pooled. Interning saves memory (one copy of each distinct literal) — that is its purpose, not enabling `==` comparisons. ## Underscores in numeric literals (Java 7+) Large numbers are hard to read: `1000000` vs `1_000_000`. Java 7 added **underscore digit separators**: underscores placed *between digits* of any numeric literal, which the compiler **ignores entirely** (they change neither value nor type). They work in all bases and in floating-point: ```java int million = 1_000_000; long card = 1234_5678_9012_3456L; int hex = 0xFF_EC_DE_5E; int binary = 0b0101_0010_1010_1110; double pi = 3.141_592_653; ``` ### Where underscores are NOT allowed An underscore must be **immediately surrounded by digits**. It is a **compile error** to place it: - at the **start** or **end** of the number: `_100`, `100_` - **adjacent to the decimal point**: `3._14`, `3_.14` - **before the `L`/`f`/`d` suffix**: `100_L`, `1.0_f` - **right after the base prefix** `0x` / `0b`: `0x_FF`, `0b_101` - **right before** the prefix or in other non-digit-adjacent spots Note a subtle case: `0_52` is allowed (it's the octal literal 052 = 42 with a separator after the leading zero, since the zero is a digit), but `_052` is not. The rule to remember is simply: **a `_` must have a digit on both sides.** ## Why both features matter String interning explains the classic `==` vs `.equals` interview trap and the memory model of literals. Underscores are a pure-readability tool — use them to group thousands, bytes, or card-number chunks, and know the placement rule so a misplaced `_` doesn't cause a confusing compile error.

  • Why is new String("hi") == "hi" false?
    The literal "hi" is interned in the string pool, but new String("hi") explicitly constructs a brand-new String object on the heap, distinct from the pooled one. == compares object identity, so it is false. .equals compares characters and is true. You can fold the new one back into the pool with .intern().
  • Is 0x_FF a valid literal?
    No. An underscore must sit strictly between two digits; 0x_FF places it right after the base prefix and before the first digit, which is a compile error. 0xF_F or 0xFF would be valid.

saying these in an interview costs you the question

  • Comparing strings with == in general code (works only for pooled literals)
  • Thinking interning's purpose is to make == work (it is memory saving)
  • Believing underscores change the numeric value
  • Putting an underscore at the start/end or next to a dot/suffix/prefix and expecting it to compile

context