kotlin : 注解(Annotation):枚举类(enum)与@IntDef @StringDef与静态常量

时间:2025-02-08 12:16:43

LinearLayout类中:

@IntDef({HORIZONTAL, VERTICAL})
@Retention(RetentionPolicy.SOURCE)
public @interface OrientationMode {}

public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;

private int mOrientation;

public void setOrientation(@OrientationMode int orientation) {
    if (mOrientation != orientation) {
        mOrientation = orientation;
        requestLayout();
    }
}

@OrientationMode
public int getOrientation() {
    return mOrientation;
}

Java / kotlin注解定义对比:

//java 注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {}

//kotlin 注解
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation
class Test

Android中不使用枚举类(enum)替代为@IntDef @StringDef

使用注解代替枚举(enum)

enum在java中的实质是特殊单例的静态成员变量。在运行期,所有枚举类作为单例,全部加载到内存中。
所以,枚举增加了运行时的内存占用。

@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
@MustBeDocumented
@IntDef(Sex.MAN, Sex.WOMEN)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation
class Sex {
    companion object {
        const val MAN = 0x2
        const val WOMEN = 0x3
    }
}
//使用@IntDef/@StringDef + @interface来进行限定参数:
@IntDef({ItemState.ADD, ItemState.SUCCESS, ItemState.LOADING, ItemState.FAIL})
@Retention(RetentionPolicy.SOURCE)
public @interface ItemState {
    int ADD = 0;
    int SUCCESS = 1;
    int LOADING = 2;
    int FAIL = 3;
}

一、简单静态常量


import androidx.annotation.IntDef


class SexTest1 {
    companion object {
        private const val MAN = 101
        private const val WOMEN = 102
    }

    private var sex: Int = 0

    //设置性别
    fun setSex(sex: Int) {
        this.sex = sex
    }

    //获取性别
    fun getSex(): String {
        if (MAN == sex) return "男"
        return if (WOMEN == sex) "女" else "未知"
    }
}
//当我们定义了一个男女的final整型作为入参时,不一定保证入参的都是我们想要的入参
//这里就有一个 类型不安全 的问题出现,而枚举就可以解决这个问题

//fun main() {
//    val sexTest1 = SexTest1()
//    (102)
//    val sex1 = ()
//    println("====================================$sex1")
//}

二、枚举常量

Enum 是 java 中一种包含固定常数的类型
当我们需要预先定义一些值,并限定范围时,使用 Enum,来做到编写和编译都查错

  • Java 的 Enum 的实质是特殊单例的静态成员变量

  • Enum 可以在编写器,编译器做到各种静态检查防呆

  • Enum 在运行期,所有枚举类作为单例,全部加载到内存中

因为上述原因,Enum 增加了APK 的内存占用,比常量多5到10倍的内存占用
所以放弃枚举,就是关于安卓应用性能的内存占用部分的最佳实践方法之一


class SexTest2 {

    private var sex: SexEnum? = null

    enum class SexEnum {
        MAN, WOMEN
    }

    //设置性别
    fun setSex(sex: SexEnum) {
        this.sex = sex
    }

    //获取性别
    fun getSex(): String {
        if (SexEnum.MAN == sex) return "男"
        return if (SexEnum.WOMEN == sex) "女" else "未知"
    }
}
//
//利用枚举,在 setSex() 方法里面对入参做了枚举Sex的限制
//对于想输入任何非枚举类Sex里面定义的枚举常量,编译都是不能通过的
//这就很好的限制了入参混乱的问题
//
//使用 Enum 的缺点
//
//每一个枚举值都是一个单例对象,在使用它时会增加额外的内存消耗,
//          所以枚举相比与 Integer 和 String 会占用更多的内存
//较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的IO开销,使我们的应用需要更多的空间
//特别是分dex多的大型APP,枚举的初始化很容易导致ANR


//fun main() {
//    val sexTest2 = SexTest2()
//    ()
//    val sex2 = ()
//    println("====================================$sex2")
//}

三、注解

众所周知,在Android中使用Enum枚举会极大的影响 性能,内存消耗大。原因如下:

Android官方文档中也有说明,enum中的每一个值其实都是一个Object对象,
每声明一个值就会创建一部分的内存以使它能够被引用到这个对象,相比于静态常量,enum会花费近2倍的内存空间。

通常使用定义静态常量来代替Enum使用。但是这种方式在用户不了解就会出现取值范围不清楚的问题,
调用者在不知道源码的情况下有可能会导致传值错误,而且也不知道从何处找到该常量,
为了解决这种方式google官方提供了另外一种很好的解决方式。

如果在需要用大量的Enum时,用这种方式替换相信内存占用及性能会好很多,而且也是google官方推荐的。


class SexTest3 {

    //注解:核心
    @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
    @MustBeDocumented
    @IntDef(MAN, WOMEN)
    @kotlin.annotation.Retention(AnnotationRetention.SOURCE)
    annotation
    class Sex
    
    @Sex
    private var sex: Int = 0

    fun setSex(@Sex sex: Int) {
        this.sex = sex
    }

    fun getSex(): String {
        if (MAN == sex) return "男"
        return if (WOMEN == sex) "女" else "未知"
    }

    companion object {
        const val MAN = 0x2
        const val WOMEN = 0x3
    }
}

//fun main() {
//    val sexTest3 = SexTest3()
//    ()
//    val sex = ()
//    println("===============================$sex")
//}


不使用枚举类型的解决方案

既然是因为参数的类型太泛了造成的类型不安全,那么我只要将参数限定在某一个类型集合里面
要将的@IntDef/@StringDef + @interface来进行限定参数

字符串也可以使用注解@StringDef:


class StringConstants {

    @FlagDef
    var flag = UNDEFINE

    // 表示开启Doc文档
    @MustBeDocumented
    //限定为 , 
    @StringDef(OK, ERROR)
    //表示注解作用范围,参数注解,成员注解,方法注解
    @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
    //表示注解所存活的时间,在运行时,而不会存在 .class 文件中
    @Retention(AnnotationRetention.SOURCE)
    annotation class FlagDef//接口,定义新的注解类型

    companion object {
        const val UNDEFINE = "undefine"
        const val OK = "ok"
        const val ERROR = "error"
    }
}

fun main() {
    val stringConstants = StringConstants()
    stringConstants.flag = StringConstants.OK
    println("========================================${stringConstants.flag}")
}