字符串String

本文详细介绍了Java中String类的特性,如不可变模式、实例化方式(直接赋值、构造器、intern方法)、池化原理以及为何设计为不可变。还探讨了不同初始化方式下的对象创建数量和intern方法的作用。
摘要由CSDN通过智能技术生成
1、string的三大特性
  1. 不变形,string是一个immutable模式(不变模式)的对象,在被多线程访问时,可以保证数据的一致性。
  2. 常量池优化:string对象创建后,会在字符串常量池中进行缓存,下次创建同样对象时,会直接返回缓存的引用
  3. final:不可变被继承,提升安全性。

不变模式,指的是一个对象的状态在对象被创建之后就不在变化。包括强不变模式和弱不变模式,

  • 弱不变模式:类的实例状态是不可变化的,但是这个类的子类的实例可能会变
    • 对象实例化之后,没有方法修改对象的状态
    • 所有的属性都是私有的
    • 这个对象如果引用了其他可变对象的话,必须限制外界对这些可变对象的访问,以防止被修改
  • 强不变模式:一个类的实例状态不会改变,同时子类的实例也有不可变的状态
    • 除了弱不变几点外,要么这个类本身是final修饰,要么所有方法都是final修饰。

不变和只读的区别?

一个变量只读时,变量的值不能被修改,但是不意味着这个值不会改变,比如一个人的年龄。

2、string的实例化方式
  • 直接定义赋值
  • 通过构造器,传入一个字面量,或者传入一个char[] 数组
    /**
     * 字符串创建的两种方式
     * 1、直接赋值,在字符串常量池中创建对象
     * 2、通过构造器,堆内存和字符串常量池中各一份
     */
    private static void test1() {
        String str1 = "hello"; // 字符串常量池中没有hello,就在字符串常量池中创建hello,并将地址返回给str1
        String str2 = "hello"; // 字符串常量池中已经有hello了,直接拿到hello的地址给str2
        // str1和str2都是引用这个hello的地址,因此打印true
        System.out.println(str1 == str2);

        // 看到new,就意味着在堆内存中创建了一个对象
        // 因为常量池中已经存在hello了,因此不会在创建对象了
        String str3 = new String("hello");
        String str4 = new String("hello");
        // 两个对象比较地址,显然是false
        System.out.println(str3 == str4);
        // String重新了equals方法,比较对象的值,答案是true
        System.out.println(str3.equals(str4));
    }

3、string的不可变性怎么体现的?为什么要设计成不可变的?
* 1、怎么个不可变法?
* 1.1:string底层是一个char[]数组(java8以后变成了byte[]数组),这个数组被private 和 final修饰,并且没有暴露任何可以修改char[]数组的接口
* 1.2:string本身被final修饰,不可被继承修改
* 1.3:string提供给外面的所有接口都是创建一个新对象返回,对其修改不会改变自身,因此是不可变的


* 2、为什么要设计成不可变?
* 2.1、池化思想,字面量创建字符串时,字符串常量池会返回已有对象的引用,如果字符串可变,那么引用的值就会改变,常量池就无法复用了
* 2.2、字符串不可变,保证了hashcode不会变,这样计算一次就可以缓存下来,提升性能,
* 2.3、不可变对象是线程安全的,可以用于并发。
    private static void test2() {
        String str1 = "test";
        String str2 = str1;
        // 很明显相等
        System.out.println(str1 == str2);

        // String的不可变性,修改字符串会重新创建一个对象,str2值不会变
        str1 = str1 + "aaa";
        System.out.println(str1 == str2);
    }


    /**
     * String的不可变性,真是不可变的吗?--- 其实也不是,通过反射是可以改变其值的
     */
    private static void testFinal() throws NoSuchFieldException, IllegalAccessException {
        String a = "abc";
        System.out.println("a= " + a + ", " + System.identityHashCode(a) + ", " + a.hashCode());

        // String类型底层是char[]数组,通过反射获取到这个数组,然后修改数组元素,可以发现,string被改了
        Class<? extends String> aClass = a.getClass();
        Field value = aClass.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(a);
        chars[1] = 'c';
        // 可见,修改完之后,hashcode没有变化,这是因为字符串重新了hashcode方法,第一次计算之后就缓存起来了,不会二次计算
        System.out.println("a= " + a + ", " + System.identityHashCode(a) + ", " + a.hashCode());
        // 这个位置也会输出acc,这是因为字符串字面量是都是指向字符串常量池中的字符串对象,因为我们已经修改了这个值,所以打印修改之后的值
        System.out.println("abc");
    }

 

4、String不同初始化方式,创建了几个对象的问题

4.1、不存在 + 号的情况:

// 直接赋值,就是一个,在字符串常量池中
String str = "str";

// 通过构造器new出来的,两个
// 一个是在堆中,
// 一个是在字符串常量池中 (如果字符串常量池中已经存在了,就不创建了)
String str0 = new String("test");

4.2、存在 + 号的情况

当+号两边都是字符串常量时,则先会寻找字符串常量池中是否存在已经拼接好的字符串。

/**
 * 创建了几个对象?
 * 1个:
 * jvm优化,直接在字符串常量池创建a1b1
 * 因为+号左右两侧都是确定了的字面量
 */
String str2 = "a1" + "b1";

当+号一边存在字符串变量时,则会在堆中创建一个为该拼接字符串的对象。

String str3 = "cc";
/**
 * 创建几个对象? 不算cc
 * 3个:
 * 1、创建了一个StringBuilder对象
 * 2、常量池创建了一个dd
 * 3、StringBuilder.toString, 在堆里面创建了一个字符串对象
 */
String str4 = str3 + "dd";


/**
 * 创建了几个对象?
 * 6个:
 * 1、先创建了一个StringBuilder对象
 * 2、堆中new了一个aa
 * 3、字符串常量池创建了一个aa
 * 4、堆中new了一个bb
 * 5、字符串常量池创建了一个bb
 * 6、StringBuilder.toString,又在堆中创建了一个对象
 * 值得注意的是,并没有在字符串常量池创建aabb
 */
String str1 = new String("aa") + new String("bb");
这里值得注意的是,只有使用引号包含文本的方式创建的字符串对象之间使用“+”连接产生的新对象才会被加入字符串常量池中。对于所有包含new方式新建对象(包括null)和变量形式 的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。 

这也很好理解,因为常量池是为了提高效率而设置的,如果每连接两个变量字符串都要在常量池中创建一份的话,那常量池的容量大小岂不是要爆了,毕竟字符串的组合可以千变万化。考虑到这一点,所以常量池只创建文本常量对象。

5、intern方法
    private static void test1() {
        String str = new String("aa") + new String("bb");
        // 此时字符串常量池中没有aabb,调用intern之后,就在字符串池中创建aabb
        // 但是这个aabb的值,是指向前面创建对象的一个指针,也就是说存的值是str的地址
        str.intern();
        // 字符串常量池中已经有了,不创建了,直接返回引用给str1
        String str1 = "aabb";
        // 这里比较的是地址,所以是true
        System.out.println(str1 == str);
    }

    private static void test2() {
        String str = new String("aa") + new String("bb");
        // 此时字符串常量池中没有aabb,就在字符串池中创建aabb
        String str1 = "aabb";
        // 此时字符串常量池中有aabb,调用intern之后,返回aabb的指针,但是没有数据接收返回值
        str.intern();
        // false
        System.out.println(str1 == str);
    }

    private static void test3() {
        String str = new String("aa") + new String("bb");
        // 此时字符串常量池中没有aabb,就在字符串池中创建aabb
        String str1 = "aabb";
        // 此时字符串常量池中有aabb,调用intern之后,返回aabb的指针,并把指针赋给了str
        str = str.intern();
        // 这里就是true
        System.out.println(str1 == str);
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值