Java String类详解(附图解)

Java String类详解(附图解),第1张

文章目录
    • JDK中String类的声明
      • String类源码
    • 创建字符串的四种方式
    • 字符串的字面量
    • 字符串比较相等
      • 拓展equalsIgnoreCase方法
  • 重点:
    • 关于字符串的常量池问题
      • 当使用直接赋值法产生字符串对象时
      • 当使用new产生字符串对象时
    • 手工入池
      • 1.当前常量池中已经存在了该对象
      • 2.当前常量池中不存在该对象
    • 字符串的不可变性
    • 如何修改字符串的内容
      • 关于StringBuilder类的具体使用
      • StringBuilder类和String类的相互转换
      • StringBuilder类的其他方法
    • 总结

JDK中String类的声明 String类源码

为什么String类被final修饰?

因为别final修饰的类无法被继承,String类不存在子类,这样就保证所有使用JDK的人,大家用到的String类仅此一个,大家都相同!!!

创建字符串的四种方式

1.直接赋值

String str = “hello world”;

2.通过构造方法产生对象

String str = new String(“hello world”);

3.通过字符数组产生对象

char[] data = new char[]{“a”, “b”, “c”};

String str = new String(data);

4.通过String的静态方法valueOf(任意数据类型),转为字符串

String str = String.valueOf(hello world);

字符串的字面量

直接写出来的数值称之为字面量

10 -> int字面量
10.1 -> double字面量
true -> boolean字面量
"abc" -> String字面量——就是一个字符串的对象

String str = “hello world”;是字符串字面量,也是字符串的对象。

下列代码,在内存中的变化

        String str1 = "hello";
        String str2 = str;
        str2 = "world";
        System.out.println(str2);
//输出结果:world

分析:

当执行第一行和第二行代码时,首先会在堆中产生一个“hello”对象,而且str1和str2指向同一个对象,但是执行第三行代码时,会在堆中再产生一个world对象,此时str2指向新的对象也就是world对象。

world也是字符串字面量,是一个新的字符串对象,str2实际上指向了新的对象world,str1仍然指向原对象world

图示:

字符串比较相等

所有引用数据类型在比较是否相等时,使用equals方法比较,JDK常用类,都已经腹泻了equals方法。

引用数据类型使用==比较的仍然是数值,也就是比较的是地址是否相等

代码实例:

        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
//输出结果:true

此时的str1和str2指向的是同一个对象(至于为什么是同一个对象,接下来我会在字符串常量池中介绍到),既然指向的是同一个对象,而且此时比较的是地址,那就是地址相同。所以返回true。

        String str1 = new String ("hello");
        String str2 = new String ("hello");
        System.out.println(str1 == str2);
//输出结果:false

此时的这段代码,是产生了两个不相同的对象,虽然其中的值相等,但是地址却完全不同。所以返回false。

拓展equalsIgnoreCase方法

使用equalsIgnoreCase();方法不区分大小写比较字符串

代码实例:

//使用equals方法比较字符串,区分大小写

		String str1 = new String ("Hello");
        String str2 = new String ("hello");
        System.out.println(str1.equals(str2));
//输出结果:false

//使用equalsIgnoreCase方法比较字符串,不区分大小写
        String str1 = new String ("Hello");
        String str2 = new String ("hello");
        System.out.println(str1.equalsIgnoreCase(str2));
//输出结果:true

问题:

在实际开发中,让用户从浏览器输入一个String变量userName,和你定义的String变量比较是否相等。

String userName = null;
System,out,println(userName.equals("彭于晏"));	//方法一
System,out,println("彭于晏".equals(userName));	//方法二

问:上面方法一和方法二哪个更好?还是都可以无所谓?

答案:使用方法二更好,原因是,当userName为空时,就会出现空引用,会报错空指针异常!!!

因为我们要比较的特定内容本身就是字符串的字面量,一定不是空对象,把要比较的内容放在equals前面就可以避免userName为空的问题。

重点: 关于字符串的常量池问题 当使用直接赋值法产生字符串对象时

利用==比较这几个引用的地址是否相同,相同则返回true,不同返回false。

下面看两段代码,来直观了解字符串常量池。

代码段1:

String str1 = "hello";
String str2 = "hello";
String str3 = "hello";
System.out.println(str1 == str2);
System.out.println(str2 == str3);
//输出结果:
//true
//true

此时这三个引用指向了相同的内存。

代码段2:

String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");
System.out.println(str1 == str2);
System.out.println(str2 == str3);
//输出结果:
//false
//false

因为要new一个对象的时候就要在堆中开辟一块新空间,所以这三个引用指向三个不同的字符串对象,也就指向了三块不同的内存空间。

结论

​ 当使用直接赋值法产生一个字符串对象时候,JVM会维护一个字符串常量池,若该对象在堆中还不存在,则产生一个新的字符串对象加入字符串的常量池中。

​ 当继续使用直接赋值法产生字符串对象时,JVM发现该引用指向的内容在常量池中已经存在了,则此时不再新建字符串对象,而是复用已有对象。

图解内存空间变化

当使用new产生字符串对象时

同样是, 若是在堆中不存在,第一次产生的字符串对象,就会加入到常量池中。但是同时也会在堆中产生对象。

下面是使用三个引用产生三个字符串对象。

String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");

那么这三个字符串在内存中是如何产生的呢?让我们用图示来直观的看下内存变化。

图解内存空间变化

所谓的“池”,都是类似的思想,

共享设计模式 - 节省空间(因为内存是第一个非常珍贵的资源),字符串产生之后大部分情况都是要用来进行输出的,打印打的内容,所以为何不整一个放入池中就行了~~~

类似的数据库的连接池,线程池都是类似的思想~~~

手工入池

使用String提供的intern();方法

调用intern方法会将当前字符串引用指向的对象保存到字符串常量池初中。

1.当前常量池中已经存在了该对象

a.若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象。

代码实例1:

        String str1 = new String("hello");
        str1.intern();
        String str2 = "hello";
        System.out.println(str1 == str2);
//输出结果:false

图解分析:

因为str1使用new去产生一个对象,此时str1会先在堆中开辟一块空间,产生一个对象,此时hello这个字符串对象是第一次产生,所以会将hello这个对象放入到常量池中。

然后执行intern方法,会返回常量池中的String对象,但是此时没有变量去接收这个返回的String对象。随后str2通过赋值法产生字符串对象,但是此时在常量池中已经存在hello对象,所以str2直接指向常量池中的对象。

代码示例2:

    	String str1 = new String("hello");
        str1 = str1.intern();
        String str2 = "hello";
        System.out.println(str1 == str2);
//输出结果:true

图解分析:

此时当执行到第二行代码时,会先将str1产生的对象放入常量池中,因为inter方法会返回常量池中的对应的对象,所以此时str1引用会指向常量池中的对象。

2.当前常量池中不存在该对象

b.若当前常量池中不存在该对象,则将该对象入池,返回入池后的地址。

代码示例:

        char[] data = new char[]{'a','b','c'};
        String str1 = new String(data);
        str1.intern();
        String str2 = "abc";
        System.out.println(str1 == str2);
//输出结果:true

图解分析:

此时现在堆中产生一个字符数组data的对象,第二行代码中String接收的不是字符串对象,而是字符数组,此时还没有对象!!!所以只会在堆中产生一个普通的对象,而不会在常量池中加入此对象!!

这时再执行str1的intern方法,但是此时在常量池中并没有str1的对象,所以会将str1的对象入池,也就是移动到常量池中,所以此时的str1指向的是常量池中的对象。

str2通过赋值法产生字符串对象,但是此时已经在常量池中有了abc这个字符串对象,所以str2指向常量池中的对象~~~

字符串的不可变性

所谓的字符串不可变值得是内容不能变,而不是引用不能变。

这里的不可变指的是字符串对象hello,world,!!!这三个对象不能变!!!一旦声明后无法修改其内容!!!

String str = "hello";
str += "world";
str += "!!!"
System.out.ptintln(str);

图解分析:

因为都是通过赋值法产生字符串对象,所以第一次产生的对象都会放入常量池中,后边不再赘述。

首先会在常量池中产生一个hello对象,str指向该对象。

然后会在常量池中产生一个world对象,随后还会产生一个helloworld对象,str此时指向该对象。

同理,还会再产生一个!!!对象,和helloworld!!!对象,此时str指向该对象。

但是在内存中,这些队产生后就不可修改!!!

问题:为何字符串的对象无法修改内容,但是其他类型的对象能修改内容?

字符串其实就是一个字符数组char[],字符串的保存实际上在数组中保存。

而且是一个被private修饰的字符数组,所以外部根不能无法拿到这个变量,更何况修改这个变量?

如何修改字符串的内容

a.在运行时通过反射破坏value数组的封装(不推荐,所以在这里不展开介绍,后边再讲到反射时候会展开介绍~~~)

b.更换使用StringBuilder类或者StringBuffer类,此时已经不是String类型了

这两个都是类!!!调用类中的方法。

应用场景:

​ 若需要频繁进行字符串的拼接,使用StringBuilder类的append();方法。(只会产生一个对象,对内容进行修改)

​ StringBuffer使用方法和StringBuilder用法一样,只是有线程安全,但是性能较差。

代码示例:

		StringBuilder str = new StringBuilder();
        str.append("hello");
        str.append("world");
        str.append("!!!");
        System.out.println(str);
//输出结果:helloworld!!!
关于StringBuilder类的具体使用

StringBuilder类和String类是两个独立的类, StringBuilder就是为了解决字符串拼接问题产生的。

因为String类的对象无法修改内容,为了方便字符串的拼接 *** 作,产生了StringBuilder类,StringBuilder类的对象是可以修改内容的。

StringBuilder类和String类的相互转换

1.StringBuilder类变成String类

​ 调用toString方法

String str1 = str.toString();

2.String类变成StringBuilder类

​ 使用StringBuilder的构造方法,

StringBuilder sb = new StringBuilder(“hello”);

​ 使用append方法

sb.append("123");
StringBuilder类的其他方法

a.字符串反转 *** 作reverse()方法

不需要传入变量

​ 代码示例:

        StringBuilder str = new StringBuilder();
        str.append("hello");
        str.append("world");
        str.append("!!!");
        str.reverse();
        System.out.println(str);
//输出结果:!!!dlrowolleh

b.删除指定范围的数据delete()方法

delete(int left,int right),传入所以范围,左闭右开区间

       StringBuilder str = new StringBuilder();
        str.append("hello");
        str.append("world");
        str.append("!!!");
        str.delete(5,10);
        System.out.println(str);
//输出结果:hello!!!

c.插入 *** 作insert()方法

insert(int index , 任意类型数据),从索引为index的位置插入任意数据类型的数据

​ 代码示例:

		StringBuilder str = new StringBuilder();
        str.append("hello");
        str.append("world");
        str.append("!!!");
        str.insert(3,"3");
        System.out.println(str);
//输出结果:hel3loworld!!!

面试题

请解释String,StringBuilder,StringBuffer的区别:

1.String的对象无法修改,俩sb的对象内容可以修改。
2.StringBuffer是线程安全的 *** 作,性能较差;StringBuilder是线程不安全,性能较高。

总结

1.关于字符串常量池以及intern方法的理解问题

2.理解啥是字符串不可变性

3.StringBuilder的使用

4.要使用String类,就采用直接赋值的方式,要比较引用类型是否相等就使用equals方法。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/887131.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-14
下一篇2022-05-14

发表评论

登录后才能评论

评论列表(0条)

    保存