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文件中的一些数据。(注意理解)