Java和Kotlin的Field在继承中的不同表现

本文比较了Java和Kotlin中Field在继承中的表现,Java的域有遮蔽但无覆盖,Kotlin则支持遮蔽和覆盖。Kotlin的open和val特性带来了一些值得注意的设计差异,如对不可变值的操作问题。

Kotlin是一个宣称与Java兼容性较好的语言,但在接触后发现一些技术还是有“概念上”的冲突,本文就记录下两者对象的Field(中文的说法有字段、域、属性、成员变量,下文若出现这些表达,指的都是这个东西)在继承中的不同表现。

Java中Field在继承中的表现

首先来看一段简单的程序:

public class FieldInheritDemo {
    public static void main(String[] args) {
        Child child = new Child();
        Parent parent = child;
        System.out.println(child.a + "-" + parent.a);// => 8-1
        child.a = 88;
        System.out.println(child.a + "-" + parent.a);// => 88-1
        parent.a = 11;
        System.out.println(child.a + "-" + parent.a);// => 88-11

        System.out.println(child.b + "-" + parent.b);// => 9-2
        child.b = 99;
        System.out.println(child.b + "-" + parent.b);// => 99-2
//        parent.b = 22; //error: 不能对final变量赋值
        parent.printParent();// => 11-2-11-2-88-99
        child.printChild();// => 88-99-88-99-11-2
    }
}

class Parent {
    public int a = 1;
    public final int b = 2;

    public void printParent() {
        System.out.println(a + "-" + b + "-" + this.a + "-" + this.b + "-" + ((Child) this).a + "-" + ((Child) this).b);
    }
}

class Child extends Parent {
    public int a = 8;
    public int b = 9;

    public void printChild() {
        System.out.println(a + "-" + b + "-" + this.a + "-" + this.b + "-" + super.a + "-" + super.b);
    }
}

从输出结果来看,Java的域有“遮蔽”的现象,但是没有“覆盖”或“重写”的现象。具体引用的是父类的域还是子类的域取决于变量的类型,而非对象的实际类型。this虽然是动态变量,但是在Parent中它仍然是this

Kotlin中Field在继承中的表现

同样来看一段和上面相似的程序:

fun main(args: Array<String>) {
    val child: Child = Child()
    val parent: Parent = child

    println("${child.a}-${parent.a}")// => 8-8
    child.a = 88
    println("${child.a}-${parent.a}")// => 88-88
    parent.a = 11
    println("${child.a}-${parent.a}")// => 11-11

    println("${child.b}-${parent.b}")// => 9-9
    child.b = 99;
    println("${child.b}-${parent.b}")// => 99-99
//    parent.b = 22; //error: 不能对val变量赋值

    parent.printParent()// => 11-99-11-99-11-99
    child.printChild()// => 11-99-11-99-1-2
}

open class Parent {
    open var a: Int = 1
    open val b: Int = 2

    fun printParent() {
        println("$a-$b-${this.a}-${this.b}-${(this as Child).a}-${(this as Child).b}")
    }
}

class Child : Parent() {
    override var a: Int = 8
    override var b: Int = 9;

    fun printChild() {
        println("$a-$b-${this.a}-${this.b}-${super.a}-${super.b}")
    }
}

Kotlin中的输出结果来看,“遮蔽”、“覆盖”现象都存在,跟方法一样,其实只要看字节码就可以发现对Field的读写都是调方法,比如child.a = 88这行,字节码中就包含INVOKEVIRTUAL Parent.setA (I)V

但是,Kotlin中有两个需要注意的点:

  1. super的行为还是和Java类似,并非Parent.setA之类的过程调用。
  2. openval同时修饰一个域的时候,这个域可能会变,例如上面parent.b,我们没法对其赋值,但是它的值却一直在变。(没错,不可变的值看上去变了。。。我很不喜欢这点设计,用的时候当心)
<think>我们正在讨论Kotlin中的`companion object`(伴生对象)。根据引用[1][2][3][4],伴生对象是Kotlin中用来替代其他语言(如Java)中静态成员的一种方式。它允许我们在类中定义一些与类本身关联而不是与类的实例关联的成员(属性方法),这些成员可以通过类名直接访问。 ### 作用 1. **替代静态成员**:在Kotlin中没有`static`关键字,伴生对象用于声明属于类本身的成员(而不是实例的成员),这些成员可以通过类名直接访问,无需创建类的实例[^1][^2][^3]。 2. **实现类级别的状态行为**:伴生对象可以包含属性方法,这些属性方法在类被加载时初始化,并且在整个程序运行期间只有一个实例(类似于单例)[^2][^3]。 3. **支持接口扩展**:伴生对象可以继承接口,也可以被扩展(使用扩展函数)[^1][^3]。 4. **工厂模式**:可以在伴生对象中定义工厂方法,用于创建类的实例[^3]。 ### 使用场景 1. **定义常量或工具方法**:当需要定义一些与类相关但不需要实例的常量或工具方法时,可以将它们放在伴生对象中[^2]。 ```kotlin class MyClass { companion object { const val CONSTANT = "value" fun utility() { ... } } } // 使用 MyClass.CONSTANT MyClass.utility() ``` 2. **实现工厂模式**:在伴生对象中定义工厂方法,用于创建类的实例[^3]。 ```kotlin class MyClass private constructor(val value: Int) { companion object { fun create(): MyClass = MyClass(42) } } // 使用 val instance = MyClass.create() ``` 3. **实现接口**:伴生对象可以实现接口,从而使得类名可以被当作该接口的实例使用[^1][^3]。 ```kotlin interface Factory<T> { fun create(): T } class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() } } // 使用 val factory: Factory<MyClass> = MyClass val instance = factory.create() ``` 4. **扩展函数**:可以为伴生对象定义扩展函数,从而为类添加静态方法[^3]。 ```kotlin class MyClass { companion object } fun MyClass.Companion.extension() { ... } // 使用 MyClass.extension() ``` ### 注意事项 1. **伴生对象的名称**:伴生对象可以指定名称(如`companion object Named { ... }`),但通常省略(使用默认名称`Companion`)[^1][^3]。 2. **访问权限**:伴生对象的成员可以访问类的私有成员(包括私有构造方法)[^3]。 3. **初始化时机**:伴生对象在类被加载时初始化,因此其初始化顺序与类加载顺序一致[^3]。 ### 与Java互操作 在Java中调用伴生对象的成员时,需要通过`Companion`字段(如果伴生对象没有指定名称)或者指定的名称来访问。也可以使用`@JvmStatic`注解让Kotlin编译器生成真正的静态成员,以便在Java中像调用静态方法一样调用[^2][^3]。 ### 示例 ```kotlin class MyClass { companion object { const val TAG = "MyClass" fun create(): MyClass = MyClass() } } // Kotlin中使用 val instance = MyClass.create() println(MyClass.TAG) ``` 在Java中调用: ```java MyClass.Companion.create(); System.out.println(MyClass.Companion.getTAG()); // 或者使用@JvmStatic注解后,可以直接使用静态方式调用 ``` ### 总结 `companion object`是Kotlin中用于模拟静态成员、实现类级别状态行为的重要机制。它提供了比静态成员更灵活的功能(如实现接口、扩展等),同时保持了简洁的语法。正确使用伴生对象可以提升代码的组织性可读性。 §§相关问题§§ 1. 伴生对象与对象声明(object declaration)有什么区别? 2. 如何在伴生对象中使用`@JvmStatic``@JvmField`注解优化Java互操作? 3. 伴生对象是否可以继承接口?有什么应用场景?</think>### Kotlin 中 `companion object` 的作用与使用场景 `companion object`(伴生对象)是 Kotlin 中用于替代 Java **静态成员**的核心机制,它允许在类内部定义与类本身关联(而非实例关联)的成员[^1][^2][^3]。主要作用如下: #### 一、核心作用 1. **替代静态成员** 在类中定义可通过类名直接访问的常量、函数或对象,无需创建类实例[^2]: ```kotlin class MyClass { companion object { const val MAX_COUNT = 100 // 常量 fun create(): MyClass = MyClass() // 工厂方法 } } // 直接通过类名访问 val instance = MyClass.create() println(MyClass.MAX_COUNT) ``` 2. **实现类级别的状态共享** 伴生对象在类加载时初始化,生命周期与类相同,所有实例共享同一伴生对象实例[^3]: ```kotlin class Logger { companion object { private var logCount = 0 // 类级别计数器 fun log(message: String) { println("${++logCount}: $message") } } } // 所有调用共享计数器 Logger.log("First") // 输出: 1: First Logger.log("Second") // 输出: 2: Second ``` 3. **支持接口实现** 伴生对象可以实现接口,为类提供统一的行为模板[^1][^3]: ```kotlin interface Factory<T> { fun create(): T } class MyClass { companion object : Factory<MyClass> { override fun create() = MyClass() } } // 作为工厂使用 val factory: Factory<MyClass> = MyClass ``` #### 二、关键特性 1. **命名伴生对象** 可指定名称增强可读性(默认名称为 `Companion`)[^4]: ```kotlin class MyClass { companion object Named { fun specialMethod() { ... } } } // 调用 MyClass.Named.specialMethod() ``` 2. **访问私有成员** 伴生对象能访问所在类的私有构造函数私有成员[^2]: ```kotlin class User private constructor(val name: String) { companion object { fun create(name: String): User { validate(name) // 访问私有方法 return User(name) // 访问私有构造 } private fun validate(name: String) { ... } } } ``` 3. **扩展函数支持** 可为伴生对象添加扩展函数[^3]: ```kotlin class MyClass { companion object } // 扩展函数 fun MyClass.Companion.extensionFunc() = println("Extended!") // 调用 MyClass.extensionFunc() ``` #### 三、典型使用场景 1. **工厂模式** 封装对象创建逻辑: ```kotlin class Connection { companion object { fun create(url: String): Connection { // 复杂初始化逻辑 return Connection(url) } } } ``` 2. **常量容器** 集中管理类相关常量: ```kotlin class ApiClient { companion object { const val TIMEOUT = 30_000 const val BASE_URL = "https://api.example.com" } } ``` 3. **工具函数集合** 提供与类相关的静态工具方法: ```kotlin class StringUtils { companion object { fun isEmailValid(email: String): Boolean { return Patterns.EMAIL_ADDRESS.matcher(email).matches() } } } ``` #### 四、与 Java 互操作 通过 `@JvmStatic` 注解生成真正的 Java 静态成员[^2]: ```kotlin class KotlinClass { companion object { @JvmStatic fun staticMethod() { ... } @JvmField val STATIC_FIELD = 42 } } // Java 中可直接调用 KotlinClass.staticMethod(); int value = KotlinClass.STATIC_FIELD; ``` ### 总结 `companion object` 是 Kotlin 实现**类级别成员**的标准方式,解决了以下问题: 1. 替代 Java 的 `static` 关键字 2. 支持接口实现扩展函数 3. 提供更安全的初始化机制(伴随类加载) 4. 实现工厂模式等设计模式 其设计体现了 Kotlin 的核心理念:**用对象表达式解决原本需要静态成员的问题,同时保持面向对象特性的一致性**[^1][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值