String
1、不变性
不变性指的是值一旦被初始化,就不能再被改变,如果值被修改了,就会生成新的类,内存的物理地址会被改变。
String之所以具有不变性的原因:
- String类被final修饰,所有String类不能被继承,也就是说任何对String的操作方法都不会被继承覆写;
- String中保存数据的是一个char类型的数组,该数组也被final修饰了,也就是说一旦给String变量赋值了,内存地址是绝对无法再被修改的,而且该数组被private修饰,外部无法访问到该数组变量,只能通过String类提供的方法获取值
如果我们自己的类也想是不可变的,可以模仿String,将类用final修饰,然后将类里面的变量用private和final修饰,外部只能通过我们提供的方法访问类变量
因为String具有不变性,所以String的大部分方法都会返回一个新的String
2、字符串乱码
字符串乱码的原因是不同的操作系统,默认的字符编码是不一样的,从而导致在本地运行正常,部署到服务器上出现乱码,解决办法是在需要用到字符编码的地方,统一指定使用UTF-8,例如可以使用String的getBytes和new String指定字符编码
3、相等判断
可以使用String的equals方法和equalsInnoreCase方法判断两个字符串是否相等,equalsIgnoreCase方法会忽略大小写
equals方法比较两个字符串是否相等的步骤:
- 用
==
比较两个字符串的内存地址是否相同,如果相同返回true - 用instanceof判断参数是否是String类型,如果不是,返回false
- 将参数强转成String类型,判断两个字符串的长度是否相同,如果不相同,返回false
- 将字符串转成字符数组,用while循环遍历数组,判断两个数组相同下标的字符是否相同,如果有一个不相同,返回false
- 返回true
源码如下:
public boolean equals(Object anObject) {
// 判断是否是同一个对象
if (this == anObject) {
return true;
}
// 检查参数是否是String类型
if (anObject instanceof 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;
}
从String的equals的源码可以看出,逻辑非常清晰,完全是根据String的底层结构来编写的,我们实现自定义类的equals方法时也可以参考。
equalsInnoreCase方法忽略大小写比较字符是否相同,原理跟equals差不多,只不过是在循环比较每个字符时调用了toUpperCase方法,将字符都转成大写再进行比较
4、equals与==
的区别
equals方法和==
都是判断两个对象是否相等,区别是equals是使用自定义的逻辑判断两个对象是否相等,而==
只是简单的判断两个对象的内存地址是否相等,如果没有重写equals方法,equals方法也是判断两个对象的内存地址是否相同,此时跟==
是一样的,String重写了equals方法
public static void main(String[] args) {
// 案例一
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s2); // true
// 打印变量的内存地址
System.out.println("s1: " + System.identityHashCode(s1) + " s2: " + System.identityHashCode(s2)); // s1: 1347137144 s2: 1347137144
// 案例二
s1 = new String("123");
s2 = new String("123");
System.out.println(s1 == s2); // false
// 打印变量的内存地址
System.out.println("s1: " + System.identityHashCode(s1) + " s2: " + System.identityHashCode(s2)); // s1: 997608398 s2: 1973336893
}
案例一之所以是true,是因为s1和s2的内存地址是相同的,原因是String对象被放进常量池里面了,所以s1和s2都指向了常量池里面的同一个对象
案例二之所以是false,是因为每new一个对象,都会重新创建对象
5、equals跟hashCode的关系
hashCode方法的作用是为了减少equals方法的调用次数,从而提高效率。
hashCode方法会生成类的哈希码,如果两个对象相同,那么这两个对象的哈希码一定相同,如果两个对象的哈希码相同,这两个对象不一定相同,根据这个原则,在比较两个对象是否相同时,先调用hashCode方法检查两个对象的哈希码是否相同,如果不相同那这两个对象肯定不同,如果哈希码相同,再调用equals方法判断两个对象是否真的相同(hashCode方法的开销和效率比equals方法小得多)。
但是,并不是所有的类都需要同时实现equals方法和hashCode方法的,只有在需要创建"类的散列表"中时hashCode方法才会跟equals方法有关系,在其他情况下,hashCode方法和equals方法是没有关系的。简单的说就是当我们自定义的对象会被放到HashSet、HashTable、HashMap等这些本质是散列表的结构中时,才会使用到HashCode方法,当把对象加进去时,会先调用HashCode方法检查对象是否存在,这种情况下,如果只重写了equals方法,equals是不会生效的,因为hashCode不相同(hashCode默认是内存地址),会直接认为是两个对象,而不会继续调用equals方法。所以建议重写了equals方法也同时重写hashCode方法
6、replace与replaceAll方法的区别
replace方法和replaceAll方法都可以实现string替换指定字符,区别是replace方法参数是字符串,而replaceAll参数可以是正则表达式
public static void main(String[] args) {
String s1 = "123";
String s2 = "123";
System.out.println("replace: " + s1.replace("1", "0")); // 023
System.out.println("replaceAll: " + s1.replaceAll("\\d", "0")); // 000
}
可以用replace和replaceAll方法实现删除字符串中指定字符的功能。
Long
1、Long缓存机制
Long缓存了从-128到127内所有的Long值,如果是这个范围内的Long值,就不会初始化,而是从缓存中拿。
public static void main(String[] args) {
Long l1 = 12L;
Long l2 = 12L;
System.out.println("l1 == l2 : " + (l1 == l2) + " l1.equals(l2): " + l1.equals(l2));
l1 = new Long(12);
l2 = new Long(12);
System.out.println("l1 == l2 : " + (l1 == l2) + " l1.equals(l2): " + l1.equals(l2));
l1 = 128L;
l2 = 128L;
System.out.println("l1 == l2 : " + (l1 == l2) + " l1.equals(l2): " + l1.equals(l2));
}
/**
* 运行结果:
* l1 == l2 : true l1.equals(l2): true
* l1 == l2 : false l1.equals(l2): true
* l1 == l2 : false l1.equals(l2): true
*/
相关源码:
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
2、valueOf与parseLong的区别
valueOf方法可以将数字字符串或数字转成Long类型,而parseLong方法只能将字符串转成Long类型,valueOf方法如果参数是数字,并且范围是在-128到127之间,会从Long缓存中拿值,从而减少资源开销;如果参数是字符串,valueOf底层也是调用parseLong方法。
// valueOf方法参数是long
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
// valueOf方法参数是string
public static Long valueOf(String s) throws NumberFormatException {
return Long.valueOf(parseLong(s, 10));
}