== 1、String是final的,因此不可被继承。 ==
Java 8 中String的值使用char数组存储数据:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { // String是final的,因此不可被继承。
/** The value is used for character storage. */
private final char value[]; // Java 8中,String的值使用char数组存储数据
...
}
Java 9之后,String的值使用byte数组存储数据,同时使用coder来标识编码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { // String是final的,因此不可被继承。
/** The value is used for character storage. */
private final byte[] value; // Java 9 之后,String的值使用byte数组存储数据
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
2、构造函数
有很多,这里看三个构造函数源代码
// 1)、无参构造方法
public String() {
// 初始化一个个新创建的String对象,表示空字符序列,
this.value = "".value; // 由于字符串不可变,所以不需要使用此构造函数
}
// 2)、 字符串参数构造方法
public String(String original) {
// 初始化一个新创建的String对象,表示与参数相同的字符序列,即参数字符串的副本
// 除非需要original的显示副本,否则不需要此构造函数,因为字符串是不可变的
this.value = original.value;
this.hash = original.hash;
}
// 3)、 字符数组构造方法
public String(char value[]) {
// 分配一个新的String,表示字符数组参数中包含当前包含的字符序列
this.value = Arrays.copyOf(value, value.length); // 字符数组的内容被复制,字符数组的后续修改不会影响新创建的字符串
}
3、字符串的常用操作
// 返回字符序列的长度
public int length() { return value.length; }
// 返回字符序列是否为空,当且仅当,字符长度为0时返回true
public boolean isEmpty() { return value.length == 0; }
// 将字符串中的字符复制到目标字符数组中
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { }
// 使用字符集将String编码为字节序列,并存储到新的字节数组中
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { }
public String toString() { }
// 转换为新的字符数组
public char[] toCharArray() {}
//去空格
public String trim() { }
//格式化的字符串
public static String format(String format, Object... args) { }
// 转大写 toUpperCase 转小写toLowerCase
得到指定索引位置的值:
// 返回此字符串指定索引处的char值,索引从0开始,指定索引范围:[0,value.length-1]
public char charAt(int index) { }
// 返回此字符串指定索引处的Unicode code point值,索引从0开始
public int codePointAt(int index) { }
得到指定条件的索引位置:
// 得到下标位置,指定字符首次出现在此字符串中的索引
public int indexOf(int ch) { }
// 得到下标位置,指定字符最后出现在此字符串中的索引
public int lastIndexOf(int ch) { }
比较:
// 如果给定的对象表示与此字符串等效的,则为true
public boolean equals(Object anObject) { }
// 比较按字典顺序比较两个字符串,基于Unicode
public int compareTo(String anotherString) { }
// 判断此字符串是否从指定的前缀开头
public boolean startsWith(String prefix) { }
// 判断此字符串是否从指定的后缀结尾
public boolean endsWith(String suffix) { }
// 匹配,判断此字符串是否匹配给定的正则表达式
public boolean matches(String regex) { }
// 包含,判断是否包含指定的字符序列
public boolean contains(CharSequence s) { }
// 返回此字符串的哈希码
public int hashCode() { }
字符串操作:
// 截取,返回此字符串指定范围的子字符串
public String substring(int beginIndex, int endIndex) { }
// 替换,将此字符串中的所有出现的‘oldchar’替换为‘newchar’
public String replace(char oldChar, char newChar) { }
// 拆分,返回字符串数组
public String[] split(String regex) { }
// 链接
public static String join(CharSequence delimiter, CharSequence... elements) { }
4、String Pool
String Pool保存着所有字符串字面量,这些字面量在编译时期就确定。
可以使用intern()方法在运行过程中将字符串添加到String Pool中
String s1 = new String("aaa"); //会常见两个字符串对象,前提是String Pool中没有“aaa”字符串对象
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
如果直接采用字面量创建,则会自动放入String Pool
String s1 = "aaa";
String s2 = "aaa";
System.out.println(s1 == s2); // true
在Java7之前,字符串常量池被放在运行时常量池中,属于永久代;在Java 7 之后,字符串常量池被移到堆中。
5、重写equals
Object类是类层次结构的根,即所有类的最终祖先,Java中的类都是Object类的直接或间接子类
Object类中的equals()方法:
public boolean equals(Object obj) {
return (this == obj);
}
在定义类时建议重写equals()方法,String中重写的equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) { //初始判断与Object中一样
return true;
}
if (anObject instanceof String) { //判断是否为String类型
String anotherString = (String)anObject;
int n = value.length; //对该类中每个”关键“域,检查参数中的域是否与该对象中对应的域相匹配。
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
注意:从上面的例子,我们可以总结出重写equals()方法的诀窍:
- 检查是否为这个对象的引用,可以使用”==“操作符检查
- 检查是否为同一个类型,可以使用instanceof操作符,也可以使用getClass()进行判断
- 如果是同一个类型,则对该类的属性进行比较
- 并且在覆盖equals时总要覆盖HashCode()。
6、重写hashCode()
java中创建的对象是保存到堆中的,为了提高查找的速度而使用了散列查找。散列查找的基本思想是定义一个键来映射对象所在的内存地址。当需要查找对象时,直接查找键即可。查看散列码:将重写equals方法时使用的成员变量乘以不同的质数然后求和,作为新的哈希码.
String类中重写的hashCode()方法:
public int hashCode() {
int h = hash; //缓存字符串的哈希码
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。
因为如果equals为true而两个对象的hashCode不同,那么在整个存储过程中就发生了悖论。
示例代码:
public class EmployeeSimple implements Cloneable {
private String name;
private int age;
public EmployeeSimple() {
}
public EmployeeSimple(String name, int age) {
this.name = name;
this.age = age;
}
//省略get,set...
@Override
public boolean equals(Object obj) {
// 检查是否为这个对象的引用,可以使用”==“操作符检查
if (this == obj) // 如果两个员工是同一个对象则相同
return true;
// 对于任何非空引用值x,x.equals(null)应该返回false
if (obj == null)// 如果两个员工有一个为null则不同
return false;
//检查是否为同一个类型,可以使用instanceof操作符,也可以使用getClass()进行判断
if (getClass() != obj.getClass())
return false;
EmployeeSimple employee = (EmployeeSimple) obj;
//对该类的属性进行比较
return (getName().equals(employee.getName())) && (getAge() == employee.getAge());
}
// 并且在覆盖equals时总要覆盖HashCode()。
@Override
public int hashCode() {
return 7 * name.hashCode() + 11 * new Integer(age).hashCode();
}
public String toString() {
return "姓名:" + getName() + ",年龄:" + getAge();
}
}
测试:
public static void main(String[] args) {
EmployeeSimple employee1 = new EmployeeSimple("张三", 26);
System.out.println("emp1" + employee1);
EmployeeSimple employee2 = new EmployeeSimple("张三", 26);
System.out.println("emp2" + employee2);
EmployeeSimple employee3 = new EmployeeSimple("李四", 20);
System.out.println("emp3" + employee3);
System.out.println("employee1.equals(employee2):" + (employee1.equals(employee2)));
System.out.println("employee1.equals(employee3):" + (employee1.equals(employee3)));
System.out.println("emp1 HashCode:"+employee1.hashCode());
System.out.println("emp2 HashCode:"+employee2.hashCode());
System.out.println("emp3 HashCode:"+employee3.hashCode());
}
思考:java中的String为什么不可变?
tips:String 是final的,所以它们的值在创建后无法更改,不可变的好处有:
- 线程安全,可以在多个线程中安全地使用
- String Pool的需要:如果一个String 对象已经被创建过了,就会从String Pool中取得应用,只有String是不可变的,才能使用String Pool
- 可以缓存hash值