Java final常量

Posted by Codeboy on March 20, 2021

前言

final 是Java中常用的关键字,可以修饰类、方法和变量,类被修饰后,不能被继承;方法被修饰后,不能被重写;变量被修饰后,则值/引用不会改变;

工程中定义一些常量的时候,可以通过接口常量、枚举、类静态常量等提供给外部业务使用,在一些场景下,如果定义的常量发生变更,但外部没有升级依赖的版本号,则会存在外部依旧使用旧常量的场景。

测试

按照接口常量、枚举和类静态常量三种模式,分别查看引用处的字节码:

1. 接口常量

public interface IFruit {
    String APPLE = "apple";
    String BANANA = "banana";
    String ORANGE = "orange";
    String PEAR = "pear";
}

public class Test1 {
    public void test() {
        String a = IFruit.BANANA;
        System.out.println(a);
    }
}

对应test方法的字节码如下:

  public test()V
   L0
    LINENUMBER 3 L0
    LDC "banana" //这里
    ASTORE 1
   L1
    LINENUMBER 4 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 5 L2
    RETURN
   L3
    LOCALVARIABLE this LTest1; L0 L3 0
    LOCALVARIABLE a Ljava/lang/String; L1 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2

可以看到IFruit.BANANA被直接替换为字符串 banana;

2. 枚举

public enum Fruit {
    APPLE("apple"), BANANA("banana"), ORANGE("orange"), PEAR("pear");

    Fruit(String name) {
        this.name = name;
    }

    private final String name;

    public String getName() {
        return name;
    }
}

public class Test2 {
    public void test() {
        Fruit banana = Fruit.BANANA;
        System.out.println(banana.getName());
    }
}

对应test方法的字节码如下:

public test()V
   L0
    LINENUMBER 3 L0
    GETSTATIC Fruit.BANANA : LFruit; //这里
    ASTORE 1
   L1
    LINENUMBER 4 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL Fruit.getName ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 5 L2
    RETURN
   L3
    LOCALVARIABLE this LTest2; L0 L3 0
    LOCALVARIABLE banana LFruit; L1 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2

可以看到Fruit.BANANA 的引用依旧存在;

3. 类静态常量

public class Fruit {
    public final static String APPLE = "apple";
    public final static String BANANA = "banana";
    public final static String ORANGE = "orange";
    public final static String PEAR = "pear";
}

public class Test3 {
    public void test() {
        String a = Fruit.BANANA;
        System.out.println(a);
    }
}

对应test方法的字节码如下:

public test()V
   L0
    LINENUMBER 3 L0
    LDC "banana" //这里
    ASTORE 1
   L1
    LINENUMBER 4 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 5 L2
    RETURN
   L3
    LOCALVARIABLE this LTest3; L0 L3 0
    LOCALVARIABLE a Ljava/lang/String; L1 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2
}

可以看到Fruit.BANANA被直接替换为字符串 banana,和接口是一样的,实际上接口中的常量都会被添加上public final static 的修复符;

小结

final 修饰的常量在编译的时候,编译器将会直接将对应的值替换到引用的位置,如果常量提供给外部使用,通过接口常量、类静态常量的模式,尽量不要进行二次修改,如果必须修改,请务必确保使用方升级版本,可以通过枚举方式替换;

如有任何知识产权、版权问题或理论错误,还请指正。

转载请注明原作者及以上信息。