认识 String类

认识 String类

在这里插入图片描述


每博一文案

沈从文在边城中说,凡事都有偶然的凑巧,结果却又如宿命般的必然。

就像我们这一生,会与形形色色的人相遇,有相逢,有告别。

人生无常,无论是得到还是失去,都是命运的安排,但无论怎样,我都想说。

你陪我一程,我念你一生。

时光匆匆,大家都被生活退着,不得不往前走,曾经熟悉的背影不知,

何时已经变得陌生,昔日无话不说的朋友,不知不觉各奔东西,

这一年好多人,忘记了好多人,好多人代替好多人,有的人因为缘分,

相遇相知,相守一生。有些人因为远近成为了彼此生命的匆匆过客,

留下的人相知相守,有幸陪伴一生一世,离去的人,陪我们一程后,

纵我千般不舍,也会离开。

村上春树说,每个人都有属于自己的一片森林,也许我们从来不曾走过,

但它一直都在那里,总会在那里,迷失的人,迷失了,相逢的人会再相逢,

既是注定,何须执着,坦然面对。如此便好,我不后悔认识你,不遗憾你离开。

我没有刻意的去想念你,因为我知道遇到了就应该感恩,路过了就需要释怀。

缘分天注定,无论是携手到老,还是中途分道扬镳,既然有心能相遇,即是

缘,结局如何应交给上天安排?

很高兴你能来,哪怕以后的路,没有办法在一起走,还是会祝你过得好。

愿你我相逢时,你眼里依旧星辰闪烁,笑里全是坦荡。

​ —————— 一禅心灵庙语



创建字符串

String 是引用类型
我们曾经在讲数组的时候就提到了引用的概念

在这里插入图片描述

引用类似于C语言 中的指针,只是在栈上开辟了一个小块的内存空间保存了一个地址,但是引用和指针又有点不太相同,C语言中的指针 能进行各种数字运算 (指针 +1) 之类的,但是我们这里的 Java语言中的引用 是不能这么做的,这是一种 “没有那么灵活”的指针。

另外,也可以把引用 想象成一个类似于 标签 之类的,“贴” 到了一个对象上,一个对象可以**“贴”** 上多一个 “标签” ,但是如果一个对象上面没有一个 “标签” ,那么该对象就会被 JVM虚拟机 当作垃圾对象回收掉,就像是 C语言中的野指针 ,但是C语言中的野指针是不会自动释放空间的需要我们手动释放空间,防止对野指针的引用

Java当中 数组,String,以及自定义的类都是引用类型

创建字符串有以下三种方式:

  1. 直接赋值的方式
public class Blog10 {
    public static void main(String[] args) {
        String str = "helloworld";
        System.out.println(str);
    }
}
  1. 使用实例化的方式
public class Blog10 {
    public static void main(String[] args) {
        String str = new String("helloworld");
        System.out.println(str);
    }
}

  1. 通过数组赋值调用对应的构造方法
public class Blog10 {
    public static void main(String[] args) {
        char[] val = {'h','e','l','l','o'};
        String str = new String(val);
        System.out.println(str);
    }
}


字符串类型之间的比较相等

String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是 指向同一个对象
对如下代码进行一定的 比较判断

public class Blog10 {
    public static void main(String[] args) {
        String  str = "hello";
        String str2 = str;

        System.out.println(str == str2);
    }
}

在这里插入图片描述


上述代码中的 JVM 内存结构图

在这里插入图片描述

从上图中 JVM内存结构图中可以看出,它们两个字符串引用类型,引用的对象是同一个,所以结果为 :true


public class Blog10 {
    public static void main(String[] args) {
        String  str = "hello";
        String str2 = new String("hello");

        System.out.println(str == str2);
    }
}

在这里插入图片描述


上述代码中:JVM 栈上的内存布局

在这里插入图片描述

从 上图的JVM内存布局图中,我们可以明显的发现该,两个字符串引用的对象是不相同的,所以结果就为了 false


面试题:请解释 String 类中两种对象实例化的区别:
    1.直接赋值,只会开辟一块内存空间,并且该字符串对象可以自动保存在常量池中,以供下次使用
    2.构造方法,会开辟两块内存空间,不会自动保存到常量池中,可以使用 intern(),手动引入常量池中

字符串常量池

“池” 是编程中的一种常见的,重要的提升效率的方式,我们会在未来的学习中遇到 各种 “内存池”“线程池”“数据库连接池”

请看下面代码,注意: **String == ** 不是比较内容的相等,而是比较引用的对象是否是相同的

public class Blog10 {
    public static void main(String[] args) {
        String str = "helloworld";
        String str2 = "helloworld";
        String str3 = "helloworld";

        System.out.println(str == str2);
        System.out.println(str == str3);
        System.out.println(str2 == str3);
    }
}

在这里插入图片描述


String类 的设计使用了共享设计模式

JVM 底层实际上会自动维护一个对象池(字符串常量池),如果现在采用了直接赋值的模式进行 String 类的对象实例化操作,那么 实例化对象 (字符串内同) 将自动保存到这个对象池之中。如果下次继续使用直接赋值的模式声明 String 类对象,此时对象池之中,如若有指定内容,将直接进行引用,如若没有,则开辟新的字符串对象而后将其保存在对象池之中,以供下次使用

如下图中的 JVM 内存结构图,我们可以清楚的看到 str、str2、str3 它们三个的引用对象都是在字符串常量池中的 helloworld

在这里插入图片描述


手动入池操作, 使用方法 intern()

intern( ) 手动入池,从现象上来看的是:判断该当前的这个字符串在 字符串常量 池当中是否存在,如果存在,把字符串常量池中当中的引用,赋值给当前的引用类型变量,没有把当前的 字符串 加入到 字符串常量池中 **

public class Blog10 {
    public static void main(String[] args) {
        String str = "hello";
        String str2 = new String("hello");
        System.out.println(str == str2);        // 构造方法,没有入池,所以结果是false

        String str3 = new String("hello").intern();  // 手动入池
        System.out.println(str == str3);  // 入字符串常量池了,引用的对象是相同的 ,true
    }
}

在这里插入图片描述


解析

从结果上看,我们发现一个是1.通过直接赋值 的方式创建的字符串,和一个通过 构造方法 的方式创建的字符串,从前面的学习,我们可以知道了,它们的引用对象是不同的,所以第一个 str == str2 的结果是 false 的,直接赋值是保存在字符串常量池 中的,而 构造方法 保存在 方法区 中的,

str3 同样是通过构造方法创建的,可为什么 其 str == str3 的结果却是 true 呢,原因是使用了 ,手动入池的方法 intern( ) ,把其构造方法中,保存在方法区中的,引用到了 字符串常量池 中的,其结果就为 true 了,具体详细的 JVM 内存结构图,如下:

在这里插入图片描述


字符串的不可修改

字符串 无论是在 Java中还是在C语言 中都是一种不可变对象. 它的内容不可改变,只是改变了引用的对象而已,从一开始的引用 这个字符串 ,修改为了引用 那个字符串 ,其字符串中的内容是没有改变的
String 类 的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组.

public class Blog10 {
    public static void main(String[] args) {
        String str = "hello";
        String str2 = str;
        System.out.println("str:"+str);
        System.out.println("str:"+str2);
        System.out.println("*** 修改 ***");
        str2 = "world";
        System.out.println("str:"+str);
        System.out.println("str2:"+str2);
    }
}

在这里插入图片描述


解析

从运行的结果上看,我们可以发现,通过引用类型 str2 改变 str ,是没有成功的,因为这实质上是,只是修改了 str2 自身从引用指向 str 中的 ——> hello ,修改变成为了引用指向 world 字符串常量了,并没有修改到 str 的,字符串的内容是无法修改的,只是改变了引用的对象而已 从一开始的引用 这个字符串 ,修改为了引用 那个字符串 ,其字符串中的内容是没有改变,如下是 JVM 内存图

在这里插入图片描述


同样调用方法也是不可以修改 字符串内容的

public class Blog10 {
    public static void func(String str2, char[] v) {
        str2 = "world";  // 并没有修改字符串的内容,而是修改了 str 引用对象
        v[0] = 'A';
    }

    public static void main(String[] args) {
        String str = "hello";
        char[] val = {'a'};
        System.out.println(str);
        System.out.println(Arrays.toString(val));
        System.out.println("***修改后***");
        func(str,val);
        System.out.println(str);
        System.out.println(Arrays.toString(val));
    }
}

在这里插入图片描述


从结果上看,我们仍然看到,字符串 str 的内容还是没有修改的,字符串的引用不一定会修改到的内容,不要以为传引用,就会修改值的内容的。其 JVM 内存图如下:

在这里插入图片描述


一定一定,不要对字符串,进行如下操作,不要写出这样的代码出来

public class Blog10 {
    public static void main(String[] args) {
        String str = "hello";
        str = str + "world";
        str += "!!!";

        System.out.println(str);
        for (int i = 0; i < 6; i++) {
            str += "!";
        }

        System.out.println(str);
    }
}

在这里插入图片描述


解析:

对于字符串中的 += 之后的 str 打印的结果却是变了,但是不是 String 对象本身内容的改变,而是 str 引用到了其他的对象,引用相当于一个指针,里面存的内容是一个地址 ,我们要注意区分清楚当前修改的到底是修改了地址对应内存的内容发生改变了,还是引用中所存的地址改变了。内存变化如下:

在这里插入图片描述


实际上我们也是可以通过反射实实在在的修改字符串中的内容的

使用 “反射” 这样的操作可以破会封装,访问一个类内部的 private 成员,反射是面向对

象编程的一种重要特性,有些编程语言也称为 “自省” ,指的是程序运行过程中,获取修改 某个对象的详细信息 (类型信息,属性信息等),相当于让一个对象更好的 “认清自己”

使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员.反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”.
指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 "认清自己
如下:

public class Blog10 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String str = "hello";
        System.out.println(str);
        System.out.println("修改后的结果:");
       // 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
        Field field = String.class.getDeclaredField("value");
        // 将这个字段的访问属性设为 true,从而可以访问了
        field.setAccessible(true);
        // 把 str 中的 value 属性获取到
        char[] value = (char[]) field.get(str);
        // 通过下标,修改 value 的值,就是修改该 str 字符串的对应下标的值
        value[0] = 'H';
        System.out.println(str);
    }
}

在这里插入图片描述


为什么 String字符串 要不可变 ? (不可变对象的好处是什么)?

  1. 方便实现字符串对象,如果 String 可变,那么对象池(字符串常量池) 就需要考虑,何时深拷贝字符串的问题?
  2. 不可变对象是线程安全的
  3. 不可变对象更方便缓存 hash code 作为 key 时,可以更高级的保存到 HashMap 中的

字符、字符串的联系

关于字符与字符串之间的联系,因为设计到篇幅可能过多,所以如有需要的可以移不到 🔜🔜🔜 有关字符,字符串,数组常有的方法


最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值