Course outline · 0% complete

0/29 lessons0%

Course overview →

Strings and objects

lesson 2-2 · ~6 min · 5/29

String is an object, not a primitive

Almost everything real programs handle is text: usernames, file paths, URLs, JSON from an API. In Java all of it lives in String, with a capital S and double quotes.

Unlike the primitives from lesson 2-1, a String is an object, meaning a bundle of data plus methods you call with a dot, exactly like Python's "abc".upper().

String lang = "Java";
lang.length()        // 4
lang.toUpperCase()   // "JAVA"
lang.charAt(0)       // 'J', a char
lang.substring(1)    // "ava", from index 1 to the end
lang.contains("av")  // true

Strings are immutable, the same as in Python. Every method returns a new String and none of them changes the original, so lang.toUpperCase() on its own accomplishes nothing unless you keep the result.

Being an object also has a consequence for comparison, which is the subject of the next section and the most common beginner bug in the language.

A tour of the core String methods

Five calls on one String, with the original printed last.

public class Main {
  public static void main(String[] args) {
    String lang = "Java";
    System.out.println(lang.length());
    System.out.println(lang.toUpperCase());
    System.out.println(lang.charAt(0));
    System.out.println(lang.substring(1));
    System.out.println(lang);
  }
}

Output

4
JAVA
J
ava
Java

The last line is the important one: lang is still Java after four method calls, which is immutability in action.

Note that length() has parentheses because it is a method, unlike an array's length field in a later lesson. Indexes start at 0, so charAt(0) gives the first character and substring(1) skips it.

Comparing objects, and the == trap

For primitives, == compares values and behaves as expected. For objects, == asks whether these are the same object in memory, not whether they look equal. To compare String contents, always use .equals(...):

String a = "hi";
String b = new String("hi");
a == b        // false, different objects
a.equals(b)   // true, same characters

This is the single most common beginner bug in Java. The rule is short: == for primitives, .equals() for objects.

The behavior follows from where Java stores things. Local variables live on the stack, a small fast memory region tied to the method currently running. Objects live on the heap, a larger region for data that can outlive any single method.

A variable of object type holds only a reference, which is the heap location of the object, so == on two object variables compares the two locations rather than the characters stored at them. The diagram below shows exactly that.

variables (stack)objects (heap)abString "hi"String "hi"
a and b hold references to two different String objects with the same characters. == compares the arrows, .equals compares the characters.

The comparison that reads the characters

For two String variables a and b built separately but holding the same characters, a.equals(b) is the reliable test.

equals compares the characters, while == compares heap locations. Both variables are references, each holding the address of a String object, so == asks whether they point at the very same object, and two separately built Strings do not.

ExpressionAsks
a == bare these the same object
a.equals(b)do these hold the same characters
a = bassignment, not a comparison at all

The trap is that == sometimes returns true anyway. Java reuses identical string literals from a shared pool, so "hi" == "hi" is often true, which lets the bug pass every quick test and fail on text that arrived from a file or a user.

Four facts about one word

Length, first character, last character, and uppercase, all from methods on the same String.

public class Main {
  public static void main(String[] args) {
    String word = "polymorphism";
    System.out.println(word.length());
    System.out.println(word.charAt(0));
    System.out.println(word.charAt(word.length() - 1));
    System.out.println(word.toUpperCase());
  }
}

Output

12
p
m
POLYMORPHISM

length() is a method call with parentheses, unlike Python's len(word). Indexes start at 0, so the last character sits at word.length() - 1, and charAt(word.length()) would throw at runtime.

That off-by-one is worth internalizing now, because the same reasoning governs arrays and lists later. A collection of 12 items has valid indexes 0 through 11.