Kotlin属性覆盖Java中getset方法的编译异常

背景

Kotlin在编译成class文件时,会给属性自动生成get/set方法,如果一个Kotlin类覆盖了Java父类的同名get/set方法,会导致编译异常。编译时会提醒存在相同的方法签名,导致编译失败。这篇文章简单介绍这种情况出现的原因、场景和解决方案。

一、编译失败的原因

1.1、Kotlin属性自动生成get/set

在Kotlin中,当你定义一个属性时,编译器会自动为你生成对应的getter和setter方法。而Kotlin本身可以直接使用对象.属性的方式获取/设置属性值:

kotlin 复制代码
class Person {
    var name: String = "John Doe"
}
val person = Person()
person.name = "Jane Doe"  // 调用setter
println(person.name)  // 调用getter

Kotlin自动生成的get/set是遵循驼峰命名规则的,所以如果通过Java调用就会如下:

java 复制代码
Person person = new Person("John Doe");
String name = person.getName();  // 获取属性
person.setName("Jane Doe");  // 设置属性

2.2、Java倾向于使用get/set定义属性

在Kotlin中,接口也是可以定义抽象属性,让最终实现接口的子类实现属性:

kotlin 复制代码
interface MyInterface {
    var prop: Int  // 抽象的

    val propertyWithImplementation: String
        get() = "foo"
}

class MyClass : MyInterface {
    override var prop: Int = 29  // 提供实现
}

但是在Java中,接口不能像类那样有实例字段。但是,接口可以有静态的不可变字段(也就是常量)。例如:

java 复制代码
public interface MyInterface {
    String MY_CONSTANT = "SomeValue";
}

在这个例子中,MY_CONSTANT是一个常量,它的值不能被改变。

如果你想要在接口中定义一些属性,然后让实现这个接口的类去实现这些属性,只能使用抽象方法。例如:

java 复制代码
public interface MyInterface {
    void setMyValue(String value);
    String getMyValue();
}

2.3 当Kotlin通过属性实现Java的get/set方法时

当Kotlin类实现Java接口时,如果试图通过自动生成的get/set覆盖Java接口的get/set,方法签名就会重复导致编译失败:

kotlin 复制代码
class MyUser : UserDetails {

        var username: String? = null

        var password: String? = null
        
        override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
            return mutableListOf()
        }

        override fun getPassword(): String? {
            return password
        }

        override fun getUsername(): String? {
            return username
        }

        override fun isAccountNonExpired(): Boolean {
            return true
        }

        override fun isAccountNonLocked(): Boolean {
            return true
        }

        override fun isCredentialsNonExpired(): Boolean {
            return true
        }

        override fun isEnabled(): Boolean {
            return true
        }
    }
  1. 这里编译就会产生签名重复的错误。
  2. 如果试图去除override fun getPassword(): String?函数和override fun getUsername(): String?函数,编译会出现未实现抽象方法的错误。
  3. 如果尝试在usernamepassword属性上写override关键字,也会报错,因为Java接口根本没有抽象属性供子类覆盖。

二、出现的场景

这种编译错误一般出现在Kotlin试图直接通过属性自动生成get/set实现Java接口定义的get/set函数的时候,例如上面例子中,实现Spring Security的UserDetails

三、解决方案

3.1、修改属性名称

既然Kotlin是根据驼峰自动生成get/set,那么将属性的名称修改一下,使得生成的get/set和Java接口不一致就可以了:

kotlin 复制代码
class MyUser : UserDetails {

        var username_: String? = null

        var password_: String? = null
        
        override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
            return mutableListOf()
        }

        override fun getPassword(): String? {
            return password_
        }

        override fun getUsername(): String? {
            return username_
        }

        override fun isAccountNonExpired(): Boolean {
            return true
        }

        override fun isAccountNonLocked(): Boolean {
            return true
        }

        override fun isCredentialsNonExpired(): Boolean {
            return true
        }

        override fun isEnabled(): Boolean {
            return true
        }
    }

3.1.1、优点:

  1. 简单修改属性名称就可以解决问题。

3.1.2、缺点:

  1. 虽然能够实现获取设置属性的功能,但是属性名称和接口想要表达的get/set命名不统一(不符合驼峰),违反直觉约定。
  2. 在上面的例子中,通过外部数据如SQL查询,获取结果可能无法正确映射set到对应属性。

3.2、修改get/set编译后名称

在Kotlin中有一个注解,可以让我们自定义get/set方法的名称:

kotlin 复制代码
class MyUser : UserDetails {

        var username: String? = null
            @JvmName("getUsernameForKt") get

        var password: String? = null
            @JvmName("getPasswordForKt") get
        override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
            return mutableListOf()
        }

        override fun getPassword(): String? {
            return password
        }

        override fun getUsername(): String? {
            return username
        }

        override fun isAccountNonExpired(): Boolean {
            return true
        }

        override fun isAccountNonLocked(): Boolean {
            return true
        }

        override fun isCredentialsNonExpired(): Boolean {
            return true
        }

        override fun isEnabled(): Boolean {
            return true
        }
    }

3.2.1、优点

  1. 保持属性名称不变,符合直觉约定。
  2. 由于1的关系,各种框架在映射时不会出现属性对应不上的问题。

3.2.2、缺点

  1. 在某些通过get方法获取类属性的框架中,@JvmName生成的函数和继承的get函数会被认为是两个属性。如Jackson序列化的Json会出现两个值一样的字段,这点需要注意。

四、总结

签名冲突,在Kotlin官方文档中提到了@JvmName注解来解决 kotlinlang.org/docs/java-t...

个人也推荐使用该注解解决冲突。

相关推荐
M-Robots echo17 小时前
轻量化裁剪方案解析:M-Robots 模块化构建,适配高低端各类机器人硬件平台
java·机器人·ros·开源社区·atomgit·m-robots
不可求~17 小时前
多模型路由怎么落地:把任务分流写进项目的最小实现
java·前端·数据库
Kapaseker17 小时前
破坏性更新 - 解读 Jetpack Compose 1.12
android·kotlin
mldong18 小时前
用 DDD 设计工作流引擎:聚合根与充血模型
java·架构
mqiqe19 小时前
AgentScope Java Harness:4. 双层记忆系统 让 Agent 拥有真正的“长期大脑“
java·开发语言
伍一5119 小时前
03-文件管理模块-一个看似简单的模块藏着多少细节
java·ruoyi·若依
gugucoding19 小时前
46. 【Java】JUC并发工具:让并发更简单
java·开发语言
Nebula_g20 小时前
JavaSE基础语法:面向对象高级(代码中的成分)
java·开发语言·编程·javase·技术栈·高级语法
AC赳赳老秦20 小时前
公开图片 OCR 数据提取:OpenClaw 合规采集公开信息图,识别文字与表格并转化为结构化数据
java·大数据·前端·数据库·python·php·openclaw
小强库计算机毕业设计1 天前
SpringBoot+Vue3 学生宿舍管理系统实战
java·spring boot·后端·vue·学生宿舍管理系统