ZetCode

Kotlin protected 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的可见性修饰符控制对类成员的访问。protected 关键字将可见性限制为该类及其子类。本教程通过实际例子深入探讨 protected 修饰符。

基本定义

Kotlin 中的 protected 修饰符使成员在其类和子类中可见。与 Java 不同,Kotlin 的 protected 成员在同一包中不可见。Protected 适用于类成员,而不是顶级声明。

基本 protected 属性

一个 protected 属性只能在其声明类和任何子类中访问。这提供了封装,同时允许继承。

ProtectedProperty.kt
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,将其使用限制为类层次结构。这对于子类可能需要的内部实现细节很有用。

ProtectedMethod.kt
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 构造函数只能由类本身或其子类调用。这对于工厂模式或限制实例化很有用。

ProtectedConstructor.kt
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 中组合。

ProtectedInternal.kt
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 成员。所有接口成员都是公共的。此示例展示了使用抽象类的解决方法。

ProtectedInterface.kt
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 构造函数来控制继承。这限制了子类化到声明密封类的文件。

ProtectedSealed.kt
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 的,使其仅对类及其子类可见。这对于共享实现细节很有用。

ProtectedCompanion.kt
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 的最佳实践

来源

Kotlin 可见性修饰符文档

本教程深入介绍了 Kotlin 的 protected 关键字,展示了它与属性、方法、构造函数和特殊类类型的用法。正确使用 protected 可见性有助于创建可维护的类层次结构,同时保持封装。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已撰写了 1400 多篇文章和 8 本电子书。我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程