Skip to content
CodeKruPro

Your language · Java

Strings

Creating, comparing and changing strings, and why strings cannot be changed in place.

Learn: Strings in Java8 minute read

A String is Java's type for text. Test code is full of it: locators (text that tells Selenium how to find an element on a page), expected messages, URLs, JSON bodies and data read from files. Interviewers ask about strings to check three things: that you know a String never changes, that you compare strings the right way, and that you can build and split text without waste.

Step 1: a String never changes

String is immutable, which means a String object can never change after it is created. Methods such as toUpperCase(), replace(), trim() and concat() return a new object and leave the original alone. If you call title.toUpperCase(); and do not store the result, title stays the same.

The designers had four reasons for this. Identical literals (text typed directly in the code between quotes) can share one object in the string pool, which is only safe if nobody can change it. A path or URL cannot be changed after it has been checked, which helps security. The hash code (a number made from the text, which a HashMap uses to find an entry quickly) is worked out once and cached, so strings make fast HashMap keys. And an object that cannot change can be shared between threads (parts of a program that run at the same time) without locks (rules that let only one thread use something at a time). See Threads. The class is also final, so no subclass can add changeable state.

Step 2: the string pool, and why == fails

The string pool is an area of the heap (the part of memory where Java keeps objects; see Memory) where Java keeps one copy of each string literal. Every literal with the same text points at that one object. So do constants that the compiler joins before the program runs, such as "a" + "b". Text built while the program runs, read from a page or returned by an API is a new object, and new String("test") always makes a new object too.

Variables a and b both point to one pooled "test" object, while c, made with new String, points to a separate object on the heap.
Two literals share one pooled object; new String makes another one.

== asks whether two references point at the same object. equals() compares the characters. That is why == sometimes "works" in a small demo with literals and then fails in a real test.

public class Main {
    public static void main(String[] args) {
        String a = "test";
        String b = "test";
        String c = new String("test");
        String part = "te";
        String d = part + "st";              // built at run time

        System.out.println(a == b);          // same pooled literal
        System.out.println(a == c);          // new object
        System.out.println(a == d);          // new object
        System.out.println(a.equals(c) && a.equals(d));
        System.out.println(a == d.intern()); // intern returns the pooled copy

        String title = "Login";
        title.toUpperCase();                 // result thrown away
        System.out.println(title);
        title = title.toUpperCase();
        System.out.println(title);

        String actual = null;
        System.out.println("Welcome".equals(actual));   // no NullPointerException
    }
}
true
false
false
true
true
Login
LOGIN
false

When a value may be null, call equals on the known value, as in "Welcome".equals(actual), or use Objects.equals(a, b). For a check that ignores case, use equalsIgnoreCase.

Step 3: StringBuilder and StringBuffer

Because a String cannot change, every + inside a loop can create a new object. StringBuilder keeps one buffer that grows as needed, so use it when you build text step by step. StringBuffer has the same methods, but they are synchronized (only one thread can use them at a time). That makes it thread-safe and a little slower, and you rarely need it. A single line such as "Hello " + name is fine as it is.

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> cells = List.of("Asha", "QA", "Pune");
        StringBuilder row = new StringBuilder();
        for (String cell : cells) {
            if (row.length() > 0) row.append(" | ");
            row.append(cell);
        }
        System.out.println(row);
        System.out.println(new StringBuilder("level up").reverse());
        System.out.println(String.join(",", cells));
    }
}
Asha | QA | Pune
pu level
Asha,QA,Pune

Step 4: methods you will use in tests

  • length(), charAt(i), indexOf("x") (-1 when missing), contains, startsWith and endsWith.
  • substring(begin, end): the end index is not included.
  • split(regex): the argument is a regular expression (a pattern for matching text, where some characters such as the dot have a special meaning), so split on a dot with "\\.".
  • strip() removes spaces at both ends, and isBlank() is true for empty or space-only text.
  • String.format("%s has %d items", name, n) builds a message from a pattern.
String message = driver.findElement(By.id("toast")).getText().strip();
assertTrue(message.startsWith("Order"), "Unexpected message: " + message);
String orderId = message.substring(message.indexOf('#') + 1);

How to answer in an interview

Start with the rule, then back it with the pool. For "== or equals?": "equals() compares the text and == compares references. Literals share one pooled object, so == can look right in a demo, but text from a page is a new object, so I always use equals()." For "how many objects does new String("hi") create?": "One new object on the heap, plus a pooled "hi" if that literal is not in the pool yet."

Common mistakes

  • Comparing strings with ==.
  • Calling s.trim() or s.replace(...) without storing the result.
  • Calling actual.equals("x") when actual may be null.
  • Splitting on "." or "|", which are special characters in a regular expression. The dot matches any character, so the result is not what you expect.
  • Saying StringBuffer is the faster one. StringBuilder is faster because it has no locking.

Recap

Strings are immutable: every change returns a new object. Literals share one object in the string pool, which has lived in the heap since Java 7. Compare text with equals(), putting the known value first when the other may be null. Build text in loops with StringBuilder, and remember that substring excludes its end index and split takes a regular expression.

9 questions0/9 solved

StatusQuestionDifficultyAsked in
Not started== versus equals() on stringsEasyAsked in: Capgemini, Infosys
Not startedWhy are strings immutable in Java?Easy-MediumAsked in: Capgemini, Infosys
Pro+String vs StringBuilder vs StringBufferEasy-MediumAsked in: Accenture, Capgemini, Cognizant +1
Pro+Calling toUpperCase() without using the resultEasyAsked in: Capgemini, Infosys
Pro+String pool, constants and intern()MediumAsked in: Capgemini, Infosys
Pro+Comparing a string that may be nullEasy-Medium
Pro+Taking text apart: substring, split and indexOfEasy-Medium
Pro+Ways to reverse a string in JavaEasy-MediumAsked in: Accenture, Capgemini, Cognizant +1
Pro+How many objects does new String("hello") create?MediumAsked in: Capgemini, Infosys
Not startedNot startedIn progressIn progressSolvedSolvedPro+Pro+

The first 2 questions of each topic are free. Pro+ opens every question.

Back to the plan