送大家以下java学习材料,文末有支付形式
String不可变吗?
`public class App {` `public static void main(String[] args) {` `String a = "111";` `a = "222";` `System.out.println(a);` `}``}`
有的人会认为下面这段代码应该输入:111
这样才和下面的不变性吻合。
哈哈哈,然而并不是这样滴。
222
这不对呀,不是不变吗?怎么变了呢?
其实在JVM的运行中,会独自给一块地分给String。
下面的:
`Stirng a="111";`
咱们晓得字符串的调配和其余对象调配一样,是须要耗费昂扬的工夫和空间的,而且字符串咱们应用的十分多。JVM为了进步性能和缩小内存的开销,在实例化字符串的时候进行了一些优化:
应用字符串常量池。每当咱们创立字符串常量时,JVM会首先查看字符串常量池,如果该字符串曾经存在常量池中,那么就间接返回常量池中的实例援用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。因为String字符串的不可变性咱们能够非常必定常量池中肯定不存在两个雷同的字符串。
这里先去JVM给常量池里找,找到了就不必创建对象了,间接把对象的援用地址赋给a。找不到会从新创立一个对象,而后把对象的援用地址赋给a。同理a="222";也是先找,找不到就从新创立一个对象,而后把对象的援用地址赋给a。
大家有没有发现我下面的形容中“援用地址”。比如说 Object obj = new Object();很多人喜爱成obj为对象,其实obj不是对象,他只是一个变量,而后这个变量里保留一个Object对象的援用地址罢了。
援用类型申明的变量是指该变量在内存中理论存储的是一个援用地址,实体在堆中。
所以网上很多文章老喜爱这么说
`User user = new User()`
创立了一个user对象,老喜爱把user称之为对象。这里不承受反驳。
所以下面String a = “111”;表白的是变量a里保留了“111”这个对象的援用地址。变量是能够变的,不能变的是“111”。
String 为什么是不可变的?
简略的来说,String 类中应用 final 关键字字符数组保留字符串。代码如下:
`public final class String implements java.io.Serializable, Comparable<String>, CharSequence {` `private final char value[];` `public String() {` `this.value = "".value;` `}` `public String(String original) {` `this.value = original.value;` `this.hash = original.hash;` `}` `public String(char value[]) {` `this.value = Arrays.copyOf(value, value.length);` `}``}`
从下面的这段源码中能够看出三点:
String 类是final润饰
String存储内容应用的是char数组
char数组是final润饰
这里就得温习一下,final有啥用?
当用final润饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就能够用final进行润饰。final类中的成员变量能够依据须要设为final,然而要留神final类中的所有成员办法都会被隐式地指定为final办法。
当final润饰的办法示意此办法曾经是“最初的、最终的”含意,亦即此办法不能被重写(能够重载多个final润饰的办法)。此处须要留神的一点是:因为重写的前提是子类能够从父类中继承此办法,如果父类中final润饰的办法同时访问控制权限为private,将会导致子类中不能间接继承到此办法,因而,此时能够在子类中定义雷同的办法名和参数,此时不再产生重写与final的矛盾,而是在子类中从新定义了新的办法。(注:类的private办法会隐式地被指定为final办法。)
当final润饰一个根本数据类型时,示意该根本数据类型的值一旦在初始化后便不能发生变化。如果final润饰一个援用类型时,则在对其初始化之后便不能再让其指向其余对象了,但该援用所指向的对象的内容是能够发生变化的。实质上是一回事,因为援用的值是一个地址,final要求值,即地址的值不发生变化。另外final润饰一个成员变量(属性),必须要显示初始化。这里有两种初始化形式,(1.在申明的时候给其赋值,否则必须在其类的所有构造方法中都要为其赋值)
比方:
`/**` `* Description: final润饰变量` `* @author : 田维常` `*/``public class FinalDemo {` `private final String name;` `public FinalDemo(String name) {` `this.name = name;` `}` `public FinalDemo() {` `}``}`
这是会会报错
img
对于final就简略说到这里
上面来看一个应用String的 案例
`/**` `* Description:` `*` `* @author : 田维常` `* @date : 2020/11/3` `*/``public class StringDemo {` `public static void main(String[] args) {` `String name = "老田";` `name.concat("!");` `System.out.println(name);` `System.out.println(name.concat("!"));` `}``}`
输入
img
顺道溜达溜达 String中几个罕用办法源码
`public String concat(String str) {` `int otherLen = str.length();` `if (otherLen == 0) {` `//啥都没有,就间接把以后字符串给你` `return this;` `}` `int len = value.length;` `char buf[] = Arrays.copyOf(value, len + otherLen);` `str.getChars(buf, len);` `//看到了吗?返回的竟然是新的String对象` `return new String(buf, true);` `}` `void getChars(char dst[], int dstBegin) {` `System.arraycopy(value, 0, dst, dstBegin, value.length);` `}` `public String replace(char oldChar, char newChar) {` `//如果两个是一样的,那就必要替换了,所以返回this` `if (oldChar != newChar) {` `int len = value.length;` `int i = -1;` `//把以后的char数组复制给val,而后上面基于val来操作` `char[] val = value;` `while (++i < len) {` `if (val[i] == oldChar) {` `break;` `}` `}` `if (i < len) {` `//创立一个新的char数组` `char buf[] = new char[len];` `for (int j = 0; j < i; j++) {` `buf[j] = val[j];` `}` `while (i < len) {` `char c = val[i];` `buf[i] = (c == oldChar) ? newChar : c;` `i++;` `}` `//创立一个新的String对象` `return new String(buf, true);` `}` `}` `return this;` `}` `public String substring(int beginIndex, int endIndex) {` `if (beginIndex < 0) {` `throw new StringIndexOutOfBoundsException(beginIndex);` `}` `if (endIndex > value.length) {` `throw new StringIndexOutOfBoundsException(endIndex);` `}` `int subLen = endIndex - beginIndex;` `if (subLen < 0) {` `throw new StringIndexOutOfBoundsException(subLen);` `}` `//失常返回的都是新new进去的String对象` `return ((beginIndex == 0) && (endIndex == value.length)) ? this` `: new String(value, beginIndex, subLen);` `}` `public String trim() {` `int len = value.length;` `int st = 0;` `char[] val = value; /* avoid getfield opcode */` `while ((st < len) && (val[st] <= ' ')) {` `st++;` `}` `while ((st < len) && (val[len - 1] <= ' ')) {` `len--;` `}` `//如果是该字符串中蕴含了空格,调用substring办法,否则就是啥都没干本来返回` `//就是如果字符串里有空格,那么还是新生一个String对象返回` `return ((st > 0) || (len < value.length)) ? substring(st, len) : this;` `}`
无论是concat、replace、substring还是trim办法的操作都不是在原有的字符串上进行的,而是从新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被扭转。
得出两个论断:
String对象一旦被创立就是固定不变的了,对String对象的任何扭转都不影响到原对象,相干的任何变动性的操作都会生成新的对象。
String对象每次有变动性操作的时候,都会从新new一个String对象(这里指的是有变动的状况)。
回到后面的例子
`//String a = "111";相当于``char data [] ={'1','1','1'};``Stirng a = new String(data);``//a = "222";``char data [] ={'2','2','2'};``a = new String(data);`
img
这会变量a里保留的是"222"对应String对象的援用。
持续看上面的代码
`public class App {` `public static void main(String[] args) {` `String a = "111";` `String a1 = "111";` `String b = new String("111");` `//对象地址是同一个` `System.out.println(a==a1);` `//对象内容是一样的` `System.out.println(a.equals(a1));` `//对象地址不一样` `System.out.println(a==b);` `//对象内容是一样的` `System.out.println(a.equals(b));` `}``}`
输入
`true``true``false``true`
第一个输入true,阐明a和a1两个变量保留的援用地址是同一个。
第二个也输入true,阐明a和a1援用地址中内容是一样的。
a和a1放在栈上,寄存着对象的援用地址。
new的对象是在堆中。
常量其实是要看jdk版本的。
img
img
所以String a = "111"; 在JVM申请内存寄存"111"对应的对象,并将对象保存起来。当String a1="1111";的时候,会先去JVM的那块地里寻找是否存在"111",刚好后面保留过,所以找到,而后间接把对象的援用地址给了a1。所以此时的a和a1都保留着同一个援用地址。
接触java后都晓得能够new一个对象。所以 String b = new String("111");就是创立一个对象而后把对象援用地址赋给变量b。然而这里有个非凡点,那就是(“111”),这里会先去JVM里的那块地里找找,找到了间接寄存援用地址。找不到创立一个对象而后把援用地址给String的有参构造方法里。
所以第三个中输入false,因为a和b所保留的对象援用是不一样的。
最初一个输入true。那是因为两个变量所保留的援用地址中的内容都是“111”.
答案:
如果常量池中存在,则只需创立一个对象,否则须要创立两个对象。