Introduction
Encapsulation is a fundamental concept in object-oriented programming (OOP) that restricts direct access to an object’s internal state and allows modification only through well-defined methods. This helps to protect the integrity of the object’s data and makes the code more modular and maintainable. In Kotlin, encapsulation is achieved using classes, visibility modifiers, and properties with custom getters and setters.
Encapsulation in Kotlin
Encapsulation in Kotlin involves:
- Defining a class with private properties.
- Providing public methods to access and modify the properties.
- Using custom getters and setters to control how properties are accessed and modified.
Example
fun main() {
val person = Person("Rahul", 25)
println(person.name) // Accessing the property directly
println(person.age) // Accessing the property through a getter
person.age = 30 // Modifying the property through a setter
println(person.age)
}
class Person(val name: String, private var _age: Int) {
// Custom getter for age
val age: Int
get() = _age
// Custom setter for age
set(value) {
if (value > 0) {
_age = value
} else {
println("Invalid age")
}
}
}
Explanation:
private var _age: Int
: The actual age property is private and can’t be accessed directly from outside the class.val age: Int get() = _age
: A custom getter that returns the value of_age
.var age: Int set(value) { ... }
: A custom setter that updates the value of_age
if the provided value is valid.
Output:
Rahul
25
30
Benefits of Encapsulation
- Data Hiding: Internal state of an object is hidden from the outside, preventing unauthorized access and modification.
- Modularity: Changes to the internal implementation of a class do not affect the code that uses the class.
- Maintainability: Well-defined interfaces make the code easier to understand, maintain, and extend.
Example with Encapsulation
Let’s consider a more complex example with a BankAccount
class.
Example
fun main() {
val account = BankAccount("123456", 1000.0)
account.deposit(500.0)
println("Balance after deposit: ${account.balance}")
account.withdraw(200.0)
println("Balance after withdrawal: ${account.balance}")
account.withdraw(2000.0) // Attempt to withdraw more than the balance
}
class BankAccount(private val accountNumber: String, private var _balance: Double) {
// Custom getter for balance
val balance: Double
get() = _balance
// Method to deposit money
fun deposit(amount: Double) {
if (amount > 0) {
_balance += amount
} else {
println("Invalid deposit amount")
}
}
// Method to withdraw money
fun withdraw(amount: Double) {
if (amount > 0 && amount <= _balance) {
_balance -= amount
} else {
println("Invalid withdrawal amount or insufficient funds")
}
}
}
Explanation:
private var _balance: Double
: The actual balance property is private and can’t be accessed directly from outside the class.val balance: Double get() = _balance
: A custom getter that returns the current balance.fun deposit(amount: Double) { ... }
: A method to deposit money into the account.fun withdraw(amount: Double) { ... }
: A method to withdraw money from the account, with checks to prevent invalid operations.
Output:
Balance after deposit: 1500.0
Balance after withdrawal: 1300.0
Invalid withdrawal amount or insufficient funds
Using Backing Fields for Encapsulation
Backing fields allow you to create properties with custom getters and setters while still being able to store the value of the property. The field
identifier in Kotlin is used for this purpose.
Example
fun main() {
val student = Student("Amit")
student.grade = 85
println("${student.name}'s grade: ${student.grade}")
student.grade = 120 // Invalid grade
}
class Student(val name: String) {
var grade: Int = 0
set(value) {
if (value in 0..100) {
field = value
} else {
println("Invalid grade")
}
}
}
Explanation:
var grade: Int = 0 set(value) { ... }
: The propertygrade
uses a backing field to store its value and includes validation in the setter.field = value
: Thefield
identifier is used to set the value of the property.
Output:
Amit's grade: 85
Invalid grade
Conclusion
In this chapter, you learned about encapsulation in Kotlin, including the use of visibility modifiers, custom getters and setters, and backing fields. Encapsulation helps to protect the integrity of an object’s data, makes the code more modular, and improves maintainability. Understanding and applying encapsulation is essential for writing robust and maintainable Kotlin programs.