Skip to content
teach

Lesson 4. Basic Types and String Templates

Mission link: Lesson 3 established that == calls equals() on objects; this lesson is why that's true even for numbers and booleans in Kotlin, unlike Java's primitives, and the string templates already used without comment since lesson 1.
Primary source: Docs: "Types overview", Kotlin
Prerequisites: Lesson 3, Platform type

Warm-up

  1. ▢ What does == check in Kotlin, and how does that differ from what == checks in Java for reference types?
Check

Kotlin's == checks structural equality, calling equals() to compare content; Java's == on reference types checks whether two variables point to the same object, requiring an explicit .equals() call for content comparison instead.

  1. ▢ What concrete bug can result from overriding equals() without also overriding hashCode()?
Check

A hash-based collection (HashSet, HashMap) buckets objects by hashCode() before ever calling equals(); if two ==-equal objects have different hashCode() values, a lookup can look in the wrong bucket and never find an object that's logically present, appearing "missing" even though it was inserted.

Know this

Kotlin has no primitive types at the language level, unlike Java

Java splits types into primitives (int, boolean, char, with no methods and no null) and reference types (objects, which can be null and have methods). Kotlin doesn't make this split visible to you: Int, Boolean, Char, and the other basic types are ordinary classes with member functions and properties, the same as any other type, even though the compiler optimizes them to the JVM's primitive representation at runtime whenever it can. 5.toString() and 5.plus(3) are real method calls on Int, not special syntax; this is what makes Kotlin's "everything is an object" description literal rather than a simplification.

This is why generic type parameters work uniformly with numbers

Because Kotlin's basic types are real classes, they can be used as generic type arguments directly: List<Int> works the same way List<String> does, with no boxing-related asymmetry to reason about at the language level (Int still gets boxed to java.lang.Integer on the JVM where a generic type erases to Object, but that's a runtime detail the language design hides from you, not something you write different code to work around).

String templates: interpolation is a language feature, not concatenation

A string template embeds an expression directly inside a string literal: "$name" substitutes the value of name, and "${expr}" substitutes the result of evaluating any expression ("${user.name.uppercase()}", "${a + b}"). This isn't syntactic sugar over +-based concatenation bolted on afterward; it's Kotlin's actual way of building a string from parts, and it's why every lesson so far has written "Hello, $name" rather than "Hello, " + name. The simple $name form only works for a bare identifier; anything more than that (a property access, a method call, an expression) needs the ${...} braces.

Top: "Hello, $name" where $name is a bare identifier, needing no braces. Bottom: "Total: ${a + b}" where the braces are required because the template holds a full expression rather than a single identifier.

String is immutable, the same shape of guarantee as val

Kotlin's String type is immutable: once created, its contents never change, and every operation that looks like it modifies a string (.uppercase(), .replace(...), +) actually returns a new String rather than mutating the original. This is independent of whether the reference holding it is val or var (lesson 2): a var name: String can be reassigned to point at a different string, but neither val nor var ever makes the string object itself mutable, since String simply has no mutating methods to call.

Practice

  1. ▢ Why can you call .toString() or .plus() directly on an Int literal in Kotlin, when Java's int primitive has no methods at all?
Check

Kotlin's basic types, including Int, are ordinary classes at the language level with real member functions, unlike Java's primitives; the compiler optimizes them to a primitive representation at runtime where possible, but that's an implementation detail hidden from the code you write.

  1. ▢ Why does "${a + b}" need braces while "$name" doesn't?
Hint

Consider what the simple $identifier form is actually able to parse versus a general expression.

Check

The bare $identifier form only recognizes a simple identifier reference; anything beyond that, an expression, an operation, a method call, or a property access, needs the ${...} form so the template can parse and evaluate the full expression rather than just substituting a single variable's value.

  1. ▢ val name = "Ada" followed by name.uppercase(). Does this mutate name's value? What does name hold afterward if the result isn't captured?
Check

No: String is immutable, so .uppercase() returns a new String ("ADA") rather than modifying the original. If the result isn't captured into a variable, it's simply discarded, and name still holds "Ada" afterward, unchanged.

  1. ▢ Why does the fact that Kotlin's basic types are real classes matter for how List<Int> and List<String> behave from the language's perspective?
Check

Because Int is a real class rather than a primitive, it can be used as a generic type argument exactly the same way String can, with no special-casing needed at the language level; the JVM's boxing of Int to java.lang.Integer for generics is a runtime detail Kotlin's design hides rather than something the code has to account for explicitly.

  1. ▢ Which claim correctly describes Kotlin's basic types and string templates?

    • a) Int and other basic types are primitives with no methods, exactly like Java, for runtime performance
    • b) Kotlin's basic types are real classes with member functions, optimized to a primitive representation at runtime where possible; string templates ($name, ${expr}) are the language's actual mechanism for building strings from parts, not sugar over concatenation
    • c) String templates only work with the ${...} braces; the bare $name form is deprecated
    • d) String's immutability depends on whether it's held by a val or a var
Check

b) That's the precise pair of facts this lesson covers. (a) is false: Kotlin's basic types have real methods at the language level, unlike Java's primitives. (c) is false: the bare $name form is the standard shorthand for a simple identifier; braces are needed only for a fuller expression. (d) is false: String immutability is a property of the type itself, independent of whether the holding variable is val or var (lesson 2's reference-vs-object distinction again).

Real-world reps

  • [ ] Find a Kotlin file you've written or have access to. Count how many string concatenations use + versus a string template, and consider whether any +-based ones would read more clearly as a template.
  • [ ] Find a place where a basic type (Int, Boolean, Double) is used as a generic type argument (in a List<Int>, a Map<String, Int>, or similar). Confirm you can explain why this doesn't need special handling the way Java's primitive-vs-boxed distinction sometimes does.
  • [ ] Tomorrow: read the primary source's overview of Kotlin's number types in full, and note one thing that surprised you about how they behave compared to Java's primitives.

Going further


Not landing? Reread the primary source at the top, since this lesson compresses it and compression is where understanding leaks. Check the glossary for any term that felt slippery.

If the lesson itself is unclear rather than the material, that is a defect: open an issue.

Table of contents