What you'll learn
Good design means exposing only what you intend. This module covers Kotlin's visibility modifiers — including the module-level internal that Java lacks — and how to organise code with nested and inner classes.
By the end you'll be able to:
- Control access with the four visibility modifiers
- Use
internalfor module-wide visibility - Distinguish nested classes from inner classes
Visibility modifiers
Visibility decides who can see a class, function, or property. Unlike Java (where the default is package-private), Kotlin's default is public. Choosing the tightest level that works keeps your public surface small and your internals free to change:
| Modifier | Visible… |
|---|---|
public | everywhere (the default) |
internal | anywhere in the same module |
protected | in the class and its subclasses |
private | in the class (or file, for top-level) only |
class BankAccount {
private var balance = 0.0 // hidden from outside the class
fun deposit(amount: Double) { // public by default
if (amount > 0) balance += amount
}
fun getBalance() = balance
}
val acc = BankAccount()
acc.deposit(100.0)
// acc.balance // ERROR — balance is private
println(acc.getBalance()) // 100.0Tip
private and open up only when needed. It's far easier to loosen access later than to tighten a member half your codebase already depends on.internal — module visibility
internal is a Kotlin addition Java doesn't have: it makes something visible everywhere in the same module (a compiled unit — a Gradle module, a library) but hidden from other modules that depend on it. It's ideal for a library's internal helpers — public within the library, invisible to its users.
Nested vs inner classes
You can declare a class inside another. There's an important distinction: a plain nested class is just grouped inside — it can't touch the outer instance. Adding inner gives it a reference to the outer object, so it can use the outer's members:
Nested (default)
A class inside a class. Cannot access the outer instance's members. Just grouping.
Outer.Nested()
inner
Marked inner. Holds a reference to the outer instance and can use its members.
Outer().Inner()
class Outer {
private val secret = "hidden"
class Nested { // nested: NO access to Outer's members
fun show() = "I'm nested"
}
inner class Inner { // inner: CAN access Outer's members
fun reveal() = "Secret is $secret"
}
}
println(Outer.Nested().show()) // create nested directly
println(Outer().Inner().reveal()) // inner needs an Outer instanceNote
inner keyword — which avoids accidental references to the outer instance (a common source of memory leaks in Android). Reach for inner only when you genuinely need the outer object.Recap & quick check
Key takeaways
- Kotlin's default visibility is public (unlike Java's package-private).
- The modifiers: public (everywhere), internal (same module), protected (class + subclasses), private (class/file).
- internal is Kotlin-specific: visible within the module, hidden from other modules — great for library internals.
- A nested class is just grouped inside and can't access the outer instance.
- An inner class holds a reference to the outer instance and can use its members — you must opt in with 'inner'.
Quick check
1. What is Kotlin's default visibility?
2. What does 'internal' mean?
3. Can a plain nested class access the outer instance's members?
4. What does the 'inner' keyword add?
5. How does Kotlin's nested-class default differ from Java's?
Excellent — you've completed Phase 3! You can design classes, model data and state, and control access like a pro. Next comes Phase 4 — Collections & Generics.