类的定义
在日常开发中,String可以说是最常用的类之一了,但也是最容易被忽视的类。先来看看String的定义
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char[] value; // jdk 8
private final byte[] value; // jdk 9 使用 byte 减少空间的浪费,
private final byte coder;
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
}
首先,String被final所修饰,同时实现了Serializable,Comparable和CharSequence接口。我们一个个来看:
为撒要用 final 修饰
根据源码,我们可以看到String的值在内部是用一个byte数组value维护的,所以value是一个引用类型,对于引用类型的变量,我们往往都会十分小心,因为鬼知道在什么时候,我们就会掉进一个大坑... 更何况String是一个非常常用的类。所以一个非常重要的原因就是安全性,使得String类需要被final修饰,即不可变。所以对String类加上了final修饰符,防止String被继承,从而重写value。刚才说到value是一个引用类型,所以在value上也加上了final修饰,并且在整个类中,没有对value进行update的操作。所有的update操作都是会新建一个新的string。
为撒实现 Serializable 接口
详情可见🔎「OpenJdk-11 源码-系列」Serializable
Comparable & Comparator
先来看看Comparable接口
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable接口是一个排序接口,若一个类实现了该接口并重写了compareTo方法,就意味着这个类支持了排序。当存在一个实现了Comparable接口的类的集合或数组,那么该集合或数组就可以通过Collections.sort / Arrays.sort进行排序。
- 当返回值为正数,那么就意味着 a > b (
a.compareTo(b)) - 当返回值为零,那么a = b
- 当返回值为负数,那么a < b
再来看看Comparator 接口
public interface Comparator<T> {
//最主要的俩方法
int compare(T o1, T o2);
boolean equals(Object obj);
}
刚才我们说到如果一个类实现了Comparable接口后,那么该类就有了排序的功能。但有的时候我们不想修改这个类,减少对类的侵入。那么我们就可以自定义一个类来实现Comparator接口。
总结一下,实现Comparable接口可以做到内部比较(即相同类之间的比较);而实现了Comparator接口的类,可以使用该类对任意两个类型的对象进行比较,且无侵入性。
属性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final byte[] value;
private final byte coder;
private int hash; // Default to 0
}
-
value
value存储的是String的内容,即当使用String str = "abc";的时候,"abc"是存储在一个byte类型的数组中的。这在后续的构造函数中会讲解到。 -
coder
在JDK9中,
String维护了这样一个新的属性coder,它是一个编码格式的标识,表示LATIN1或者UTF-16,String生成的时候会自动初始化这个值,如果字符串中都是能用LATIN1就能表示的就是0,否则就是UTF-16。那么为什么要加这个
coder属性呢?先说结论,可以对字符串的空间进行压缩。先看源码//jdk8 public int length() { return value.length; } //jdk9 static final boolean COMPACT_STRINGS; static { COMPACT_STRINGS = true; //默认开启压缩 } byte coder() { return COMPACT_STRINGS ? coder : UTF16; } public int length() { return value.length >> coder(); //如果coder() = 1则会右移一位 }所以,如果当我们调用
length方法的时候,如果value是LATIN1编码的话那么就会右移一位,减少一个字节数。 -
hash
hash是String在实例化的时候对hashcode的一个缓存。由于在开发中String会经常拿来比较,比如HashMap中如果key是String类型,每次比较如果都重新计算hashcode的话就会很费时。
构造方法
public String() {
this.value = "".value;
this.coder = "".coder;
}
空参构造方法,会创建一个空的字符串序列。一般不会这么创建。
//常见的几种构造方法
public String(byte[] bytes) {
this(bytes, 0, bytes.length);
}
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBoundsOffCount(offset, length, bytes.length);
StringCoding.Result ret =
StringCoding.decode(charsetName, bytes, offset, length);
this.value = ret.value;
this.coder = ret.coder;
}
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBoundsOffCount(offset, length, bytes.length);
StringCoding.Result ret =
StringCoding.decode(charset, bytes, offset, length);
this.value = ret.value;
this.coder = ret.coder;
}
使用字节数组来创建可以分为指定编码或默认编码(ISO-8859-1)进行编码操作。
常用方法
比较简单,可直接查看API
其它
String 对 + 号的重载
String str = "this is";
String str1 = str + "str"
在底层,Java 对 String 的+的支持使用的是 StringBuilder 以及它的append和toString方法。即:
String str = "str";
String str1 = (new StringBuilder(String.valueOf(str))).append("str").toString();
intern
public native String intern();
可以看到 intern 是被 native修饰,这说明该方法是由底层的C/C++进行实现的。它的作用是 如果常量池中存在当前字符串, 就会直接返回当前字符串。如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。通过双引号声明的字符串会放入到常量池中
String str = "123";
String str1 = new String("123");
String str2 = str1.intern();
s
System.out.println(str == str1); //false
System.out.println(str == str2); // true