Kotlin protected 关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的可见性修饰符控制对类成员的访问。protected 关键字将可见性限制为该类及其子类。本教程通过实际例子深入探讨 protected 修饰符。
基本定义
Kotlin 中的 protected 修饰符使成员在其类和子类中可见。与 Java 不同,Kotlin 的 protected 成员在同一包中不可见。Protected 适用于类成员,而不是顶级声明。
基本 protected 属性
一个 protected 属性只能在其声明类和任何子类中访问。这提供了封装,同时允许继承。
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'
}
这里 maxSpeed 是 protected 的,因此它可以在 Vehicle 和 Car 中访问,但不能从 main() 访问。如果取消注释,注释行将导致编译错误。
protected 方法
方法也可以标记为 protected,将其使用限制为类层次结构。这对于子类可能需要的内部实现细节很有用。
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'
}
protected 的 makeSound 方法在 Animal 和 Dog 中可访问,但不能从 main() 访问。公共方法可以在需要时公开 protected 功能。
protected 构造函数
protected 构造函数只能由类本身或其子类调用。这对于工厂模式或限制实例化很有用。
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 ''
}
Parent 类只能通过其工厂方法或子类实例化。这控制了实例的创建方式,同时允许继承。
protected 和 Internal 冲突
当一个成员既是 protected 又是 internal 时,它对子类和模块成员可见。这展示了修饰符如何在 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
}
secret 属性在子类和同一模块中都可访问。internal 修饰符将 protected 可见性扩展到模块内。
protected 在接口中
默认情况下,Kotlin 接口不能有 protected 成员。所有接口成员都是公共的。此示例展示了使用抽象类的解决方法。
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'
}
通过使用实现接口的抽象类,我们可以添加 protected 成员。Car 类继承了接口和 protected 成员,同时将它们对外部代码隐藏。
protected 在密封类中
密封类通常使用 protected 构造函数来控制继承。这限制了子类化到声明密封类的文件。
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 ''
}
密封类的 protected 构造函数阻止直接实例化,同时允许预定义的子类。这是限制层次结构的常见模式。
protected 在伴生对象中
伴生对象成员可以是 protected 的,使其仅对类及其子类可见。这对于共享实现细节很有用。
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'
}
protected 伴生成员在 Logger 和 FileLogger 中可访问,但不能在外部访问。这共享了实现,同时将其对用户隐藏。
protected 的最佳实践
- 谨慎使用: 仅当子类需要直接访问成员时才将成员设为 protected。
- 记录 protected 成员: 清楚地记录 protected API,因为它们是您类契约的一部分。
- 考虑替代方案: 有时组合比公开 protected 成员更好。
- 注意过度使用: 太多 protected 成员会使子类化变得复杂。
- 与其他修饰符结合使用: protected internal 对于框架代码很有用。
来源
本教程深入介绍了 Kotlin 的 protected 关键字,展示了它与属性、方法、构造函数和特殊类类型的用法。正确使用 protected 可见性有助于创建可维护的类层次结构,同时保持封装。
作者
列出 所有 Kotlin 教程。