要看String这个类,当然还是根据官方出的API以及源码入手会更加合适,首先来看官方API中如何定义的。
The String class represents character strings. All string literals in Java programs, such as "abc", are implemented as instances of this class.Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.
String类表示字符串。Java程序中的所有字符串字面值,如“abc”,都作为该类的实例实现。字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变字符串。因为字符串对象是不可变的,所以可以共享它们。
那么就比较清楚了,String这个类在名义上来说是不可以修改的,每一个字符串字面值都作为一个常量来对待,每一个字符串都是可以共享的。
-
String类的声明
public final class String extends Object implements Serializable, Comparable<String>, CharSequence
String类在这里明确的说明,使用final进行修饰,那么也就是说是不可以被继承的;继承来自java.lang.Object类,那么会有Object中的方法,实现Serializable、Comparable、CharSequence接口,那么会有其中的方法可以使用(具体什么是接口,放在后边的博客中说明)。
-
String 的构造方法
构造方法是用于构造一个对象所使用的,那么针对于String的构造有多少呢?(在JDK11中已经标记过时的方法就不再去看了)
构造方法 | 描述 | 中文翻译 |
---|---|---|
String() | Initializes a newly created String object so that it represents an empty character sequence. | 初始化一个新创建的字符串对象,使其表示一个空字符序列。 |
String(byte[] bytes) | Constructs a new String by decoding the specified array of bytes using the platform's default charset. | 通过使用平台的默认字符集解码指定的字节数组来构造新的字符串。 |
String(byte[] bytes, int offset, int length) | Constructs a new String by decoding the specified subarray of bytes using the platform's default charset. | 通过使用平台的默认字符集解码指定的字节子数组来构造新的字符串。 |
String(byte[] bytes, int offset, int length, String charsetName) | Constructs a new String by decoding the specified subarray of bytes using the specified charset. | 通过使用指定的字符集解码指定的字节子数组来构造新的字符串。 |
String(byte[] bytes, int offset, int length, Charset charset) | Constructs a new String by decoding the specified subarray of bytes using the specified charset. | 通过使用指定的字符集解码指定的字节子数组来构造新的字符串。 |
String(byte[] bytes, String charsetName) | Constructs a new String by decoding the specified array of bytes using the specified charset. | 通过使用指定的字符集解码指定的字节数组来构造新的字符串 |
String(byte[] bytes, Charset charset) | Constructs a new String by decoding the specified array of bytes using the specified charset. | 通过使用指定的字符集解码指定的字节数组来构造新的字符串。 |
String(char[] value) | Allocates a new String so that it represents the sequence of characters currently contained in the character array argument. | 分配一个新字符串,以便它表示当前包含在字符数组参数中的字符序列 |
String(char[] value, int offset, int count) | Allocates a new String that contains characters from a subarray of the character array argument. | 分配一个新字符串,该字符串包含字符数组参数的子数组中的字符 |
String(int[] codePoints, int offset, int count) | Allocates a new String that contains characters from a subarray of the Unicode code point array argument. | 分配一个新字符串,该字符串包含Unicode代码点数组参数的子数组中的字符。 |
String(String original) | Initializes a newly created 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. | 初始化新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本 |
String(StringBuffer buffer) | Allocates a new string that contains the sequence of characters currently contained in the string buffer argument. | 分配一个新字符串,该字符串包含字符串缓冲区参数中当前包含的字符序列。 |
String(StringBuilder builder) | Allocates a new string that contains the sequence of characters currently contained in the string builder argument. | 分配一个包含字符串生成器参数中当前包含的字符序列的新字符串。 |
会发现,构造方法如此之多,应该选择那个呢?实际上,在真正的开发过程中,几乎很少使用String的构造方法,使用方式一般如下: |
String s = "我是一个String 的字符串 " ;
那么,用""引用起来的东西,就表示一个字符串了,当然可以使用上述方法进行使用也是可以的。
- String的不可以变性
String为什么不可以变?什么情况化会发生改变?那么改变的到底是什么?
public class TestString {
public static void main(String[] args) {
/**
* String 类型是一个引用类型,那么s 作为 变量而出现,其实是存放在栈空间中
* 其中的Hello World 作为一个取值而出现 因此一定不是存放在栈空间中的
* 那么 s 中存放的一定是 一个 地址
*/
String s = "Hello World " ;
/**
* System.identityHashCode( s ) : 底层调用的是一个本地的方法,那么可以查看API进行说明。
* Returns the same hash code for the given object as would be returned by the default method hashCode(),
* whether or not the given object's class overrides hashCode(). The hash code for the null reference is zero.
* 以上是这个方法的详细说明,其本质就是返回当前对象的hashCode值
* Integer.toHexString( int i ) : 将一个数值转换成一个16进制的数据
*/
System.out.println( Integer.toHexString( System.identityHashCode( s ) ) ); // 1e643faf
String s2 = "Hello World ";
System.out.println( Integer.toHexString( System.identityHashCode( s2 ))); // 1e643faf
/**
* == 经常用于比较值,如果是基本数据类型那么比较的就是地址
* 如果比较地址两个变量相等,证明存放的是同一块区域
*/
System.out.println( s == s2 );// true
/**
* equals方法继承于java.lang.Object方法,那么底层是使用==进行比较的
* 但是 String 这个类重写了 equals方法
*/
System.out.println( s.equals( s2 )); // true
}
}
说明一下:
在注释中也说明了 如果两个变量 比较地址如果返回true 的话,那么就证明是存放在同一块区域中的,此时那么这个字符串是没有经过修改的,也就是当同一个变量引用同一个字符串的时候,就会发现没有改变。间接说明了是不可以改变的,可以共享的。
那么就有人说了,你看
String s = "hello world " ;
s = "hello Java " ;
这种情况不就改变了嘛,其实不是的,因为都知道,s此时是一个变量,变量变化的是其中的值,而量不会发生改变。那么在这里也就是说s此时改变的就是其中的地址,而hello world 并没有发生改变。如下所示:
public class TestString2 {
public static void main(String[] args) {
String s = "Hello World " ;
System.out.println( Integer.toHexString( System.identityHashCode( s ) ) ); // 1e643faf
String s2 = "Hello World ";
System.out.println( Integer.toHexString( System.identityHashCode( s2 ))); // 6e8dacdf
System.out.println( s == s2 );// true
System.out.println( s.equals( s2 )); // true
/**
* 这里主要是观察地址有没有改变
*/
s = "hello java " ;
System.out.println( Integer.toHexString( System.identityHashCode( s ) ) ); // 6e8dacdf
/**
* 当运行之后,发现 地址发生了改变,那么也就是说其原来的字符串( Hello World )并没有发生改变
* 那么 如果 s 再次引用 Hello world ,地址是否也是相同的呢 ?
*/
s = "Hello World " ;
System.out.println( Integer.toHexString( System.identityHashCode( s ) ) ); // 1e643faf
/**
* 当输出之后,发现地址没有改变,也就是说 所有的字符串 字面值都放在一个区域中,没有改动
*/
}
}
这个例子说明了地址是没有改变的。
-
String 的存储方式
在官方API中说明了
For example:
String str = "abc";
is equivalent to:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
如果直接声明一个字符串字面值直接被声明的话,相当于构建一个字符数组,然后使用构造方法将其转换成字符串,那就可以看一下源码去追一下了。
/**
* 因为在官方文档中说明了,使用字符串字面值的时候
* 相当于创建一个char数组,然后使用构造进行转换
* 所以这里就直接创建一个char数组,然后查看构造方法
*/
public class TestString3 {
public static void main(String[] args) {
char data[] = {'a', 'b', 'c'};
String str = new String(data);
System.out.println( Integer.toHexString( System.identityHashCode( str )) ); //1e643faf
String s = "abc" ;
System.out.println( Integer.toHexString( System.identityHashCode( s )) );//1e643faf
}
}
```
这里有两个地址是不相同的,那么证明了实际上就是这样的,虽然在官方API中说的是相同的,但是地址确实不一样。那么new String 到底干了啥呢?new 负责开辟空间,String()执行构造方法,new+String()的形式就返回了一个地址。如下是构造源码:
public String(char value[]) {
// 调用另一个构造方法:使用传入的char数组,从0 开始,到 最后的一个位置,后边跟了一个null
this(value, 0, value.length, null);
}
那么这里有调用了一个构造:
// 调用的就是这个方法 String(char[] value, int off, int len, Void sig) { // 如果长度是0,那么就直接返回了,表示是一个空串 if (len == 0) { this.value = "".value; this.coder = "".coder; return; } // COMPACT_STRINGS 是一个boolean类型的常量,默认值是false,当然这里没有进行任何赋值操作,所以是false // 如果是false了,那么就不用理会了。 if (COMPACT_STRINGS) { byte[] val = StringUTF16.compress(value, off, len); if(val != null) { this.value = val; this.coder = LATIN1; return; } } // 上边两个if 都没有进去,那么UTF16(1)值给coder (此时coder = 1) this.coder = UTF16; /** * StirngUTF16是一个在Java内部的类 * StirngUTF16.toBytes()是创建一个新的字节数据,将char数组中的数据进行操作放入其中 * 然后将返回的byte数组赋值给value。 * 那么value也就是一个byte数组。(1.8的时候还是一个char数组呢) */ this.value = StringUTF16.toBytes(value, off, len); }
那么其他的构造呢?读者可以自己查阅,其本质就是要给字节数组( byte[] )
- 直接书写字面值与使用构造有什么区别么?
来看一个例子来证明一下:
public class TestString4 { public static void main(String[] args) { String x = "hello,world"; System.out.println("x :"+ Integer.toHexString(System.identityHashCode(x))); // 对于y来说,y指向了堆,堆中的对象又指向了池 String y = new String("hello,world"); System.out.println("y :"+ Integer.toHexString(System.identityHashCode(y)));
System.out.println( x == y ); // false
System.out.println( x.equals( y )); // true
/**
* 那么使用char数组呢 ?是一个道理
*/
char[] array = {'a' , 'b' , 'c' } ;
System.out.println("array :"+ Integer.toHexString(System.identityHashCode(array)));
String s = "abc" ;
System.out.println("s :"+ Integer.toHexString(System.identityHashCode(s)));
String s2 = new String( array ) ;
System.out.println("s2 :"+ Integer.toHexString(System.identityHashCode(s2)));
}
}
new的时候是创建了一个对象,对象是放在堆中的,变量一般放在栈中,而字符串字面值是放在池中的。这个地方叫做字符串池。那么用图来解释的话,就如下所示:
如果是正常的情况的话,是这样的
![](https://oscimg.oschina.net/oscnet/7ece0113321dac16a5462f664798ed773c9.jpg)
如果是带有构造的话,那么就是这样的 :
![](https://oscimg.oschina.net/oscnet/b8cf9305c81021d8d206b94a21ea1a36ed3.jpg)
关于String的方法,下篇博客会说明。