Why hide anything
So far every property was visible to all code. That is convenient, but it exposes the class's internals. If a later change needs validation, or a different way of computing a value, every piece of code that touched the property directly may break. Encapsulation is the answer: keep the data inside the class and let outside code reach it only through the class's functions. An access modifier says who may see a member.
The four modifiers
| Modifier | Visible to |
|---|---|
public | Everyone. This is the default and is usually left out. |
private | Only code inside the same class. |
protected | The class and its subclasses. |
internal | Any code in the same module, which is one compiled unit of a project. |
For now, public and private are the two to focus on. A modifier goes in front of the declaration, and for a constructor property it goes before val or var:
class Dog(private val name: String)
Inside Dog, name is available as usual. Outside, it is not. Given val rex = Dog("Rex"), the expression rex.name fails to compile with an error saying that name is private.
protected and top-level private
A protected member can be used inside the class and inside its subclasses, but not by unrelated code. If Animal declares protected var energy = 10, a Dog that extends it can write energy -= 3, while Animal("Rex").energy from outside is a compile error. Inheritance itself comes in a later lesson.
Modifiers also work on top-level declarations. A private function or class declared at the top of a file is visible only inside that file.
Exposing data through functions
A private member is not useless to outsiders; the class chooses what to offer. A private property plus a public function that returns it is the classic getter. A public function that changes it is the setter, and it can check the new value:
class Account {
private var balance: Double = 0.0
fun deposit(amount: Double) {
if (amount < 0) throw IllegalArgumentException("negative")
balance += amount
}
fun getBalance(): Double = balance
}
Callers cannot set balance to a nonsense value. They can only call deposit, which rejects a negative amount with an IllegalArgumentException.
The Kotlin shortcut
Kotlin properties already come with accessors, so you often do not write getters by hand. To make a value readable but not writable from outside, keep it public and restrict the setter:
class Counter {
var count: Int = 0
private set
fun inc() {
count++
}
}
Outside code can read c.count but c.count = 5 does not compile. The class itself can still change it through inc.
One trap: a public val name and a function getName() in the same class collide. The property generates a getter with that exact name on the JVM, and the compiler reports a platform declaration clash. In the lesson's code name is private, so it generates no getter and fun getName() is fine. In a new class, a public val name alone is more idiomatic.
What private really means
Private is per class, not per object. A function in Dog may read the private property of a different Dog. If Dog has fun same(other: Dog) = name == other.name, it compiles and compares two private names. Private also does not make a value secret at run time; it only controls what the compiler accepts.
The exercise
The starter code gives Dog a private name and calls rex.getName(), which does not exist yet. Step 1 declares a public function getName that returns a String. Step 2 makes it return the property, which is allowed because the function belongs to the class:
class Dog(private val name: String) {
fun getName(): String {
return name
}
}
class Solution {
fun mainFunction() {
val rex = Dog("Rex")
println("Dog's name: " + rex.getName())
}
}
The output is Dog's name: Rex.
Common mistakes
- Reading the private property directly.
rex.namefromSolutionis a compile error. Go throughgetName(). - Making the getter private too. Functions are public by default, so leave the modifier off.
- Forgetting the return type. A block-body function without
: StringreturnsUnit, soreturn namedoes not compile. - Writing both a public property and a same-named getter. That is the platform clash described above.
Key takeaways
- Encapsulation hides data behind functions so a class can change its internals safely.
publicis the default;privatelimits access to the class,protectedadds subclasses, andinternalopens it to the module.- A private property with a public getter function is the classic pattern;
private setis the Kotlin shortcut.