先看下面的例子

public class FinalDemo {

    public static void main(String[] args)  {
        System.out.println(A.s);
    }
}

class A{
    public static final String s = "jetty";
    public static String ss = "docker";
    public final String sss = "zookeper";
    static {
        System.out.println("A  class init。。。");
    }
}

会输出什么,简单啊~~~

jetty
A  class init。。。

然而不是这样的,正确的输出应该是

jetty

tell me why? 怎么回事呢
这就要说到class文件中的ConstantValue属性
我们先来看看class文件吧

C:\demo\target\classes\com\example\demo\test>javap -v A.class
Classfile /C:/demo/target/classes/com/example/demo/test/A.class
  Last modified 2021-8-19; size 652 bytes
  MD5 checksum 5edd6c444c043e853ed1cd7f678e85f2
  Compiled from "FinalDemo.java"
class com.example.demo.test.A
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:   //常量池
   #1 = Methodref          #9.#26         // java/lang/Object."<init>":()V
   #2 = String             #27            // docker
   #3 = Fieldref           #8.#28         // com/example/demo/test/A.sss:Ljava/lang/String;
   #4 = Fieldref           #8.#29         // com/example/demo/test/A.ss:Ljava/lang/String;
   #5 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = String             #32            // class init。。。
   #7 = Methodref          #33.#34        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #8 = Class              #35            // com/example/demo/test/A
   #9 = Class              #36            // java/lang/Object
  #10 = Utf8               s
  #11 = Utf8               Ljava/lang/String;
  #12 = Utf8               ConstantValue
  #13 = String             #37            // jetty
  #14 = Utf8               ss
  #15 = Utf8               sss
  #16 = Utf8               <init>
  #17 = Utf8               ()V
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               this
  #22 = Utf8               Lcom/example/demo/test/A;
  #23 = Utf8               <clinit>
  #24 = Utf8               SourceFile
  #25 = Utf8               FinalDemo.java
  #26 = NameAndType        #16:#17        // "<init>":()V
  #27 = Utf8               docker
  #28 = NameAndType        #15:#11        // sss:Ljava/lang/String;
  #29 = NameAndType        #14:#11        // ss:Ljava/lang/String;
  #30 = Class              #38            // java/lang/System
  #31 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
  #32 = Utf8               class init。。。
  #33 = Class              #41            // java/io/PrintStream
  #34 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
  #35 = Utf8               com/example/demo/test/A
  #36 = Utf8               java/lang/Object
  #37 = Utf8               jetty
  #38 = Utf8               java/lang/System
  #39 = Utf8               out
  #40 = Utf8               Ljava/io/PrintStream;
  #41 = Utf8               java/io/PrintStream
  #42 = Utf8               println
  #43 = Utf8               (Ljava/lang/String;)V
{
  public static final java.lang.String s;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String jetty    //这里ConstantValue 的类型是string,值为jetty

  public static java.lang.String ss;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

  public final java.lang.String sss;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_FINAL
    ConstantValue: String docker   //这里ConstantValue 的类型是string,值为docker

  com.example.demo.test.A();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String docker
         7: putfield      #3                  // Field sss:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 10: 0
        line 14: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/example/demo/test/A;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: ldc           #2                  // String docker
         2: putstatic     #4                  // Field ss:Ljava/lang/String;
         5: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #6                  // String class init。。。
        10: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: return
      LineNumberTable:
        line 13: 0
        line 16: 5
        line 17: 13
}
SourceFile: "FinalDemo.java"

分析class文件,发现 jetty 并没有在静态代码块或者构造块中赋值,可是能够直接输出 jetty ,原因就只有一个,类初始化之前就已经有值了,所以当你获取直接获取一个被 final static 修饰的常量时,可以不用加载类而直接进行获取。
在实际的程序中,只有同时被final和static修饰的字段才有ConstantValue属性,且仅限于基本类型和String。编译时Javac将会为该常量生成ConstantValue属性,在类加载的准备阶段,会用ConstantValue中的值来初始化,而不是用默认的值来进行初始化。

为什么仅限于基本类型和字符串
因为常量池中只能引用到基本类型和string类型的字面量