理解java中的String

String类

它是类,不是基本数据类型,拥有自己的属性、自己的方法,虽然使用上和基本数据类型有点相似

定义:是java中定义的一种字符串数据类型
用途:用以表示符号、数字、字母等相互结合的一串字符集。
特点:是一种不可变变量、拥有常量池(字符串常量池)

 /* @author  Lee Boynton
 * @author  Arthur van Hoff
 * @author  Martin Buchholz
 * @author  Ulf Zibis
 * @see     java.lang.Object#toString()
 * @see     java.lang.StringBuffer
 * @see     java.lang.StringBuilder
 * @see     java.nio.charset.Charset
 * @since   JDK1.0
 */
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
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written initially into an ObjectOutputStream in the
     * following format:
     * <pre>
     *      <code>TC_STRING</code> (utf String)
     * </pre>
     * The String is written by method <code>DataOutput.writeUTF</code>.
     * A new handle is generated to  refer to all future references to the
     * string instance within the stream.
     */
    private static final ObjectStreamField[] serialPersistentFields =
            new ObjectStreamField[0];


    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
        this.value = new char[0];
    }

    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }


    /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    .............
}


从这里开始我们带入问题来思考

1.源码中可以看到String这个类是用final修饰的,那为什么要用final?

首先我们来了解一下final。引用百度百科描述:final是java中的一个关键字,可以用来修饰变量、方法和类。用关键词final修饰的域成为最终域。用关键词final修饰的变量一旦赋值,就不能改变,也称为修饰的标识为常量。如果一个类的域被关键字final所修饰,它的取值在程序的整个执行过程中将不会改变。

假如说整个类都是final,就表明自己不希望从这个类继承,或者不答应其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。
除此以外,我们或许还考虑到执行效率的问题,并想确保涉及这个类各对象的所有行动都要尽可能地有效。

总结:不希望被改变(支持常量池可以更高效),安全性(因为不可变所以线程安全),希望可以像基本类型一样被使用(因为它是被几乎所有类都会用到的类)

2.类被final修饰后,对类的成员变量、成员方法有什么影响?

由于不能被继承,则成员方法不能被重写,会被隐式的指定为final。成员变量呢?没有任何影响,字节可以根据需要设为final或其它。

3.String是不可变的,为什么? 为什么我们在使用时又好像是可以改变它的值?

String类中有一个成员变量是char value[] 它就是用来真正保存字符串的,可以看到它是被final修饰,说明引用变量指向不可以被改变,但也只能说明value这个引用地址不可变,如果我们能够改变Array数组的话,结果会怎样?(这里需要注意一个概念,改变的是数组中存储的元素值,数组的长度、类型是固定的

final int[] value={1,2,3}  
int[] v2={4,5,6};  
value=v2;    //编译器报错,final不可变

value用final修饰,编译器不允许我把value指向堆区另一个地址,但如果我可以直接修改数组元素

final int[] value={1,2,3};  
value[2]=4;  //这时候数组里已经是{1,2,4}  

或者用反射直接修改

final int[] array={1,2,3};  
Array.set(array,2,4); //数组也被改成{1,2,4}  

所以String不可变其实是private修饰value数组且final修饰class String导致value数组不允许外部直接访问、而且String类中也没有提供能够改变arry数组的方法,所以String不可变。

我们常修改String值的方法,实际上都是创造了一个全新的String对象,用来存放修改后的字符串内容,然后将原来的引用指向新创建对象,而最初的String对象则不做任何处理,它在某一个瞬间可能会被GC掉。

substring、concat和replace方法

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    char buf[] = new char[count + otherLen];
    getChars(0, count, buf, 0);
    str.getChars(0, otherLen, buf, count);
    return new String(0, count + otherLen, buf);
}

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = count;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        int off = offset;   /* avoid getfield opcode */

        while (++i < len) {
        if (val[off + i] == oldChar) {
            break;
        }
        }
        if (i < len) {
        char buf[] = new char[len];
        for (int j = 0 ; j < i ; j++) {
            buf[j] = val[off+j];
        }
        while (i < len) {
            char c = val[off + i];
            buf[i] = (c == oldChar) ? newChar : c;
            i++;
        }
        return new String(0, len, buf);
        }
    }
    return this;
}
从上源码可以看到截取、连接、替换操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。在需要频繁使用这些操作的时候最好使用stringBuffer或者stringbuilder


4.String对象在内存中如何分配?


 
 


常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。(注意理解)

猜你喜欢

转载自blog.csdn.net/u012454084/article/details/79933705