Java编程基础--变量、常量与常量池【知识体系构建系列】

变量

变量是程序的基本组成部分。在 Java 程序设计中,每个声明的变量都必须分配一个类型。
Java声明变量如下:

int a;
double b = 1.1;

变量根据所处的位置而拥有不同的性质,我们通过一个表格来理解一下:

成员变量 局部变量 静态变量
定义位置 在类中,方法外 方法中或者方法的参数
初始化值 有默认初始化值 无,先定义并赋值才能使用
调用方式 对象调用 -
存储位置 堆中 栈中
生命周期 与对象共存亡 与方法共存亡
别名 实例变量 -

常量

程序执行过程中,其值不能被改变的量,称为常量(constant)。
用final修饰的成员变量表示常量。

常量池

什么是常量池呢?

就是将经常使用的常量存储在一个固定区域,在需要使用的时候直接拿出来就可以使用。与之类似的概念有:线程池,数据库连接池等。

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

  • 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
  • 节省运行时间:比较字符串时,== 比 equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

另外,可以发现 线程池、数据库连接池 都有类似的好处,他们是为了解决同样的问题而存在的。

Java中的常量池–JVM中的运用

首先,Class文件的数据结构中就有常量池的概念,在存储魔数”CAFEBABE”、版本信息之后就是常量池的信息了。
这里的常量池有哪些信息呢?主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

另外,在方法区的运行时常量池中,存放了编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。

Java中的常量池–基本类型包装类

java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean。这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。当然,用New重新实例化一个对象,就不会使用缓存了。为什么Float、Double没有常量池呢?当然是数据量太大了,没有合适的常用的数据。

示例代码如下:

Integer i1 = 100;
Integer i2 = 100;
Integer i3 = new Integer(100);
Integer i4 = Integer.valueOf(100);
System.out.println(i1 == i2); //true
System.out.println(i1 == i3); //false
System.out.println(i1 == i4); //true
Integer i5 = 400;
Integer i6 = 400;
System.out.println(i5 == i6); //false

说明:
1. Integer i1=40;Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
2. Integer i1 = new Integer(40);这种情况下会创建新的对象。

Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6));

输出:

i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true

说明:
语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。

Java中的常量池–String

字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。

Java能够进行这样的优化是因为字符串是不可变的【可以查看源代码,String其实是final的字符数组,每次对String的修改操作都是创建出了一个新的String对象】,可以不用担心数据冲突进行共享。

示例代码:

public class Test01 {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1 and str2 are created by using string literal.");
        System.out.println("    str1 == str2 is " + (str1 == str2));
        System.out.println("    str1.equals(str2) is " + str1.equals(str2));
        String str3 = new String("Hello");
        String str4 = new String("Hello");
        System.out.println("str3 and str4 are created by using new operator.");
        System.out.println("    str3 == str4 is " + (str3 == str4));
        System.out.println("    str3.equals(str4) is " + str3.equals(str4));
        String str5 = "Hel" + "lo";
        String str6 = "He" + "llo";
        System.out.println("str5 and str6 are created by using string constant expression.");
        System.out.println("    str5 == str6 is " + (str5 == str6));
        System.out.println("    str5.equals(str6) is " + str5.equals(str6));
        String s = "lo";
        String str7 = "Hel" + s;
        String str8 = "He" + "llo";
        System.out.println("str7 is computed at runtime.");
        System.out.println("str8 is created by using string constant expression.");
        System.out.println("    str7 == str8 is " + (str7 == str8));
        System.out.println("    str7.equals(str8) is " + str7.equals(str8));
    }
}

JDK8版本运行输出:

str1 and str2 are created by using string literal.
    str1 == str2 is true
    str1.equals(str2) is true
str3 and str4 are created by using new operator.
    str3 == str4 is false
    str3.equals(str4) is true
str5 and str6 are created by using string constant expression.
    str5 == str6 is true
    str5.equals(str6) is true
str7 is computed at runtime.
str8 is created by using string constant expression.
    str7 == str8 is false
    str7.equals(str8) is true

Process finished with exit code 0

字符串生成原则:
- 同一个包下同一个类中的字符串常量的引用指向同一个字符串对象;
- 同一个包下不同的类中的字符串常量的引用指向同一个字符串对象;
- 不同的包下不同的类中的字符串常量的引用仍然指向同一个字符串对象;
- 由常量表达式计算出的字符串在编译时进行计算,然后被当作常量;
- 在运行时通过连接计算出的字符串是新创建的,因此是不同的;
- 通过计算生成的字符串显示调用intern方法后产生的结果与原来存在的同样内容的字符串常量是一样的。

参考文献:
Java常量池理解与总结
什么是字符串常量池?

你可能感兴趣的:(java)