前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 编译期常量

Java 编译期常量

作者头像
Fisherman渔夫
发布2020-02-18 11:31:37
1.3K0
发布2020-02-18 11:31:37
举报
文章被收录于专栏:渔夫渔夫渔夫

Java 编译期常量

1. 什么是编译期常量

 编译期常量,即 compile-time constant。其看似是一个静态,并不一定是由 static 修饰(static 一般只是用于强调只有一份),但强制要求使用 final 进行修饰。编译期常量完整要求是:

  • declared final;被声明为 final(所有编译期常量都满足的条件);
  • primitive or String;基本类型或者字符串类型(满足其一即可);
  • initialized within declaration;声明时便已初始化(必要条件);
  • initialized with constant expression;使用常量表达式进行初始化(对于第三条的补充,说明初始化方式);

可能引起最大疑问的是“什么是常量表达式”这个问题,这个可以参考 Oracle 的官方文档:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html (15.28 小节)。

被官方认为是常量表达式的事物众多,这里就列出几个常用的:

  • 基本类型以及 String 类型的字面量(new 出来的、变量引用的都不能算);
  • 基本类型以及 String 类型的强制类型转换;
  • 使用 + 等一元运算符进行加法运算/拼接运算得到的值;

下面列出了可能遇到的所有编译期常量:

public  String str1 ="helloworld";
static  String str2= "helloworld";
public static  String str3 = "hello"+"world";

public final int integer1= 1;
static final int integer2= 1;
public static final int integer3 = -1+2;

final char character1= 'a';

补充知识

  • 如果两个变量同时指向常量池中同一块数据,即是同一个编译期常量,那么使用==操作符返回真。在一定条件下,如果返回假,则意味着两个变量指向的值不都是编译期常量池。
  • String 本质上是用以 final 修饰的 char 类型数组存储的,所以虽然指向字符串类型对象的引用变量不一定是 final 修饰,但是比较是相当于比较两个 char[]数组,由于其是 final 修饰的,不可变,是编译期常量。严格意义上,我们不能说上面代码中的 str1、str2、str3 为编译期常量,因为他们本质上是引用变量,真正的常量为双引号引起来的"helloworld"。

一个小问题:在 final 的修饰下已经足够证明一个变量是不可更改的,为何一定要规定在定义时立即进行初始化的变量才属于编译期常量?

 这是因为只有在编译期间完全确定的变量才能笃定地写入到 Class 文件中的常量池(本质上是硬盘上的二进制流)。 如果不在定义之后马上就初始化,就不能保证编译期间能够确定,比如如下带有 if-else 判断的二进制流:

public static void main(String[] args) {
        final String string_compile_time ="helloworld";
        final String string ;
        if (args == new String[]{"hello","world"}){
            string = "helloworld";
        }else{
            string = "hellworld";
        }
        System.out.println(string_compile_time=="helloworld");
        System.out.println(string=="helloworld");

    }

 从控制台输出:

true
false

 就可以看出没有在定义时马上初始化的 final 类型字符串不为编译期常量。这是因为编译期间无法确定 string 变量到底取值如何。既然存在这种情况,所以 Java 设计者们就规定了编译期常量强制要求在定义时需要进行首次且唯一的初始化。

 实际上,基本类型包装类大多实现了常量池技术,也就是说即使不使用常量表达式在 final 变量在定义的时候初始化,变量是编译期常量。

 即 Byte, Short, Integer, Long, Character, Boolean;

 这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。如以下测试代码:

public class Test {

    public static final Integer integer1 = 127;

    public static final Integer integer2 = 127;


    public static void main(String[] args) {
        System.out.println(integer1==integer2);
    }
}
//控制台输出
//true

 为验证超出一定大小的数值不支持包装类的常量值技术,可以看下面的代码:

public class Test {

    public static final Integer integer1 = 128;

    public static final Integer integer2 = 128;


    public static void main(String[] args) {
        System.out.println(integer1==integer2);
    }
}
//控制台输出
//false

 自动包装有这种现象的原因是以下两个定义是等价的:

public static final Integer integer1 = 128;
public static final Integer integer2 = Integer.valueOf(128);

 上述实际上就是编译器替我们完成自动装箱语法糖的内部本质操作。

java.lang.Integer#valueOf(int)方法的源码为:

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}

其中:

  • static final int low = -128;
  • static final int high = 127;

 可见,当待自动装箱的整数如果在[-128,127]内,那么不会 new 一个 Integer 对象返回,而是返回已创建好的 Integer 数组中的一个元素,数组下表被定义为:i + (-IntegerCache.low

参考网址:https://coderanch.com/t/454384/java/compile-time-constant

2. 编译期常量性质

编译期常量存储于字节码文件 .class 文件中的常量池中,而一个变量由:

  • 修饰
  • 类型
  • 变量名
  • 变量值

组成。前三个的引用都一定存储于字节码的字段信息数据区中,而值实际上存储于字节码文件的 Java 常量池中。而变量值只有是编译期常量时才会存储于字节码的常量池中(如果你对这段不太熟悉,可以复习一下 JVM 中关于字节码文件结构的知识)。

 一个显而易见的性质是,如果你定义的编译期常量越多,整个class 文件也会越大。

 值得一提的是,无论是编译期常量还是其他变量,最终都是要加载到内存中。在 .class 文件中存储的基本类型变量以及 String 类型/包装类数组对象都会被存储于运行时常量池中。使用 == 操作符来判断值是否相等,本质上是在判断等号两侧是否指向内存中的同一块地址,而不会是在判断是否指向常量池中的同一个值。


注意:

  • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
  • 在JDK7.0版本,字符串常量池被移到了堆中了。

还有一个运行时常量池,这个常量池和编译期常量没有直接关系,在 《Java 中的常量池》一文中会进行整理。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java 编译期常量
    • 1. 什么是编译期常量
      • 2. 编译期常量性质
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档