This chapter offers a concise, Java-aware tour of Kotlin, highlighting how the language favors safety, readability, and brevity. It frames Kotlin as a pragmatic upgrade: control structures are often expressions, immutability is the default posture, and null-handling is explicit. Throughout, the author emphasizes writing code that’s easier to reason about and maintain, previewing topics that later chapters revisit in the context of safe programming.
The overview walks through core language building blocks: declarations with val/var, type inference (and why explicit types can still help), lazy and delayed initialization, and a streamlined class model with primary constructors. Kotlin’s data classes auto-generate equals/hashCode/toString/copy and support destructuring, while companion objects replace Java-style statics and object declarations yield singletons. Functions can live at package level, visibility includes the module-scoped internal, and packages aren’t tied to folders. Kotlin erases the primitive/object split (e.g., Int), and its collection APIs distinguish read-only from mutable variants; operators like + build new lists, whereas mutators change collections in place.
Functionality leans into expressive, functional patterns: expression-bodied functions, local functions and closures, extension functions, and rich lambda syntax (trailing lambdas, implicit it, multi-line forms, optional type annotations). Null safety is first-class via nullable types, safe calls (?.), non-null assertions (!!), and the Elvis operator (?:). Control flow centers on expression forms of if and exhaustive when, with ranges and loops available when needed. Exceptions are unchecked, and try/catch/finally is an expression; use and useLines simplify resource handling. Smart casts and safe casts (as?) reduce boilerplate; == means structural equality and === referential identity. String interpolation and triple-quoted strings improve formatting, and generics leverage declaration- and use-site variance with out/in to model safe subtyping. Overall, Kotlin keeps seamless Java interop while enabling code that is more concise and robust by design.
Summary
Fields and variables have different syntax and different visibility.
Classes and interfaces save you most boilerplate code, especially for data classes, letting you create the equivalent of a Java object with properties, getters and setters, constructors, equals, hashcode, toString, and copy functions (and more), all in a single line.
You can define functions at the package level (replacing Java static methods) in classes, objects, or even locally in other functions.
Using extension functions, you can add functions to existing classes, and you can call them as if they were instance functions.
In Kotlin, conditional control structures are replaced with expressions that can be evaluated.
Kotlin has looping control structures, but you can nearly always replace them with functions.
Kotlin distinguishes between nullable and non-nullable types and provides operators to safely compose null values.
FAQ
What’s the difference between val and var, and which should I use by default?Use val for an immutable reference (like final in Java) and var for a mutable one. Prefer val by default—immutability makes code easier to reason about. Switch to var only when you truly need to reassign a reference.How does Kotlin’s type inference work, and when should I write the type explicitly?Kotlin infers a variable’s type from its initializer, so you can write val name = "Mickey". You must specify the type when there’s no initializer, inference is ambiguous, or you want to ensure the inferred type matches your intent (especially with generics and lambdas).When should I use lazy initialization (by lazy) vs delayed initialization (lateinit)?Use by lazy with val to compute a value once on first access without making it nullable: val name: String by lazy { getName() }. Use lateinit with var when the value must be assigned later (for example, set externally or by a DI framework) without making it nullable: lateinit var name: String. Unlike by lazy, lateinit doesn’t compute automatically on first use.How do Kotlin classes differ from Java classes?- Public and final by default (use open to allow extension)
- Primary constructor after the class name; initialization logic in init blocks
- Properties can be declared in the constructor (class Person(val name: String))
- Getters/setters are generated as needed
- No requirement that file names match class names; multiple public classes can be in one fileWhat do data classes provide and how does destructuring work?data classes auto-generate equals, hashCode, toString, copy, and componentN functions. Destructuring uses those componentN functions to unpack properties in order, for example: for ((name, date) in persons) { ... } with data class Person(val name: String, val registered: Instant).How does Kotlin replace Java’s static members and how do companion objects work with Java interop?Kotlin has no static members in classes. Use a companion object for members you want to call like statics: Person.create(xml). From Java, you normally call Person.Companion.create(...); add @JvmStatic to expose it as Person.create(...). Also consider top-level functions to avoid utility classes entirely.What’s the difference between read‑only and mutable collections, and how do + and mutating methods behave?listOf(...) returns a read‑only view (non-mutating API). mutableListOf(...) returns a list you can modify. The + operator creates a new list and does not mutate the original. Mutating methods like add or addAll change the mutable list in place and typically return a Boolean.How does Kotlin handle null safety (?, ?., !!, and ?:)?- T vs T?: non‑nullable vs nullable
- Safe call (?.): s?.length returns Int? (or null if s is null)
- Not‑null assertion (!!): s!!.length throws NPE if s is null
- Elvis (?:): val city = addr?.city ?: City.UNKNOWN uses a default if the left side is null
Chaining safe calls keeps code concise and safe.How are control structures different in Kotlin (if and when as expressions)?if and when return values, so you can write val msg = if (...) "A" else "B" and val result = when (x) { ... }. when is exhaustive—you must cover all cases (use else or all enum entries). Loops and ranges exist (for (i in 0 until 10 step 2)), but prefer higher‑level functions when possible.What are smart casts and how do is, as, and as? differ?Using is narrows the type within the checked branch (smart cast): if (x is String) x.length. as performs an unsafe cast and throws if it fails. as? performs a safe cast and returns null on failure (type becomes nullable): val s: String? = x as? String.
pro $24.99 per month
access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!