再谈Java的String字符串

文章详细讨论了Java中String对象的不可变性,解释了对象与引用的区别,以及如何通过`new`关键字和常量池创建String。通过实例分析了`intern()`方法的作用,展示了不同创建方式下对象的内存分配情况,并通过示例题目解释了字符串比较的逻辑。
摘要由CSDN通过智能技术生成

        我们先看下面几个常见的面试题:

        String s1 = "abc";
        String s2 = new String("abc");
        String s3 = "a" + "b" + "c";
        String s4 = s2.intern();

        System.out.printf("s1=s2:%s\n", s1 == s2);
        System.out.printf("s1=s3:%s\n", s1 == s3);
        System.out.printf("s1=s4:%s\n", s1 == s4);

        那么请问各自的结果是什么?

        我们知道,String这个类是被final修饰,是不可变的。String的对象一旦建立,是不能更改的。我相信很多同学并不真正了解这里的不可变的真正含义,因为我开始学习java的时候,这个地方就困惑了我很久。   

有下面一段代码:

			String a ="Hello";
			a ="你好";

         那这时候我们说,这个a变了啊。其实这里我们主要是理解有误区,没有分清楚什么是对象和对象引用。我们这里的a 指的是对象引用而不是对象本身;对象在内存中是一块内存地址,而a则是一个指向该内存地址的引用。
        要比较两个对象是否相等,要用==;要比较两个对象的值是否相等,要用equals方法来判断。所以在上面额说的这个例子中,第一次赋值的时候,创建了一个“Hello”对象,a 引用指向“Hello”地址;第二次赋值的时候,又重新创建了一个对象“你好”,a 引用指向了“你好”,但“Hello”对象依然存在于内存中。    

        如下图:

        在java中,主要有两种创建字符串对象的方式,

  1. 通过字符串常量的方式来创建,如上面String s1 = "abc";
  2. 通过new形式创建,如上面的String s2 = new String("abc")。

        当使用第一种方式创建时,虚拟机会先检查该对象在常量池中是否存在,如果存在,就回返该对象的引用,如果不存在,就在常量池中创建该字符串对象。这样就能让字符串对象重复使用,降低内存;
        当使用第二种方式创建时,在类加载的时候,会在常量池中先创建“abc”,然后程序调用new的时候,会引用常量池中“abc”字符串(其实就是将堆中String对象char[]数组指向常量池对象abc中的char[])在堆中创建一个String对象,最后将s2的引用指向这个堆中String对象。  

 基于以上的String字符串基础内容的分析,我们看下最初那几个题的答案:

s1=s2:false
s1=s3:true
s1=s4:true

        第一,s1=s2为false,因为s1指向了常量池中的“abc”,而s2指向了堆内存的String对象,显然不相等;
        第二,s1=s3为false,因为在编译的时候,虚拟机给我们代码优化成了String s3 = "abc",显然s3也指向了常量池中的"abc",所以相等;
        第三,s1=s4为true,这里的intern()方法我们了解的可能不是很多。这个intern()方法,它的作用是如果字符串常量池已经包含一个等于此String对象的字符串,则返回字符串常量池中这个字符串的引用, 否则将当前String对象的引用地址(堆中)添加到字符串常量池中并返回。
所以s4也指向了常量池中的"abc",显然s1=s4。

明白了上面几个问题,也知道intern()这个方法的原理,我们再看一个题:

        String s1 = new String("5") + new String("5");
        s1.intern();
        String s2 = "55";
        System.out.printf("s1=s2:%s\n", s1 == s2);

         这个最后的结果是:s1=s2:true。这个就比较好分析了:通过加号动态生成的“55”字符串由于在运行时常量中没有该字符串的引用,所以会在调用s1.intern()时,在运行时常量池中生成一个s1的引用,当s2再次引用该字符串时,发现运行时常量池中存在相同值的字符串的引用,就直接返回s1的引用。所以s1==s2是返回的true。

 那么接下来我们再看一个题,哈哈:

        String s1 = new String("55");
        s1.intern();
        String s2 = "55";
        System.out.printf("s1=s2:%s\n", s1 == s2);

挺有意思,知识把上面的题和这个题调换一下顺序,但是结果却是:

s1=s2:false

分析如下:首先"55"在类加载的时候,已经存在静态常量池中,在new string(“55”)时,会在运行时常量池中创建一个“55”字符串的直接引用。而s1指向的并不是该引用,而是new string这个对象的引用。当s2=“55”时,返回的是运行时常量池中的引用。所以s1==s2返回false。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值