java常量和kotlin常量

  • java中使用final声明常量
  • kotlin中使用const val声明常量

常量在编译为字节码后会直接把调用常量的地方直接替换为常量值,示例如下:

public class ConstDemo {
    public static final String NAME = "Even";
    private static final int ID = 1001;
    static final int YEAR = 2024;
    public final int color = 255;
    public static int width = 100;
    public int height = 200;

    public static void main(String[] args) {
        System.out.println(NAME);
        System.out.println(NAME);
        System.out.println(ID);
        System.out.println(ID);
        System.out.println(YEAR);
        System.out.println(YEAR);
        ConstDemo demo = new ConstDemo();
        System.out.println(demo.color);
        System.out.println(demo.color);
        final int number = 9;
        System.out.println(number);
        System.out.println(number);
        System.out.println("--------------------------------");
        final int count;
        if (width > 100) {
            count = 1;
        } else {
            count = 2;
        }
        System.out.println(count);
        System.out.println(count);
        System.out.println(width);
        System.out.println(width);
        System.out.println(demo.height);
        System.out.println(demo.height);
        int weight = 99;
        System.out.println(weight);
        System.out.println(weight);
    }

}

编译后得到class字节码,在IntelliJ中可以直接双击这个class字节码,它是自带反编译器,效果如下:

public class ConstDemo {
    public static final String NAME = "Even";
    private static final int ID = 1001;
    static final int YEAR = 2024;
    public final int color = 255;
    public static int width = 100;
    public int height = 200;

    public ConstDemo() {
    }

    public static void main(String[] args) {
        System.out.println("Even");
        System.out.println("Even");
        System.out.println(1001);
        System.out.println(1001);
        System.out.println(2024);
        System.out.println(2024);
        ConstDemo demo = new ConstDemo();
        PrintStream var10000 = System.out;
        Objects.requireNonNull(demo);
        var10000.println(255);
        var10000 = System.out;
        Objects.requireNonNull(demo);
        var10000.println(255);
        int number = true;
        System.out.println(9);
        System.out.println(9);
        System.out.println("--------------------------------");
        byte count;
        if (width > 100) {
            count = 1;
        } else {
            count = 2;
        }

        System.out.println(count);
        System.out.println(count);
        System.out.println(width);
        System.out.println(width);
        System.out.println(demo.height);
        System.out.println(demo.height);
        int weight = 99;
        System.out.println(weight);
        System.out.println(weight);
    }
}

如上代码,可以发现,只要是final修饰的变量在调用时直接被常量值替代了,有一个例外,就是在局部变量中声明的final int count;,它不是在声明时直接赋值的,而是经过一个if判断之后才赋值的,所以需要在运行时才能确定它的值是多少,所以在编译为字节码时调用该变量的地方没有被常量值替换,因为此时不知道它的值是多少。

另外也看到了一些有趣的地方,编译时编译器会有一些优化,比如int number = true;还能这样啊?没搞懂,它的final被去掉了,count中地final修饰符也被去掉了,而且类型变成了byte类型,编译器通过if中的判断得出值不是1就是2,用byte足已,所以改成了byte类型。

基于这个常量的特性,我们可以猜到,通过反射也是无法修改final类型的常量的,示例如下:

public class ConstDemo {
    public static final int age = 18;

    public static void main(String[] args) throws Exception {
        Field field = ConstDemo.class.getField("age");
        System.out.println("age = " + field.get(null));
        field.set(null, 30);
        System.out.println(age);
    }
}

运行结果如下:

age = 18
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final int field ConstDemo.age to java.lang.Integer
	at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
	at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
	at java.base/jdk.internal.reflect.UnsafeQualifiedStaticIntegerFieldAccessorImpl.set(UnsafeQualifiedStaticIntegerFieldAccessorImpl.java:77)
	at java.base/java.lang.reflect.Field.set(Field.java:799)
	at ConstDemo.main(ConstDemo.java:9)

从这里也可以看出,为什么常量在编译为class字节码之后,调用它的地方已经被常量值所替换,为什么常量的声明语句还保留了,因为还是有可能会被用到的,比如我们通过反射读取该常量的值,这是需要在运行时才能完成的,无法在编译阶段就直接使用常量值替代的。

再来看一个Demo:

public class ConstDemo {
    public final int age = 18;
    public static final ConstDemo demo = new ConstDemo();

    public static void main(String[] args) throws Exception {
        System.out.println(demo);
        System.out.println(demo);
        System.out.println(demo.age);
        System.out.println(demo.age);
    }
}

编译为字节码后,再反编译结果如下:

public class ConstDemo {
    public final int age = 18;
    public static final ConstDemo demo = new ConstDemo();

    public ConstDemo() {
    }

    public static void main(String[] args) throws Exception {
        System.out.println(demo);
        System.out.println(demo);
        PrintStream var10000 = System.out;
        Objects.requireNonNull(demo);
        var10000.println(18);
        var10000 = System.out;
        Objects.requireNonNull(demo);
        var10000.println(18);
    }
}

可以看到声明为非原始类型的final常量在编译为字节码时无法使用常量值代替,因为它是一个对象,而对象的内存地址得在运行时才能确定,所以这种不应该叫常量的,所以,kotlin在这方面就做的比较好,表示一个变量不可改变用val,表示一个常量用const val,分得更加清楚,示例如下:

const val NAME = "Even"

class ConstDemo {

    val width = 100
    var height = 200

    companion object {
        const val ID = 1001

        @JvmStatic
        fun main(args: Array<String>) {
            println(NAME)
            println(NAME)
            println(ID)
            println(ID)
            val demo = ConstDemo()
            println(demo.width)
            println(demo.height)
        }
    }
    
}

可以看到,声明常量的地方只能是顶级属性或者companion object中,要查看反编译,如果直接在IntelliJ中找到class文件然后双击会发现反编译不了,我们可以这样查看:工具 > Kotlin > 显示Kotlin字节码 > 反编译,结果如下:

public final class ConstDemo {
	private final int width = 100;
	private int height = 200;
	public static final int ID = 1001;
	
	public final int getWidth() {
      	return this.width;
   	}

	public final int getHeight() {
      	return this.height;
   	}

   	public final void setHeight(int var1) {
      	this.height = var1;
   	}

	public static final void main(@NotNull String[] args) {
      	 Intrinsics.checkNotNullParameter(args, "args");
         String var2 = "Even";
         System.out.println(var2);
         var2 = "Even";
         System.out.println(var2);
         short var4 = 1001;
         System.out.println(var4);
         var4 = 1001;
         System.out.println(var4);
         ConstDemo demo = new ConstDemo();
         int var3 = demo.getWidth();
         System.out.println(var3);
         var3 = demo.getHeight();
         System.out.println(var3);
   	}
}

public final class ConstDemoKt {
   @NotNull
   public static final String NAME = "Even";
}

在Kotlin中,常量只能是8大原始类型,不能是对象类型的,如下代码在编译器就报错了:
java常量和kotlin常量_第1张图片
声明常量有什么好处?这里我想到之前写的一篇文章:https://blog.csdn.net/android_cai_niao/article/details/113571171

你可能感兴趣的:(java,Kotlin,java,kotlin,python)