Kotlin 注解 第三篇 @JvmField 与 @JvmStatic

本文深入探讨了Kotlin中的@JvmField和@JvmStatic注解的应用,讲解了如何在Java中直接访问Kotlin的属性和静态成员,以及如何通过companion object实现静态功能。

本文是既 JvmName 注解在 Kotlin 中的应用JvmMultifile 注解在 Kotlin 中的应用的第三篇关于 Kotlin的注解文章。

介绍的内容比较简单,主要是包含了JvmField和JvmStatic两个。

@JvmField

示例代码声明

1
2
3
package com.example.jvmannotationsample

class Developer (@JvmField val name: String, val ide: String)

使用@JvmField,我们在Java中调用的时候,可以直接使用属性名,而不是对应的getter方法。

调用代码对比

1
2
3
4
//test jvmField
Developer developer = new Developer("Andy", "Android Studio");
System.out.println(developer.getIde());// not using JvmField
System.out.println(developer.name);// using JvmField

@JvmStatic

除此之外,对于静态属性和静态方法的实现,我们也可以使用@JvmStatic实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.example.jvmannotationsample

class Sample {
    companion object {
        @JvmStatic
        val TAG_NAME = "Sample"

        val NON_STATIC_VALUE = "non_static_value"

        @JvmStatic fun callStatic() {

        }

        fun callNonStatic() {

        }
    }
}

调用代码如下

1
2
3
4
5
6
//JVM static method
Sample.callStatic();
Sample.Companion.callNonStatic();

Sample.getTAG_NAME();
Sample.Companion.getNON_STATIC_VALUE();

Companion

Kotlin中我们可以借助object实现静态的形式,比如下面的代码

1
2
3
4
5
6
7
8
9
package com.example.jvmannotationsample

class SomeClass {
    companion object {
        fun getCommonProperties(): List<String> {
            return emptyList()
        }
    }
}

其实除此之外,我们还能命名companion的名称,如下代码

1
2
3
4
5
6
7
8
9
package com.example.jvmannotationsample

class AnotherClass {
    companion object Assistant {
        fun scheduleSomething() {

        }
    }
}

调用代码示例

1
2
3
//test companion
SomeClass.Companion.getCommonProperties();
AnotherClass.Assistant.scheduleSomething();

相关文章推荐

<think> 我们正在详细讨论Kotlin中用于静态成员的三个关键注解@JvmField、const和@JvmStatic。用户特别询问了它们在实现静态方法时的区别,但需要注意的是,const和@JvmField主要用于属性(字段),而@JvmStatic用于方法。不过,为了全面性,我们将涵盖所有三者,并明确区分它们在方法和属性上的应用。 首先,让我们明确每个注解的核心用途: 1. **@JvmField**: 用于属性,指示Kotlin编译器不要为该属性生成getter/setter,而是将其作为公开的字段(Java的public字段类似)。这使得在Java中可以直接访问该字段,而不需要通过getter/setter方法。 2. **const**: 用于编译时常量,要求属性必须是顶层属性或声明在object中的属性,并且类型为原始类型或String。它使得该属性在编译时被内联到使用的地方,并且在字节码中生成一个public static final字段(在Java中可以直接通过类名访问)。 3. **@JvmStatic**: 用于方法或属性,当它应用于object或companion object中的成员时,会生成一个静态方法(或静态字段)在包含该object的类中(如果是companion object,则生成在伴生类中),这样在Java中就可以像调用静态方法一样调用。 现在,我们分别详细说明它们在静态方法(以及属性)上的区别: ### 1. `const val`:编译时常量 - **作用对象**:仅用于属性(val,不可变)。 - **要求**: - 必须为顶级属性(直接定义在文件内)或声明在`object`(或`companion object`)内。 - 只能是基本类型(`Int`、`Long`、`Char`等)或`String`。 - **效果**: - 在字节码中生成`public static final`字段。 - 在编译时,常量值会被内联到使用的位置(类似于C++的宏替换)。 - **Java中使用方式**:`类名.常量名`(如果是伴生对象内的常量,则通过`类名.常量名`访问,因为Kotlin编译器会将其提升为外部类的静态字段)。 - **示例**: ```kotlin // 文件:Constants.kt const val PI = 3.14 // 顶层常量 class MyClass { companion object { const val MAX = 100 // 伴生对象内的常量 } } ``` 在Java中: ```java double value = Constants.PI; // 顶层常量 int max = MyClass.MAX; // 伴生对象内的常量,直接通过类名访问 ``` ### 2. `@JvmField`:暴露字段 - **作用对象**:属性(可以是var或val)。 - **要求**: - 属性不能有自定义的getter/setter。 - 不能是private、protected或internal(除非在同一个模块)的属性。 - 通常用于object或companion object中的属性,或者普通类的属性(但普通类的属性使用@JvmField会变成实例字段,而不是静态字段,所以通常我们只在object或companion object中使用来生成静态字段)。 - **效果**: - 在字节码中生成一个public的字段(非final,如果是var)。 - 如果是object或companion object中的属性,则生成静态字段(static field)。 - **Java中使用方式**:`类名.字段名`(对于object或companion object中的属性)或`对象实例.字段名`(对于普通类的实例属性)。 - **示例**: ```kotlin class MyClass { companion object { @JvmField val ID = "ID123" // 静态字段 } @JvmField var name: String? = null // 实例字段(非静态) } object Singleton { @JvmField val VERSION = "1.0" } ``` 在Java中: ```java String id = MyClass.ID; // 静态字段,直接通过类名访问 String version = Singleton.VERSION; // 静态字段 MyClass obj = new MyClass(); obj.name = "Tom"; // 实例字段 ``` ### 3. `@JvmStatic`:生成静态方法或静态字段 - **作用对象**:方法或属性(注意:当作用于属性时,它会为该属性生成静态的getter/setter方法,而不是静态字段)。 - **要求**:必须用于object或companion object中的成员。 - **效果**: - 对于方法:生成一个静态方法在包含该object的类中(如果是companion object,则生成在外部类中)。 - 对于属性:为该属性生成静态的getter和setter(如果是var)方法,但不会生成静态字段(除非同时使用@JvmField)。换句话说,属性本身仍然存储在object实例中,只是提供了静态的访问方法。 - **Java中使用方式**:`类名.方法名()` 或 `类名.属性名()`(通过getter/setter)。 - **示例**: ```kotlin class MyClass { companion object { @JvmStatic fun foo() {} // 生成静态方法foo @JvmStatic var count: Int = 0 // 生成静态的getCount和setCount方法 } } object Singleton { @JvmStatic fun bar() {} // 生成静态方法bar } ``` 在Java中: ```java MyClass.foo(); // 调用静态方法 MyClass.setCount(10); // 调用静态setter int count = MyClass.getCount(); // 调用静态getter Singleton.bar(); // 调用静态方法 ``` ### 三者在静态成员上的对比 | 特性 | `const val` | `@JvmField` | `@JvmStatic` | |----------------------|----------------------------|----------------------------|--------------------------------------| | **作用对象** | 属性(仅val) | 属性(var/val) | 方法或属性 | | **静态字段生成** | 是(public static final) | 是(public static,非final)| 否(对于属性,生成静态方法而非字段) | | **静态方法生成** | 否 | 否 | 是(对于方法) | | **编译时常量内联** | 是 | 否 | 否 | | **适用位置** | 顶层或object/companion object | 任何(但只有object/companion object中才是静态) | object或companion object | | **Java访问方式** | `类名.字段名` | `类名.字段名`(静态字段) | `类名.方法名()` 或 `类名.get属性名()` | | **是否可写(可变)** | 不可变 | 是(如果是var) | 是(如果是var,通过setter) | ### 如何选择? - 如果你需要定义一个**编译时常量**(基本类型或String),使用`const val`。它在编译时内联,效率最高,且生成public static final字段。 - 如果你需要一个**静态字段**(可以是可变的,或者类型不是基本类型/String),并且在Java中希望直接访问字段(而不是通过getter/setter),则在object或companion object中使用`@JvmField`。 - 如果你希望object或companion object中的**方法**在Java中可以像静态方法一样通过类名调用,或者为属性生成静态的getter/setter(而不是静态字段),则使用`@JvmStatic`。 ### 注意事项 - 在companion object中,`const val`和`@JvmField`都会在外部类中生成静态字段,而`@JvmStatic`方法则会在外部类中生成静态方法。但是,`@JvmStatic`作用于属性时,不会生成静态字段,而是生成静态的访问器方法。 - 在普通类中,`@JvmField`作用在实例属性上,会生成一个public的实例字段(非静态),这在需要Java框架互操作时有用(例如,某些框架要求字段为public)。 - 在object中,`@JvmField`和`@JvmStatic`都可以用于静态成员,但它们的表现不同:`@JvmField`生成静态字段,`@JvmStatic`生成静态方法(对于方法)或静态访问器(对于属性)。 ### 示例总结 假设我们有一个伴生对象: ```kotlin class Example { companion object { const val CONST_VAL = 100 @JvmField val jvmFieldVal = 200 @JvmStatic val jvmStaticVal = 300 // 不推荐这样写,因为实际上会生成getJvmStaticVal()静态方法,而字段是非静态的 @JvmStatic fun jvmStaticFun() {} } } ``` 在Java中访问: ```java // const val 生成静态字段 int constVal = Example.CONST_VAL; // @JvmField 生成静态字段 int jvmFieldVal = Example.jvmFieldVal; // @JvmStatic 属性生成静态getter int jvmStaticVal = Example.getJvmStaticVal(); // 注意:这里实际上是调用getter // @JvmStatic 方法生成静态方法 Example.jvmStaticFun(); ``` 注意:对于`jvmStaticVal`,尽管我们使用了`@JvmStatic`,但它在伴生对象中仍然是一个实例属性,所以实际上Example类中并没有静态字段`jvmStaticVal`,而是有一个静态的getter方法`getJvmStaticVal()`。因此,如果你希望在Java中直接访问字段,应该使用`@JvmField`而不是`@JvmStatic`来注解属性。 ### 结论 - 对于常量,优先使用`const val`。 - 对于需要公开静态字段的情况,使用`@JvmField`。 - 对于需要公开静态方法的情况,使用`@JvmStatic`(在方法上)。 - 避免在属性上同时使用`@JvmField`和`@JvmStatic`,因为它们是互斥的(一个生成字段,一个生成访问器方法)。 通过上述分析,我们可以清楚地看到三者的区别,并根据需求选择适当的注解。 §§相关问题§§ 1. 在Kotlin中,如何为伴生对象中的属性生成Java风格的静态getter和setter? 2. `@JvmField`能否用于普通类的非静态属性?如果能,会有什么效果? 3. 为什么在Kotlin中,object或companion object中的常量推荐使用`const val`而不是`@JvmField`? </think> ### `@JvmField`, `const`, `@JvmStatic` 在静态成员实现中的区别详解 在 Kotlin 中实现 Java 风格的静态成员时,三个关键注解的适用场景和效果有显著差异: | 特性 | 适用对象 | 字节码效果 | Java 调用方式 | 典型场景 | |--------------|----------------|-------------------------------------|--------------------------|-----------------------------| | **`const val`** | 基础类型/字符串常量 | 生成 `public static final` 字段 | `类名.常量名` | 编译时常量(如配置值) | | **`@JvmField`** | 任意属性 | 跳过 getter/setter,生成 public 字段 | `类名.字段名` | 高频访问字段/框架兼容性 | | **`@JvmStatic`** | 方法 | 生成静态方法 | `类名.方法名()` | 工具类方法 | #### 1. `const val`:编译时常量 ```kotlin class Constants { companion object { const val API_KEY = "a1b2c3" // 编译时常量 } } ``` **效果**: - 生成真正的 Java 静态常量:`public static final String API_KEY = "a1b2c3";` - **强制限制**:只能是基本类型或 String - Java 调用:`Constants.API_KEY` - ✅ 优势:编译时内联优化,无运行时开销[^4] #### 2. `@JvmField`:字段暴露 ```kotlin class Config { companion object { @JvmField var TIMEOUT = 5000 // 可变静态字段 } } ``` **效果**: - 生成 public 字段:`public static int TIMEOUT = 5000;` - **无类型限制**(支持任意类型字段) - Java 调用:`Config.TIMEOUT = 6000;` - ⚠️ 注意:会绕过 getter/setter,**不能添加校验逻辑**[^2] - 典型场景:Android 的 `View` 属性注解 `@JvmField val recyclerView: RecyclerView` #### 3. `@JvmStatic`:静态方法生成 ```kotlin class StringUtils { companion object { @JvmStatic fun isEmpty(s: String?) = s.isNullOrEmpty() } } ``` **效果**: - 生成静态方法:`public static boolean isEmpty(String s)` - Java 调用:`StringUtils.isEmpty(text);` - ❌ **误区**:作用于属性时仅生成静态 getter/setter,**不会生成字段**: ```java // 错误期待:Config.getCount() // 实际效果:Config.getCount() ``` --- ### 组合使用场景 #### 伴生对象中的静态常量 ```kotlin class Device { companion object { // 常量优先用 const const val MODEL = "X-100" // 非常量静态字段用 @JvmField @JvmField var activeCount = 0 } } ``` Java 调用: ```java String model = Device.MODEL; // 常量直接访问 Device.activeCount = 5; // 字段直接赋值 ``` #### 静态工具类 ```kotlin object MathUtil { const val PI = 3.1415926 @JvmStatic fun circleArea(r: Double) = PI * r * r } ``` Java 调用: ```java double pi = MathUtil.PI; // 常量 double area = MathUtil.circleArea(2.5); // 静态方法 ``` --- ### ⚠️ 常见误区澄清 1. **`@JvmStatic` 修饰属性 ≠ 静态字段** 错误示例: ```kotlin companion object { @JvmStatic var count = 0 // 生成 getCount()/setCount() 方法而非字段 } ``` Java 中仍需调用:`MyClass.setCount(10);`[^2] 2. **`const` `@JvmField` 互斥** `const` 已隐含静态常量语义,不可组合使用: ```kotlin // ❌ 编译错误 @JvmField const val NAME = "Kotlin" ``` 3. **可见性规则差异** | 注解 | 允许的可见性 | |--------------|------------------------| | `const val` | 仅 `public` | | `@JvmField` | `public`/`internal` | | `@JvmStatic` | 同所在作用域可见性 | --- ### 总结选择策略 1. **需要编译时常量** ➜ `const val`(仅限基础类型/String) 2. **需要公开静态字段** ➜ `@JvmField`(支持任意类型) 3. **需要静态工具方法** ➜ `@JvmStatic`(作用于方法) 4. **避免三者混用**:优先用单一方案满足需求 > 最佳实践:在 `companion object` 中,**常量用 `const val`,变量字段用 `@JvmField`,方法用 `@JvmStatic`**[^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值