最近工作中遇到一个问题,通用的工具方法,出于kotlin和java代码中都能方便调用,于是想定义到静态工具类中。但是发现使用Companion对象+@JvmStatic或object对象的方式定义的方法,通过工具转成字节码再反编译之后,查看还是跟Java中的静态方法不一样,于是改成了顶层函数的方式声明,故出此文做下对比,以记录两种定义静态方法的差别。
kotlin中定义静态方法的两种方式
1. companion object + @JvmStatic注解
class StaticClass {
companion object {
@JvmStatic
fun test() {
println("我是静态方法!")
}
}
}
但是上述方法,通过工具转成java代码后发现仍然是在内部创建了一个静态对象,然后最终转调到了静态内部类的对应方法中:
public final class StaticClass {
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
@JvmStatic
public static final void test() {
Companion.test();
}
@Metadata(
mv = {1, 6, 0},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0007¨\u0006\u0005"},
d2 = {"LStaticClass$Companion;", "", "()V", "test", "", "KotlinDemo"}
)
public static final class Companion {
@JvmStatic
public final void test() {
String var1 = "我是静态方法!";
System.out.println(var1);
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
这种方式声明静态对象,虽然是有了一个跟java中类似的静态方法,但是内部还是会多创建一个Companion对象,这点跟java是不同的。
如果在Kotlin中大量使用,则会因为内部的默认静态内部类占用内存,并且内部的所有方法都转调了一次。
2. 顶层方法
顶层方法是直接定义在.kt文件中的方法,不用声明class xxx这种形式。
// 这里通过注解修改默认的文件名,否则在java中调用时,需要在文件后+kt.test()的方式调用静态方法
@file:JvmName("StaticUtil")
fun test() {
println("我是静态方法!")
}
再通过工具转成.java文件观察:
@Metadata(
mv = {1, 6, 0},
k = 2,
d1 = {"\u0000\n\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0002\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u001a\u0006\u0010\u0002\u001a\u00020\u0001¨\u0006\u0003"},
d2 = {"main", "", "test", "KotlinDemo"}
)
@JvmName(
name = "StaticUtil"
)
public final class StaticUtil {
public static final void test() {
String var0 = "我是静态方法!";
System.out.println(var0);
}
public static final void main() {
test();
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
这下变成跟java中静态方法一样的写法了,没有多出一个名称为Compaion的内部类。
!!缺点是在IDE中搜索时,按照类名的方式无法搜到到了,要按文件名搜索。
3. object对象
import kotlin.Metadata;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 5, 1},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Lcom/yyg/test/StaticUtil;", "", "()V", "test", "", "KotlinTest"}
)
public final class StaticUtil {
@NotNull
public static final StaticUtil INSTANCE;
public final void test() {
String var1 = "我是静态方法!";
System.out.println(var1);
}
private StaticUtil() {
}
static {
StaticUtil var0 = new StaticUtil();
INSTANCE = var0;
}
}
object对象的方式和在class中定义伴生对象定义类似,但是少了一层内部类,能减少内存的占用,但是仍然有一个静态对象会和程序的生命周期相同。
最后
上述的写法是为了能方便在java和kotlin直接互调,若单纯的只考虑在kt中使用,则优先考虑拓展函数。
转换工具:
- 第一步
- 第二步