String和常量池的思考(随手一记)

随手记一下String和常量池的一些知识点。

首先需要大概的了解编译期运行期两个概念。

当编写完JAVA程序之后,首先将java文件编译成class文件,这就是编译期要做的事情,在编译期编译器会对程序进行一些优化。

当class文件编译出来之后,开始运行程序,将class文件动态的加载进JVM,程序就进入了运行期。

常量池(以下名字不准确,是自己叫的):

  • 静态常量池:程序编译完成之后,程序中一些不会改变的常量会保存到class文件中的一块地方,这里就是静态常量池
  • 动态常量池:程序开始运行的时候,将静态常量池载入了JVM,在JVM的方法区中就产生了一块位置,就是运行期的动态常量池
问题

有一些涉及到String和常量池的问题,基本上就是比较字符串之间是否相等,为啥相等,又为啥不相等,我觉得应该是这样:

String是一个引用数据类型,不是基本数据类型,所以对其创建一个对象应该是在上分配空间的,但是String又是特殊的,它拥有向基本数据类型一样的字面量(String s = “hello, world” 其中"hello, world"就是字面量),字面量是常量,是要放到常量池中的,这样就可以提高String的复用性,可以多个String指向常量池中的同一个常量。String还拥有+运算符,不过其作用是连接两个字符串,而不是代数相加。

编译期,常量就会被放置到静态常量池中去,等到运行期的时候加载到内存中去。

看几个例子吧。

		// 在堆中创建了一个新的String对象,该对象指向常量池中的 "HelloWorld"
		String s1 = new String("HelloWorld");
		// 又在堆中创建了一个新的String对象,该对象也指向常量池中的 "HelloWorld"
        String s2 = new String("HelloWorld");
		// 指向常量池中的 "HelloWorld"
        String s3 = "HelloWorld";
		// 也指向常量池中的 "HelloWorld"
        String s4 = "HelloWorld";
		// 指向常量池中的 "Hello"
        String s5 = "Hello";
		// 指向常量池中的 "World"
        String s6 = "World";

        System.out.println(s1 == s2); // false s1与s2是两个不同的对象,指向了堆中的两块不同的地址
        System.out.println(s3 == s4); // true  s2与s4都是字面值常量,指向的是常量池中相同的地址
        System.out.println(s1 == s4); // false s1和s4一个地址在堆中,一个地址在常量池中

        System.out.println("=========================================");
		// false
        System.out.println(s1 == (s5 + s6));
		// false 如果字符串拼接时如果两边只要有一个是字符串变量,就会在堆中生成新对象,也就是说s3指向常量池,而s5+s6指向堆
        System.out.println(s3 == (s5 + s6));
		// true 如果是字面量拼接的话其实在编译的时候就已经拼接好了,就是常量池中的HelloWorld
        System.out.println(s3 == ("Hello" + "World"));  
		// false
        System.out.println(s3 == (s5 + "World"));       
结论是(重要):

1、如果使用new在堆中创建了新的字符串对象,不同的对象在堆上的地址自然是不同的,也就通过==进行比较自然不相等,因为==比较引用数据类型是比较其指向的对象是否相同。

2、如果String引用指向的是一个字符串的字面量,那么其指向的地址都会是常量池中的同一块地址,所以地址是相同的,使用==进行比较也自然是相同的。

3、如同 String s = "Hello" + " World";这种情况,也就是将字面量使用+连接,在编译期会被直接优化成String s = "Hello World",也就是说和直接使用字面量没有什么区别,s指向的还是常量池中的"Hello World"。

4、像3中说的那样,不过如果+的两边存在着变量的话,情况就不一样了,因为字符串是不可变的,所以两个字符串使用+进行连接其实是使用StringBuilder将两个字符串连接起来,然后使用其toString方法,返回一个新的字符串对象,该对象是在堆上的。

5、如果两个字符串变量都使用final关键字修饰的话,则说明这两个字符串的引用都不能指向新的值,也就是成为了一个常量,这种情况下这两个字符串常量使用+运算符连接,与两个字面量常量连接是一样的,都是在编译期将其放置到常量池中去。

	// 再看一下这一个例子
	public static final String s1 = "HelloWorld";
    public static final String s2 = "HelloWorld";
    public static final String s3 = "Hello";
    public static final String s4 = "World";
    public static final String s5;
    public static final String s6;
    public static final String s7;
    public static final String s8;

    public static String s9 = "Hello";
    public static String s10 = "World";

    static {
        s5 = "HelloWorld";
        s6 = "HelloWorld";
        s7 = "Hello";
        s8 = "World";
    }

    public static void main(String[] args) {
       System.out.println(s1 == s2); 
       System.out.println(s5 == s6);
       System.out.println(s1 == s5);

       System.out.println("========================");
	   // true 因为此时s3和s4都是被final修饰且已经被显式赋值,其值已经确定,等同两个字面值相加,在编译期已经完成了字符串拼接
       System.out.println(s1 == (s3 + s4)); 
       // false
       System.out.println(s1 == (s7 + s8));
       // false
       System.out.println(s5 == (s7 + s8));
       // false
       System.out.println(s1 == (s9 + s10));
    }

还有就是String类有一个实例方法intern,会检查常量池中有没有和当前这个字符串内容一样的字符串常量,如果没有的话就将该该字符串加入到常量池中去,并且不管有没有都会返回一个常量池中该字符串的字符串常量。这也是极少数的能够在运行期将字符串加入到常量池中的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值