1. 概述

在本教程中,我们将回顾 Java 中的 static final 变量,并学习如何在 Kotlin 中实现类似的功能。

在 Java 中,使用 static final 声明的变量通常用于表示常量。而在 Kotlin 中,我们有多种方式可以达到相同的目的。


2. 在 Kotlin object 中定义常量

首先,我们来看在 Kotlin 的 object 中定义常量的方式:

object TestKotlinConstantObject {
    const val COMPILE_TIME_CONST = 10

    val RUN_TIME_CONST: Int
    init {
        RUN_TIME_CONST = TestKotlinConstantObject.COMPILE_TIME_CONST + 20
    }
}

在这个例子中,我们使用:

  • const val 定义编译期常量(compile-time constant)
  • val 定义运行期常量(run-time constant)

在 Kotlin 中可以直接像访问 Java 的 static final 变量一样使用它们:

// Kotlin
assertEquals(10, TestKotlinConstantObject.COMPILE_TIME_CONST)
assertEquals(30, TestKotlinConstantObject.RUN_TIME_CONST)

⚠️ 注意: 我们不能在 Java 中直接使用 TestKotlinConstantObject.RUN_TIME_CONST
因为 Kotlin 中的 val 变量默认不会暴露为 Java 可访问的 public static final 字段。

在 Java 中要访问 RUN_TIME_CONST,需要通过 INSTANCE 调用 getter 方法:

// Java
assertEquals(10, TestKotlinConstantObject.COMPILE_TIME_CONST);
assertEquals(30, TestKotlinConstantObject.INSTANCE.getRUN_TIME_CONST());

解决方案: 使用 @JvmField 注解可以将 val 变量暴露为 Java 中的 public static final 字段:

@JvmField val JAVA_STATIC_FINAL_FIELD = 20

这样就可以像 Java 的 static final 一样访问:

// Kotlin
assertEquals(20, TestKotlinConstantObject.JAVA_STATIC_FINAL_FIELD)
// Java
assertEquals(20, TestKotlinConstantObject.JAVA_STATIC_FINAL_FIELD);

📌 另外,我们也可以使用 @JvmStatic,但需要注意:

@JvmStatic 会为属性的 getter/setter 生成静态方法,但并不会将字段本身变为 static final


3. 在 Kotlin class 的 companion object 中定义常量

在 Kotlin 的类中定义常量,通常放在 companion object 中:

class TestKotlinConstantClass { 
    companion object { 
        const val COMPANION_OBJECT_NUMBER = 40 
    } 
}

调用方式与 object 类似:

// Kotlin
assertEquals(40, TestKotlinConstantClass.COMPANION_OBJECT_NUMBER)
// Java
assertEquals(40, TestKotlinConstantClass.COMPANION_OBJECT_NUMBER);

4. 在 .kt 文件顶层定义常量

除了在 objectcompanion object 中定义,我们还可以直接在 Kotlin 文件的顶层定义常量。

例如:

// KotlinFile.kt

package com.baeldung.constant

const val VALUE_IN_KT_FILE = 42
val greeting = "Hello"

在 Kotlin 中可以直接使用:

// Kotlin
assertEquals(42, VALUE_IN_KT_FILE)
assertEquals("Hello", greeting)

但在 Java 中不能直接访问这些变量,因为 Java 要求变量必须属于某个类或对象。

编译器会将 .kt 文件编译为一个类,类名由文件名决定:

  • 文件名首字母大写:myKotlin.ktMyKotlinKt
  • 首字符非法则加 _7myKotlin.kt_7myKotlinKt
  • 替换 ._my.Kotlin.ktMy_KotlinKt

因此在 Java 中访问方式如下:

assertEquals(42, KotlinFileKt.VALUE_IN_KT_FILE);
assertEquals("Hello", KotlinFileKt.getGreeting());

📌 和 object 中一样:

  • const val 可以直接访问
  • val 需要通过 getter 方法访问

优化建议: 使用 @file:JvmName 注解自定义编译后的类名,提高可读性和可控性:

// KotlinFileWithAnnotation.kt

@file:JvmName("NiceKotlinUtil")

package com.baeldung.constant

const val VALUE_IN_KT_FILE_WITH_ANNOTATION = 4242
@JvmField val greetingFromFileWithAnnotation = "Hello world"

这样在 Java 中就可以这样访问:

// Java
assertEquals(4242, NiceKotlinUtil.VALUE_IN_KT_FILE_WITH_ANNOTATION);
assertEquals("Hello world", NiceKotlinUtil.greetingFromFileWithAnnotation);

5. 总结

本文我们介绍了在 Kotlin 中实现 Java static final 变量的几种方式:

方式 说明 Java 可见性
const val 编译期常量
val 运行期常量 ❌(需通过 INSTANCE.getXXX()
@JvmField val 将变量暴露为 Java 的 public static final
companion object 中的 const val 类似 Java 静态常量
.kt 文件顶层 const val 默认生成类名 FilenameKt
.kt 文件加 @file:JvmName + @JvmField 更加灵活和 Java 友好

📌 踩坑提醒:

  • Kotlin 的 val 不会自动暴露为 Java 的 static final
  • 文件名转换规则复杂,建议用 @file:JvmName 显式控制类名
  • 需要 Java 可见的常量,记得加 @JvmField

完整的代码示例可在 GitHub 上找到。


原始标题:Creating Java static final Equivalents in Kotlin