String是个啥?

String是个啥?

  字符串?不可变字符串?今天想起来这个又意思的东西,所以来记录一下。我们说String是不可变字符串,那他就真的不可变吗?

public class StringDemo {
    public static void main(String[] args) {
        String s = " a b ";
        s.trim();
        System.out.println("-" + s + "-");
    }
}

输出:- a b -
空格没有被删除

public class StringDemo {
    public static void main(String[] args) {
        String s = " a b ";
        s = s.trim();
        System.out.println("-" + s + "-");
    }
}

输出:- a b -
空格没有被删除

public class StringDemo {
    public static void main(String[] args) {
        String s = " a b ";
        String ss = s.trim();
        System.out.println("-" + ss + "-");
    }
}

输出:-a b-
空格被删除
public class StringDemo {
    public static void main(String[] args) {
        String s = " a b ";
        String ss = s.trim();
        System.out.println("-" + s + "-");
    }
}

输出:- a b -
空格没有被删除

  我蒙蔽了,来理一理,先看一看String的源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];

    private int hash; 
    
    private static final long serialVersionUID=-6849794470754667710L;
    ......
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--;
        }
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
    }

 String类为什么被称为不可变字符串呢?是因为类上面加了final还是value[]数组上面加了final呢?还是因为String类没有提供可变的方法呢?我们不妨利用反射拿到这个value数组试一试。

public class StringDemo {
    public static void main(String[] args) throws Exception {
        String s = " a b ";
        Class clazz = s.getClass();
        Field f = clazz.getDeclaredField("value");
        f.setAccessible(true);
        char[] cs = (char[])f.get(s);
        cs[1] = 'A';
        System.out.println(s);
    }
}
输出: A b 

  这说明它是可变的啊,final只不过是约束了变量所指向的地址不能变,并不是说地址段的内容不能变,也就说,虽然value数组使用final修饰,但这只是定义了我value变量指向的地址不能变,但是地址段本身的内容(数组的内容)是可变的。

  String 是final类,也就是说,一旦定义了String类型的变量,那么变量本身所指向的地址就不能变了。但是String变量指向的地址的内容可变吗?这你就得注意点了,String类型变量和普通类型变量以及引用类型变量都有区别,虽然String类型变量是引用类型,但他还有另一个身份,String类型的变量还是个常量!既然是常量,那JVM就会把它区别对待,把它保存在方法区的常量池中。

  当s.trim()时,trim()方法入栈,在本方法的栈针中,定义了一个新的char类型数组val,并指向保存原字符串信息的char数组value,但由于数组是基本类型的,所以并没有传引用,而是直接把数组的内容复制给了新的数组val,经过循环操作完成方法后返回一个字符串,但是这个字符串是个常量,JVM如果发现这个常量在常量池中不存在,就会把他保存在常量池中,注意了:这个新的字符串常量和原字符串常量在不同的内存块。但是由于s是final修饰的,所以s并不能改变地址指向新的字符串常量,所以s的输出的值不变,与s不同的是,String ss = s.trim()刚好站了出来,说:我来指向它!于是,ss的输出的值就成了新的字符串常量。如果没有ss指向那个新的常量,那他就可能过会儿被GC了。

posted @ 2019-12-05 21:44  菜菜菜鸡  阅读(719)  评论(0编辑  收藏  举报