【java基础】1.一篇玩明白什么是String

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
    字面量方式创建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[]是相等的
    构造器(new)方式创建的内存图
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"的地址值没有改变
    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对象而已,但并不是同一个参数
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值