年轻人的第一篇String类超详解——Java中的String类的常见 *** 作、常量池、与字符数组的互相转换

年轻人的第一篇String类超详解——Java中的String类的常见 *** 作、常量池、与字符数组的互相转换,第1张

文章目录
  • 一、String类
  • 二、创建字符串的四种方式
    • 1.直接赋值
    • 2.通过构造方法产生对象
    • 3.通过字符数组产生对象
    • 4.通过String的静态方法valueOf(任意数据类型)转为字符串
  • 三.字面量和常量池
    • 1.字面量
    • 2.字符串比较相等
      • 1.equals方法
      • 2.equalsIgnoreCase方法。
      • 3.compareTo方法
    • 3.关于字符串的常量池问题
    • 4.手工入池 intern方法
  • 四.字符串的不可变性
    • 1.不可变性
    • 2. 修改字符串的内容
    • 3.StringBuilder类和String类的转换
    • 4.StringBuilder类的其他方法
      • 1. 字符串的反转 *** 作
      • 2. 字符串删除指定范围数据
      • 3. 插入 *** 作
      • 4.String、StringBuilder、StringBuffer的区别
  • 五.字符串和字符数组的互相转换
    • 1.String转为char:
    • 2.char转为String:
    • 3.判断一个字符串的对象是由纯数字组成
  • 总结


一、String类

JDK中的String类:

为什么String类被final修饰呢?被final修饰后无法被继承,不存在子类,这样就可以保证所有使用JDK的人,大家用到的String类仅此一个,大家都相同。否则的话会导致子类行为不一致的问题。

二、创建字符串的四种方式 1.直接赋值

代码如下:

String str = "hello world";
2.通过构造方法产生对象

代码如下:

String str2 = new String("hello world");
3.通过字符数组产生对象

代码如下:

char[] data = new char[] {'a','b','c'};
String str = new String(data);
4.通过String的静态方法valueOf(任意数据类型)转为字符串

代码如下:

String str = String.valueOf(10);

最常用的是方式一和方式四

三.字面量和常量池 1.字面量

直接写出来的数值就成为字面量。
比如:10,就是int字面量;10.1,double字面量;true,boolean字面量;“abc”,String字面量。字符串(String)是一个引用数据类型,实际上字符串的字面量就是一个字符串的对象。
String str = “hello world”;字符串字面量,也是字符串的对象。str就是字符串的一个引用。

public class StringTest {
    public static void main(String[] args) {
        String str ="hello world";
        String str1 = str;
        str1 = "Hello";
        System.out.println(str);
    }
}

结果如下:

此时str1 = “Hello”;Hello也是字符串的字面量,是一个新的字符串对象,str1实际上指向了新的字符串对象"Hello";str仍指向原字符串对象"hello world"。

2.字符串比较相等
  1. 所有引用数据类型比较相等时,使用equals方法比较,JDK常用类,都已经覆写了equals方法,大家直接使用即可。
  2. 引用数据类型使用"=="比较的仍然是数值(就是地址是否相等)。
1.equals方法
		String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1==str2);

结果如下:

这并不是说他俩的值相等,而是他们两个的地址相等,指向的是同一块地址。

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

结果如下:

为什么这个时候false呢?因为new出来的是两个不同的对象。他们的地址肯定是不同的,我们需要使用equals方法比较他们的数值是否相等。

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

结果如下:

2.equalsIgnoreCase方法。

我们如果把str2中的hello改为Hello,此时再运行就会发现输出false,因为用equals方法比较是区分大小写的,我们要想不区分大小写来比较需要使用equalsIgnoreCase方法。

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

运行结果如下:

假设此时我们有一个由用户输入的用户名:

那么是第一种输出方式好呢还是第二种,当然是第二种,我们要记住,所有牵扯到用户自己输入的都需要进行判空,如果此时userName是一个null那么再通过userName去"."equals方法,就可能会发生空指针异常。


更推荐使用第二种方式。因为我们要比较的特定内容本身就是字符串字面量,一定不是空对象,把要比较的内容放在equals的前面,就可以方便处理userName为空的问题。

3.compareTo方法
		String str1 = "abc";
        String str2 = "Abc";
        System.out.println(str1.compareTo(str2));

运行结果如下:


这个32是 a 和 A 的ASCII码值的差值。A是65,a是97.
按照字典序排列字符串:就是按照字符串内部的字符的ASCII码大小排序。
相等返回0
小于返回 负值
大于返回 正值

3.关于字符串的常量池问题

所谓的"池",都是一种共享设计模式的思想,当前进程中,所有常量池中的常量都是共享的。当字符串产生之后大部分情况下都是用来进行输出的,也就是打印它的内容。也就是说"hello"只要有一个就行了,这样做可以节省空间。

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

运行结果如下:

说明这三个引用指向了相同的内存。

		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);

运行结果如下:

这三个引用指向不同的字符串对象。
我们来分析一下:

  • 当使用直接赋值法产生对象时,JVM会维护一个字符串常量池,若该对象还在堆中不存在,则产生一个新的字符串对象加入字符串的常量池中。
  • 当继续使用直接赋值法产生字符串对象时,JVM发现该引用指向的内容在常量池中已经存在了,则此时不再新建字符串对象,而是复用已有对象。
  • JDK8之后将常量池放在了JVM的堆中存储。

我们来看一下上面两种产生字符串方式的内存分析:

上图中三行代码实际上只产生了一个字符串。

那当我们采用new产生新的字符串时又是怎么一回事呢?

String str1 = new String("hello");

程序的执行是从右向左,当字符串字面量"hello"第一次出现的时候,字符串常量池中还没有这个东西,就产生一个"hello"存入常量池中。下来前面又有一个new,有new就有新空间,在堆中就又产生了一个新的字符串对象,这个字符串对象的值也是"hello",并且这个字面量不会进入常量池,就在堆中存储。就相当于这一行代码,产生了两对象。str指向了堆中的普通的"hello",当执行第二行代码时,此时常量池中已经有"hello"了,所以不再产生新的字符串字面量,然后又new一个新的"hello"在堆中存储,str2指向它。以此类推,就相当于这三行代码,产生了四个字符串对象。其中一个在常量池中,其余三个在堆中。

内存图如下:

4.手工入池 intern方法

String类提供的intern方法。

这是一个本地方法,调用intern方法会将当前字符串引用指向的对象保存到字符串常量池中。它有下面两种情况:

  1. 若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象。
  2. 若当前常量池中不存在该对象,则将该对象入池,返回入池后的地址。

我们来看看使用intern方法的几种例子:
eg1:

//这个str1指向堆中普通的字符串对象
        String str1 = new String("hello");
        str1.intern();
        String str2 = "hello";
        System.out.println(str1 == str2);

结果如下:

为什么时false呢?

  • 当执行第一行代码时,字符串字面量"hello"还从来没有出现过,此时就产生一个"hello"存入常量池中。下来前面又有一个new,有new就有新空间,在堆中就又产生了一个新的字符串对象,这个字符串对象的值也是"hello",并且这个字面量不会进入常量池,就在堆中存储。str1指向了堆中的普通的"hello"。 这一行代码产生了两个对象。
  • 当执行第二行代码的时候 str1.intern(); 常量池中已经存在"hello",不会再产生一个新的字符串对象,而是返回常量池中已经存在的字符串对象的地址。此时str1只是调用了一下intern方法,并没有去接收这个方法的返回值。
  • 当执行第三行代码的时候,直接给str2赋值一个"hello" 此时常量池中已经有这个"hello"了,不是第一次出现,所以str2指向的就是常量池中的这个"hello"。
  • 所以他们俩地址不相等。
    内存图如下:

那如何让他们俩相等呢?只需要让str1去接收一下intern方法的返回值即可:

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

运行结果如下:


因为此时str1接收的是常量池中"hello"的地址,所以它两相等。
内存图如下:

eg2:

		char[] data = new char[] {'a','b','c'};
        String str1 = new String(data);
        str1.intern();
        String str2 = "abc";
        System.out.println(str1 == str2);

运行结果如下:

为什么此时又是true呢?

  • 执行第一行代码时,首先先new一个字符数组,有new就有新空间,在堆中开辟一个字符数组存放a,b,c三个值。
  • 执行第二行代码时,我们new String (data);时,这里面没有字符串,data是一个字符数组,还没有字符串对象呢,所以常量池中还是空。此时只是在堆中产生了一个普通的字符串对象,它的值为"abc",由data数组赋值而来的。str1就指向了普通的"abc"。这一行代码产生了一个对象。
  • 执行第三行代码时,str1.intern();此时常量池中没有该对象,就把普通的"abc"挪到常量池中,也就是入池,并不是拷贝过去。str1还是指向这个"abc"。
  • 执行第四行代码时,此时的常量池中已经有了"abc",不再产生新的字符串对象,而是直接复用已经存在的"abc",所以str2指向常量池中已经存在的"abc",和str1指向的是同一个地址。所以运行结果是true。
    内存图如下:

    没调用intern方法之前,str1指向的"abc"是在常量池外面的。
四.字符串的不可变性 1.不可变性

所谓的字符串不可变指的是字符串对象的内容不能变,而不是字符串引用不能变。

比如:

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

运行结果如下:

这里的不可变指的是 “hello” “world” “helloworld” “!” “helloworld!” 这几个字符串对象不可变,一但声明后就无法修改其内容。

  • 刚开始的时候栈中只有str一个引用,堆中有常量池
  • 执行第一行代码的的时候常量池中只有"hello"这个对象,str指向它。
  • 执行第二行代码的时候先 + 了一个"world",现在常量池中产生了一个"world",然后进行拼接,常量池中产生一个"helloworld",str指向了"helloworld"。
  • 执行第三行代码的时候,现在常量池中产生了一个"!“,然后进行拼接常量池中又产生了一个"helloworld!”,str又指向了"helloworld!"。

内存图如下:

字符串的引用是可以随意改变的。
为什么字符串的对象无法修改内容而其他类的对象能修改内容? 我们来看看JDK8中String类的源码。(在JDK17中是byte数组)

字符串其实就是一个字符数组 char[ ],字符串保存的值实际上在数组中保存。这个value数组被private修饰封装,String类的外部无法访问value这个数组。String类内部并没有提供关于value属性的getter和setter方法。对于String类的外部而言,value完全无法使用。因此字符串对象的内容无法修改(String类的外部根本拿不到这个value数组)。
内存图如下:

value是一个数组。

2. 修改字符串的内容
  1. 在运行时通过反射破坏value数组的封装(不推荐,以后再讲)。
  2. 更换使用StringBuilder或者StringBuffer类。但是这两个已经不是String类型了。StringBuffer是线程安全,性能较差,使用起来和StringBuilder没什么区别。
  • 若需要频繁进行字符串的拼接,我们使用StringBuilder类的append方法。
  • StringBuilder类可以修改对象的内容(不是String类)。
public class StringBuilderTest {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append("hello");
        sb.append("world");
        sb.append("!");
        System.out.println(sb);
    }
}

运行结果如下:

这里面的值在不停的修改,并不是在拼接。

3.StringBuilder类和String类的转换

StringBuilder和String类是两个独立的类,StringBuilder类就是为了解决字符串拼接问题产生的。因为String的对象无法修改内容,为了方便字符串的拼接 *** 作,产生了StringBuilder类,StringBuilder类的对象是可以修改内容的。

  1. 将String类转换为StringBuilder类
  • 使用StringBuilder的构造方法或者append方法

    这里的"hello"就是一个字符串字面量,我们调用了StringBuilder的构造方法,将一个字符串的字面量转换为了StringBuilder对象。


这也是将字符串字面量转换为StringBuilder类

  1. 将StringBuilder类转换为String类
    调用toString方法:

    运行结果如下:
4.StringBuilder类的其他方法 1. 字符串的反转 *** 作

调用reverse();方法。

 StringBuilder sb = new StringBuilder("hello");
        sb.append("123");
        sb.reverse();
        String str = sb.toString();
        System.out.println(str);

运行结果如下:

2. 字符串删除指定范围数据

调用delete(int start, int end);方法
删除从start索引开始,end之前的所有内容。左闭右开,按照索引删除

    StringBuilder sb = new StringBuilder("hello");
        sb.append("world");
        sb.delete(5,10);
        String str = sb.toString();
        System.out.println(str);

运行结果如下:

3. 插入 *** 作

insert(int start,各种数据类型);
将新元素插入当前StringBuilder对象,插入后新值的其实索引为start

 StringBuilder sb = new StringBuilder("hello");
        sb.append("world");
        sb.delete(5,10);
        sb.insert(5,10);
        String str = sb.toString();
        System.out.println(str);

运行结果如下:


插入之后,10这个元素的位置就是5。

4.String、StringBuilder、StringBuffer的区别
  1. String的对象无法修改, StringBuilder和StringBuffer可以修改
  2. StringBuffer是线程安全的 *** 作,性能较差;StringBuilder是线程不安全,性能较高。除此之外,他们两的其他 *** 作完全一样,方法名都一样。
五.字符串和字符数组的互相转换 1.String转为char:
  1. 取出字符串中,指定索引的字符:
    调用String类的charAt方法
		String str = "hello";
        System.out.println(str.charAt(1));

输出结果如下:

  1. 将字符串中的内容转为字符数组 String->char[ ]
    调用 toCharArray() 方法
		String str = "hello";
        char[] data = str.toCharArray();
        System.out.println(data);
        System.out.println(data[0])

运行结果如下:

说明这个字符数组索引为0的值是h

2.char转为String:
  1. 通过String类的构造方法:
		char[] ch = new char[] {'a','b','c'};
		
        String str = new String(ch);
        
        System.out.println(str);

运行结果如下:

我们怎么确定他就转换成功了呢? 我们可以通过调用String类的方法来确定

System.out.println(str.length);

结果如下:

  1. 通过String类的valueOf方法:
		char[] ch = new char[] {'a','b','c'};
      
        String str1 = String.valueOf(ch);
        
        System.out.println(str1);

运行结果如下:

其实这个valueOf在JDk内部还是调用的String类的构造方法。

  1. 将字符数组的部分内容转为字符串对象
    还是通过构造方法:
		char[] ch = new char[] {'a','b','c'};
        String str = new String(ch);
        String str1 = new String(ch,1,2);

        System.out.println(str);
        System.out.println(str1);

运行结果如下:

构造方法里的1代表字符数组开始的索引,2代表转换的字符个数。

3.判断一个字符串的对象是由纯数字组成
  1. 自己写一个循环去遍历
public class StringTest {
    public static void main(String[] args) {
   		String str1 = "123";
        String str2 = "123a";
        System.out.println(isNumber(str1));
        System.out.println(isNumber(str2));
        }
    public static boolean isNumber(String str) {
        //转为字符数组
        char[] data = str.toCharArray();
        //循环遍历data中的每个字符,判断这个字符是否是数字字符
        for (char c : data) {
            if (c < '0' || c > '9') {
                return false;
            }
        }
        return true;
    }
}
  1. 待用Character包装类的 isDigit() 方法:
public static boolean isNumber(String str) {
        //转为字符数组
        char[] data = str.toCharArray();
        //循环遍历data中的每个字符,判断这个字符是否是数字字符
        for (char c : data) {
        //判断字符c是否是数字,返回true或false
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }

运行结果如下:

这两个方法干的事完全一样。不过一个是我们自己写的,一个是JDK给我们写好的。


总结

要使用String类,就采用直接赋值的方式。要比较内容是否相等使用equals方法。还有一些字符串的其他 *** 作。
关于String类还有其他常见的 *** 作,要是再写下去文章未免过于臃肿,不利于大家阅读。
如文中有不足之处,还请批评指出。

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

原文地址: https://outofmemory.cn/langs/876673.html

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

发表评论

登录后才能评论

评论列表(0条)

保存