Kotlin 中实现静态方法调用的 四种方式--按需选择

本文介绍了Kotlin中的包级函数、伴生对象、扩展函数、对象声明等特性,并通过实例展示了这些特性的使用方法。

 从 Java 学习 Kotlin 语音很容易上手,只要有了基本的编程思想,学起来还是很快的,关键在于多练习,多使用。

 Kotlin 使用起来似乎比 Java 简单多了,从这个静态方法可见一斑。主要方法有包级函数、伴生对象、扩展函数、对象声明等,

这篇文章讲的很详细,https://www.jb51.net/article/115037.htm , 这里主要记录备忘及简单拓展

一、包级函数

Kotlin和Java及C#不同的是,可以在包里面直接声明函数。做法和类中是一样的,这里就不多说了,的确是一个非常好的选择。适用于函数不需要不包内部的类进行数据共享的方法。

新建一个 kotlin 文件,在里面声明方法即可
fun main(args: Array<String>) {
    packageFunction()
}

fun packageFunction(){
    println("这是包级函数")
}

二、伴生对象

从语义上来讲,伴生函数与Java中静态方法最为相近,所以用伴生对象完全可以实现Java中静态类的所有内容。但在Java中使用静态方法有时是没有办法的选择,在Kotlin中伴生对象只是我们的一种选择。下面我就介绍一下伴生对象。
大家在使用Java的时不知有没有注意到,Java类中的静态变量及方法是在什么时候初始化的?是在静态方法第一次调用时还是在相应的Java类被加载时?答案是在Java类被加载时(也就是说如果你调用过Java类中的实例变量及方法后,即使你没有使用静态变量及方法,静态变量已经初始化了)。这种现象用伴生来描述是不是很贴切。

在Kotlin中,认为一个类中有两类东东,一类是Java类中的实例变量及方法,另一类是Java类中的静态变量及方法。Kotlin将静态实例及方法统一打包到一个伴生类中,就是这个样子啦。下面给出一个例子:

fun main(args: Array<String>) {
 Books.getBestSellers()
}
class Books(var name: String, val page: Int) {
  fun getWordCount()=page*100
  companion object ComBooks{
    var bestSellers=arrayOf("Harry Potter\r\t","Lord of the Rings\r\t")
    fun getBestSellers() {
      bestSellers.forEach{v->println(v)}
    }
  }
}

伴生类是用companion来声明的,他在伴生对象所在的类被加载,伴生对象被初始化,与Java静态成员一样。其可以匿名或者与包含他的类类名相同。调用有两种: Books.ComBooks.getBestSellsers()或 Books.getBestSellsers()。

三、扩展函数

在Java中,我们经常用写Utils类,这些类往往都是针对某一个对象对其功能进行与自身程序相匹配的操作。其中的方法以静态方法居多,比方说:

public class Utils {

  public static boolean isEmpty(String string){
    return string != null && string.length() == 0;
  }

  public static boolean isWeakEmpty(String string){
    return isEmpty(string) && string.trim().length() == 0;
  }
}

我们当然可以用上面的两种方法来实现这些静态方法,但是我们有更好的方法。

fun String.isEmpty() = this != null && this.length == 0;

fun String.isWeakEmpty()= this.isEmpty() && this.trim().length == 0

上面两行代码给String这个类扩展了两个函数,这两个函数就可以像其原生函数一样调用,代码十分优美。其实扩展函数并没有修改String类内部的东东,也只是给String加了两个静态函数,但相比Java的Utils类,可读性有了非常大的提高。

四、对象声明

继续考虑上面的Utils类,这个类中所有的方法(有时也有可能有变量)都是静态的,这个方法根本没有必要实例化,在Java中我们经常将此种类声明为静态类,在Kotlin中针对这种情况有没有好的对应方案呢?有没有比伴生对象更好的方案呢?当然是有的,对象声明就是一种。

对象声明非常好理解,就是用object关键字声明了一个对象,对象里面可以用变量也可以有方法,如:

object AppInfo{
  var AppName = "Kotlin Message"
  var AppAuthor = "Riley Ge"
  fun toSimpleString() {
    println("AppName:$AppName,AppAuthor:$AppAuthor") 
  }
}

发现Kotlin的对象真是强大!有一点大家注意一下,AppInfo是在第一次被访问到时延迟初始化的,也就是说在对象声明时AppInfo并没有初始化。

五、总结

说了这么多方法,现在没有人在担心Kotlin没有静态方法了吧,Kotlin没有只是因为他可以做的更好。而且Kotlin也给了大家更多的选择,大家可以根据自己的实际情况选用合适的方法,让自己的代码高效而优美。

 

<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]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值