一、什么是字符串?
定义:字符串是一个或多个字符的序列
在java中用char数组来表示字符串,我们可以从源码看到:java对char数组进行了封装,并用String类型来表达字符串,也就是说java程序中所有字符串的文字都被成为此类的对象。
#java 8源码
/** The value is used for character storage. */
private final char value[];
看了字符串的定义之后,让我们来看一下
1.字符串的赋值方式?
2.两种赋值方式的区别
3.解释为什么String是不可变的?
二、赋值方式
1.直接赋值
顾名思义,直接赋值就如下所示:
String s1="abc";
2.new
String s2=new String("abc");
可以看到这两种方式,都可以创建一个字符串,那么我们现在来解释一下两种赋值方式的区别。
区别:直接赋值会创建一个对象或者不创建对象,而new的方式至少会创建一个都对象。
在说明这个区别之前,我们要了解几个概念:
1.堆内存:存储对象或者数组,new来创建的,都存在堆内存中。
2.栈:方法运行时使用的内存,比如main方法运行,进入方法栈中执行。
3.常量池:我们在开发中,会经常大量使用String类型,java对其进行了性能优化。
看下面一段代码:
String a1=new String("dog");
String a2=new String("dog");
System.out.println(a1==a2); //运行结果false
这段代码可以看出,按照常规方式创建字符串对象的时候,即使字符串内容完全一致,也会创建多个对象,想象一下如果要使用几万个字符串,都要创建对象的话,那么会极大的浪费资源,所以就引入了字符串常量池这个概念:
当我们用字面量创建字符串时,字符串常量池会将其对象引用进行保存,后面如果创建重复的字面量,就会直接将字符串常量池的引用进行返回。
public class demo11 {
public static void main(String[] args) {
String s1="abc";
String s2="abc";
System.out.println(s1==s2);//结果为true
}
}
上图中代码可以看出对s1和s2使用直接赋值的方式,字面量都是"abc"。直接赋值时,系统都会检查该字符串在串池是否存在,不存在的话则创建新的,存在的话,可以看到字符串常量值"abc"的地址值返回。这也就解释了直接赋值的方式会创建一个或者不会创建对象。
那么对于new的方式,直接看内存原理图和代码就很好理解了。
public class demo98{
public static void main(String[] args) {
String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2);//运行结果为false
}
}
从内存图可以看出,当s1创建字符串对象时,发现常量池中并没有”abc"这个字符串这个对象,它就会在常量池中创建一个对象还会在堆上创建一个字符串s1并指向常量池,而且将s1的地址值返回,这时就是创建了两个对象。当s2创建对象的时候,也会去检查常量池中是否有该字符串,若有的话,直接在堆中创建一个新的String对象,这个对象也会指向常量池中的"abc",然后把这个新的String的地址返回,这时就是创建了一个对象。
那么,可能有人会说,常量池到底有没有我们也不知道啊,你这样说,也没有依据啊,那我们就来看一下字节码文件,通过javap -v demo98.class进行编译查看结果
Constant pool:
#1 = Methodref #8.#21 // java/lang/Object."<init>":()V
#2 = Class #22 // java/lang/String
#3 = String #23 // abc
#4 = Methodref #2.#24 // java/lang/String."<init>":(Ljava/lang/String;)V
#5 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #27.#28 // java/io/PrintStream.println:(Z)V
#7 = Class #29 // stringDemo/demo98
#8 = Class #30 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 StackMapTable
#16 = Class #31 // "[Ljava/lang/String;"
#17 = Class #22 // java/lang/String
#18 = Class #32 // java/io/PrintStream
#19 = Utf8 SourceFile
#20 = Utf8 demo98.java
#21 = NameAndType #9:#10 // "<init>":()V
#22 = Utf8 java/lang/String
#23 = Utf8 abc
#24 = NameAndType #9:#33 // "<init>":(Ljava/lang/String;)V
#25 = Class #34 // java/lang/System
#26 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#27 = Class #32 // java/io/PrintStream
#28 = NameAndType #37:#38 // println:(Z)V
#29 = Utf8 stringDemo/demo98
#30 = Utf8 java/lang/Object
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 (Ljava/lang/String;)V
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 println
#38 = Utf8 (Z)V
我们可以看到,常量池会把abc存进去,供之后复用。
到这里,还有一个问题就是那么String为什么是不可变的呢?
首先,从前面的知识我们知道,当对一个字符串进行直接赋值时,它会到常量池先去查找这个字符串,如果没有,则会创建一个新的字符串对象,并将其返回,而不是在原来的基础上进行修改。
其次,从原码的角度解释:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
final:
.final修饰的变量表示变量的值不可改变,被final修饰过的变量就是常量
.final修饰方法表示此方法不可以被重写
.find修饰类表示此类不能被继承
1.第一点:可以看到String这个类被final修饰,表示该类不能被继承,从而杜绝了子类覆盖父类行为的过程
2.第二点:就是String字符串底层存储的实现也是用char value[]这个数组,这个数组也被final修饰,但是只说明它不能指向新的数组,不代表数组本身的数据不可被改变,真正不可变的原因是他还被private修饰,并且String并没有暴露提供任何修改字符数组的方法。
综上两点,说明String时不可变的。
标签:lang,abc,java,String,Utf8,----,字符串,Java From: https://blog.csdn.net/qq_44766305/article/details/141997945