Java String类

String

今天我们介绍 Java 中的字符串类型。

目录

String

1. 创建字符串

2. 字符串比较相等

3. 字符串常量池

4. 字符串主动入池

5. 字符串不可变,反射


首先需要清楚,Java 中的字符串和 C 语言不同,和 char[] 没有任何关系,Java 是通过一个专门的类 String 来实现,是 Java 标准库中的一个类,在 Java.lang 这个包中,在使用这个类的时候会自动导入。

1. 创建字符串

基本上有两种方式

a) 

String str = "abcd";

b)

String str = new String("abcd");

这两者虽然结果相同,但是创建的过程有区别,方式 b) 中先在字符串常量池中创建了一个字符串 "abcd",然后通过该字符串又 new 出一个新的字符串,这个字符串的内容也是 "abcd" ,然后将常量池中的 "abcd" 通过垃圾回收机制删除。

2. 字符串比较相等

看下面一个代码:

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2);

结果为:false

这是为什么呢?因为实际上在非内部类里 == 并不是比较内容,而是比较引用是否指向了同一个对象,我们给出上面代码的内存布局图一切都明了了。

很明显,这里的 str1 和 str2 指向的不是一个对象,所以结果为 false。

所以如果要比较字符串的内容需要借助 equals 函数。

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者这样写也行

此时的执行结果就为 :true。

需要注意一个小细节:

String str = new String("Hello");
// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

更建议使用方法二,因为当 str = null 时,方式一会抛出异常。

3. 字符串常量池

上面我们频繁提到字符串常量池,其实字符串常量池是 jvm 底层实现的一个自动维护从而减少内存浪费的对象池。

看下面一段代码:

String str1 = "hello" ;
String str2 = "hello" ; 
String str3 = "hello" ; 
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true

这里的结果都为 true 因为实际上在堆上只有一份 "hello" ,三个引用同时指向该内存区域,我们给出内存分布图。

字符串常量池遵循下面的原则:

a) 如果现在采用了直接赋值的模式进行 String 类的对象实例化操作,那么该实例化对象(字符串内 容)将自动保存到这个对象池之中。

b) 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用,如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。

上面是直接赋值,下面我们看使用构造方法的情况:

String str = new String("hello");

正如 1 中所说,方式 b) 中先在字符串常量池中创建了一个字符串 "abcd",然后通过该字符串又 new 出一个新的字符串,这个字符串的内容也是 "abcd" ,然后将常量池中的 "abcd" 通过垃圾回收机制删除。

这样的做法有两个缺点:

a) 如果使用 String 构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 "hello" 也是一个匿名对象,用了一次之后就不再使用了, 就成为垃圾空间,会被 JVM 自动回收掉。

b) 字符串共享问题,同一个字符串可能会被存储多次,比较浪费空间。

4. 字符串主动入池

我们可以使用 String 的 intern 方法来手动把 String 对象加入到字符串常量池中。

// 该字符串常量并没有保存在对象池之中
String str1 = new String("hello");
String str2 = "hello"; 
System.out.println(str1 == str2); 
// 执行结果
false
    
String str1 = new String("hello").intern(); 
String str2 = "hello";
System.out.println(str1 == str2);
// 执行结果
true

可以看出来,使用 intern 来手动入池的结果和直接赋值类似。

5. 字符串不可变,反射

Java 中的关键字 final 如果修饰引用代表引用的指向不可变,但是在字符串的操作中我们经常看见 String str = "hello"; str = "你好"; 这样的操作,可见 String 中的不可变和 final 关键字的引用指向不可变不是一个意思。

看下面一段代码:

String str = "hello"; 
str = str + " world"; 
str += "!!!"; 
System.out.println(str); 
// 执行结果
hello world!!!

对字符串使用 + 或者 += 的操作,看似改变了字符串内容,实则不是。我们给出上面代码的内存图。

可以看见,在代码执行的过程中,引用 str 的指向不断被改变,最后指向的是 "hello world !!!",前面的 "hello","hello world",等字符串常量由于没有引用指向都被垃圾回收机制回收了。

由上面的例子可以看出,字符串不可变是指字符串的内容不可变

如果我们想改变字符串内容可以借助反射机制来改变,但是不建议这样因为它会破坏封装,这和我们封装的思想是相违背的,下面给出反射机制的代码演示。

注释写的很详细,想要了解的朋友可以看看。

        final Test t = new Test();
        Test t1 = new Test();

        //下面这句不可行,final修饰引用,引用的指向不可变
        //t = t1;

        //可以改变引用指向对象的内容
        t.a = 100;
        System.out.println(t.a);
        String str1 = "hello";
        str1 = str1 + "hi";
        System.out.println(str1);
        //进入String的源码,可以看见我们的String是借助于char[]类型实现的
        //字符串内容不可变但是可以通过特殊手段改变内容
        //这种特殊手段叫 自省 或者 反射
        //下面给出例子

        String string = "hello";
        //我们把类看成图纸,把类下面的对象看成根据图纸制造出来的零件
        //1.获取到String的类对象,类对象:站在更高到的角度把每一个类看成一个对象,可以理解为图纸对象
        //对应的操作为 String.class
        //2.根据String中的value这个属性名,在类对象中拿到对应的字段(仍然是图纸的一个部分)
        //对应的操作为 getDeclaredField("value")
        Field valueField = String.class.getDeclaredField("value");
        //3.让value这个private的成员也可以被访问到
        valueField.setAccessible(true);
        //4.根据得到的局部图纸把对象拆开,取出对应局部图纸的零件部分
        char[] value = (char[])valueField.get(string);
        //5.修改内容
        value[4] = 'a';
        //查看修改后的内容
        System.out.println(string);

代码运行的结果为:

通过上面的代码我们可以看出,final 修饰的引用,引用的指向的对象不可变,但是可以修改引用所指对象的内容;通过反射机制,可以改变字符串的内容。

 

今天的内容就分享到这里,希望大家多多评论,一同提高。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值