Kotlin protected Keyword
last modified April 19, 2025
Kotlin's visibility modifiers control access to class members. The protected
keyword restricts visibility to the class and its subclasses. This tutorial explores
the protected
modifier in depth with practical examples.
Basic Definitions
The protected
modifier in Kotlin makes a member visible within its
class and subclasses. Unlike Java, Kotlin's protected members aren't visible in
the same package. Protected applies to class members, not top-level declarations.
Basic protected Property
A protected property can only be accessed within its declaring class and any subclasses. This provides encapsulation while allowing inheritance.
package com.zetcode open class Vehicle { protected val maxSpeed = 100 fun showMaxSpeed() { println("Max speed: $maxSpeed") // Accessible here } } class Car : Vehicle() { fun displaySpeed() { println("Car max speed: $maxSpeed") // Accessible in subclass } } fun main() { val car = Car() car.showMaxSpeed() // Output: Max speed: 100 car.displaySpeed() // Output: Car max speed: 100 // println(car.maxSpeed) // Error: Cannot access 'maxSpeed' }
Here maxSpeed
is protected, so it's accessible in Vehicle and Car
but not from main(). The commented line would cause a compilation error if
uncommented.
protected Method
Methods can also be marked protected, restricting their use to the class hierarchy. This is useful for internal implementation details that subclasses might need.
package com.zetcode open class Animal { protected fun makeSound() { println("Animal sound") } fun publicSound() { makeSound() // Accessible here } } class Dog : Animal() { fun bark() { makeSound() // Accessible in subclass println("Woof!") } } fun main() { val dog = Dog() dog.publicSound() // Output: Animal sound dog.bark() // Output: Animal sound\nWoof! // dog.makeSound() // Error: Cannot access 'makeSound' }
The protected makeSound
method is accessible within Animal and Dog
but not from main(). Public methods can expose protected functionality when needed.
protected Constructor
A protected constructor can only be called by the class itself or its subclasses. This is useful for factory patterns or limiting instantiation.
package com.zetcode open class Parent protected constructor(val name: String) { companion object { fun create(name: String): Parent { return Parent(name) // Accessible in companion } } } class Child(name: String) : Parent(name) { // Can call protected constructor } fun main() { val parent = Parent.create("John") // Using factory method val child = Child("Alice") println(parent.name) // Output: John println(child.name) // Output: Alice // val direct = Parent("Bob") // Error: Cannot access '' }
The Parent class can only be instantiated through its factory method or by subclasses. This controls how instances are created while allowing inheritance.
protected and Internal Conflict
When a member is both protected and internal, it's visible to subclasses and module members. This shows how modifiers can combine in Kotlin.
package com.zetcode open class Base { protected internal val secret = "Confidential" } class Derived : Base() { fun reveal() { println(secret) // Accessible in subclass } } fun main() { val derived = Derived() derived.reveal() // Output: Confidential val base = Base() println(base.secret) // Also accessible in same module }
The secret
property is accessible both in subclasses and within the
same module. The internal modifier extends the protected visibility within the
module.
protected in Interfaces
Kotlin interfaces can't have protected members by default. All interface members are public. This example shows a workaround using abstract classes.
package com.zetcode interface Vehicle { fun start() // Implicitly public } abstract class ProtectedVehicle : Vehicle { protected abstract val maxSpeed: Int protected open fun internalStart() { println("Starting vehicle") } override fun start() { internalStart() } } class Car : ProtectedVehicle() { override val maxSpeed = 120 override fun internalStart() { println("Starting car at max speed $maxSpeed") } } fun main() { val car = Car() car.start() // Output: Starting car at max speed 120 // car.internalStart() // Error: Cannot access 'internalStart' // println(car.maxSpeed) // Error: Cannot access 'maxSpeed' }
By using an abstract class implementing the interface, we can add protected members. The Car class inherits both the interface and protected members while keeping them hidden from external code.
protected in Sealed Classes
Sealed classes often use protected constructors to control inheritance. This restricts subclassing to files where the sealed class is declared.
package com.zetcode sealed class Result { protected constructor() class Success(val data: String) : Result() class Error(val message: String) : Result() } fun process(result: Result) { when (result) { is Result.Success -> println(result.data) is Result.Error -> println(result.message) } } fun main() { val success = Result.Success("Data loaded") val error = Result.Error("Failed to load") process(success) // Output: Data loaded process(error) // Output: Failed to load // val custom = Result() // Error: Cannot access '' }
The sealed class's protected constructor prevents direct instantiation while allowing predefined subclasses. This is a common pattern for restricted hierarchies.
protected in Companion Objects
Companion object members can be protected, making them visible only to the class and its subclasses. This is useful for shared implementation details.
package com.zetcode open class Logger { protected companion object { const val PREFIX = "LOG: " fun formatMessage(message: String): String { return PREFIX + message } } fun log(message: String) { println(formatMessage(message)) } } class FileLogger : Logger() { fun logToFile(message: String) { println("Writing: ${formatMessage(message)}") } } fun main() { val logger = Logger() logger.log("Test message") // Output: LOG: Test message val fileLogger = FileLogger() fileLogger.logToFile("File message") // Output: Writing: LOG: File message // println(Logger.PREFIX) // Error: Cannot access 'PREFIX' // Logger.formatMessage("Direct") // Error: Cannot access 'formatMessage' }
The protected companion members are accessible within Logger and FileLogger but not externally. This shares implementation while keeping it hidden from users.
Best Practices for protected
- Use judiciously: Only make members protected when subclasses need direct access to them.
- Document protected members: Clearly document protected APIs as they're part of your class's contract.
- Consider alternatives: Sometimes composition is better than exposing protected members.
- Watch for overuse: Too many protected members can make subclassing complex.
- Combine with other modifiers: protected internal can be useful for framework code.
Source
Kotlin Visibility Modifiers Documentation
This tutorial covered Kotlin's protected
keyword in depth, showing
its use with properties, methods, constructors, and special class types. Proper
use of protected visibility helps create maintainable class hierarchies while
preserving encapsulation.
Author
List all Kotlin tutorials.