欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【java基础】1. String类详解

程序员文章站 2022-07-05 19:53:57
文章目录1. String2. String类型详解2.1. String类2.2. String的创建方式2.3. 不同的创建方式在内存中的行为2.3.1 字面量方式创建2.3.1.1 字面量拼接陷阱2.3.2 构造器(new)创建3. String的不可变性3.1 String作为参数的不可变性1. StringString 是一个引用数据类型String可以和8种基本数据类型变量进行运算,且运算只能用"+"进行运算,运算后的结果为String类型2. String类型详解2.1. Str...

1. String

  • String 是一个引用数据类型
  • String可以和8种基本数据类型变量进行运算,且运算只能用"+"进行运算,运算后的结果为String类型

2. String类型详解

2.1. String类

  • String类被fianl修饰,不可被继承
  • String的底层实际上是将字符串以字符的形式存放在一个char[]类型的数组中
  • 存放字符串的数组名称为:private final char value[];
  • 存放字符的char[]数组是fianl修饰的,这也就体现了String字符串定义后的不可变性,在创建后不可修改
  • 如果要搞清楚String,首先一定要知道字符串存放在内存的哪里
  • 不论是以什么方式创建的String,其字符串存放的地址一定是在方法区的常量池中

2.2. String的创建方式

        String str1 = "0000"; //字面量方式 创建
        String str2 = new String("0000"); //构造器的方式创建
        String str3 = new String(new char[]{'0','0','0','0'}); //构造器方式创建
  • 主要方式是两种,字面量方式创建和构造器方式创建

2.3. 不同的创建方式在内存中的行为

2.3.1 字面量方式创建

        String str1 = "0000"; //字面量方式 创建
        String str2 = "0000"; //字面量方式 创建
        System.out.println(str1==str2); //true
  • 字面量方式创建会在内存中生成0或1个对象,如果常量池中不存在该字符串,就创建一个,如果存在就直接引用
  • str1使用字面量的方式创建时,此时方法区的常量池中还没有"0000"这个字符串,所以会先在方法区中的常量池中创建"0000"这个字符串,然后将"0000"这个字符串的地址值给str1引用
  • 然后再声明str2,同样是使用字面量的方式创建,由于之前在创建str1时,常量池中已经有了"0000",所以此时会直接将"0000"的地址值给str2,不会再重新在常量池中创建新的"0000"
  • 最后将str1与str2通过==进行比较,==比较的地址值,此时str1与str2的地址值相同,所以结果为ture
    【java基础】1. String类详解

2.3.2 构造器(new)创建

        String str3 = new String("0000"); //构造器创建
        String str4 = new String("0000"); //构造器创建
        System.out.println(str3 == str4); //false
  • 构造器创建会在内存中生成1或2个对象
  • str3使用构造器方式创建,由于此前常量池中没有"0000"这个字符串,所以会在常量池中创建"0000"字符串,然后在堆中创建String对象,将常量池中的"0000"的地址值给String对象的value[]引用,最后将String对象在堆中的地址给str3进行引用,一共在内存中生成了2个对象
  • str4使用构造器方式创建,由于之前创建str3时,在常量池中已经生成了"0000"这个字符串,所以不会在常量池中重复创建,不过会在堆中再重新生成一个String对象,将"0000"的地址值给String对象的value[]引用,一共只生成了一个String的对象
  • 最后用==进行对比,结果为false,因为对比的是两个在堆中生成的String类,地址值是不相等的,但是两个String对象内部的value[]是相等的
    【java基础】1. String类详解

2.3.3 字面量拼接陷阱

        String str1 = "0000";
        String str2 = "1111";

        String str3 = "00001111";
        String str4 = "0000"+"1111";
        String str5 = str1+"1111";
        String str6 = "0000"+str2;
        String str7 = str1+str2;
        System.out.println(str3 == str4); //true
        System.out.println(str3 == str5); //false
        System.out.println(str3 == str6); //false
        System.out.println(str3 == str7); //false
        System.out.println(str3 == str7.intern()); //true
  • 主题: 当进行拼接时,只有拼接的字符串全都是常量时,才是字面量方式创建,直接引用常量池中的地址,如果拼接时含有变量时,就会在堆中创建,引用的是堆中String对象的地址
  • str3==str4之所以为true,就是因为str4拼接时都是常量拼接,所以是直接引用了常量池中的字符串常量的地址
  • 其他的为false是因为,它们都是使用了变量进行拼接,所以都会在堆中创建String对象,然后引用String对象的地址
  • String.intern()方法,返回的是字符串在常量池中的地址,所以str3==str7.intern()的结果为true

3. String的不可变性

  • String类加上了fianl关键字,表示其不可被继承,并且String类中的value[]属性加上了final关键字表示value[]不可被继承和修改,但是对于数组来说,不能被修改的只是它指向的地址值,我们还是可以对数组中的元素进行修改的,为了保证元素无法被修改,value[]属性前加上了prevate进行修饰。也就是说,我们无法通过任何方法对value[]进行实质性的操作,在String类中,也没有暴露出对value[]直接操作的api,为的就是保持其不可变性与安全性
       String str5 = "0000";
       String str6 = "0000";
       str5 = str5+"1111";
  • 上面的代码中可以看到,str5与str6是相等的,常量池中只有一个"0000"字符串常量

  • 此时将str5与"1111"进行拼接,并不是将常量池中的"0000"后面追加"1111",而是在常量池中重新创建一个"00001111"的字符串,将地址给str5

  • str6引用的地址值依旧还是"0000"的地址值没有改变
    【java基础】1. String类详解

  • 如果String是可变的,当str5与str6同时引用"0000"后,str5修改后不是创建新的字符串,而是直接将"0000"进行修改,那么也会导致引用了同一个地址的str6也被修改

3.1 String作为参数的不可变性,与"引用传递"

class test{

    public static String str=new String("hello");

    public static char myChar[]={'w','o','r','l','d'};

    public static char myChar2[] = myChar;

    public static void changed(String str,char[] myChar){
        str = "hi";
        myChar[0] = 'F';
    }
}

/**
 * String不可变性
 */
public class StringFinal {
    public static void main(String[] args) {
        System.out.println(test.str); //hello
        System.out.println(test.myChar); //world
        System.out.println(test.myChar2); //world
        test.changed(test.str,test.myChar);
        System.out.println(test.str); //hello
        System.out.println(test.myChar); //Forld
        System.out.println(test.myChar2); //Forld
    }
}


  • 结论

    • 在上面的例子中,有声明为字符串的静态变量str,与声明为数组的静态变量myChar
    • 我们创建一个方法changed(),将str与myChar作为参数传进去
    • 调用changed()对str与myChar进行修改
    • 修改后打印发现,原本的静态变量myChar与myChar2已经被改变,而静态变量str并没有被改变
  • 分析: 为什么静态变量myChar的内容被改变了

    • 首先引用数据类型在进行参数传递的时候,是引用传递,也就是传递的是堆中对象的地址值
    • 调用changed()方法时,myChar作为参数传递到方法中,传递的不是myChar本身,而是myChar所引用的对象的地址
    • 所以在方法中被修改的并不是静态变量myChar,而是myChar所引用的char数组对象本身,只不过静态变量myChar的引用是指向这个char数组对象的,所以myChar被修改了
  • 分析: 为什么静态变量myChar2的内容也被改变了

    • 在changed()方法中,我们并没有将myChar2作为参数传进去,对它进行修改,但是它还是被改变了
    • 因为在定义myChar2时,我们使用了myChar2 = myChar ,所以myChar2与mychar引用的是同一个对象
    • 正如我们上面所说,修改的不是变量本身,而是对变量所引用的实际堆中的对象进行了修改
    • myChar2与myChar引用了同一个对象,所以当堆中的对象被修改时,myChar与myChar2的值就同时被修改了
  • 分析:为什么str没有被修改

    • 静态变量str作为String类型,在进行参数传递时也是引用传递,那它为什么没有被修改呢
    • 因为String是不可变的,它是重新在常量池中创建了一个"hi"的字符串常量,所以在方法中的那个str相当于是重新引用了"hi"这个字符串的地址值,但是作为静态变量的str引用的还是"hello"的地址值
    • 我曾经不理解,为什么既然方法中的str重新引用了"hi"的地址值,静态变量str却没有重新引用"h1"的地址值呢?
    • 这就是我们之前所说的"引用传递",传递过来的是静态变量str引用的String对象的地址值,而不是静态变量str本身,方法中的形参str与静态变量str只是名称相同而已,但他们绝不是同一个参数,例如我们将changed()方法的形参名称可以改一下public static void changed(String strMd,char[] myChar),就能看出来,方法中的叫strMd,而静态变量叫str,它俩只是引用的同一个String对象而已,但并不是同一个参数

本文地址:https://blog.csdn.net/small_clear/article/details/110948252