技术有限,才疏学浅,如有表述不正确或有不足,欢迎吐槽、指导和交流。
————————————————————————————————————
Java中 == 和 equals()的使用非常频繁, 以及String类的compareTo()和compareToIgnoreCase()的使用也很多,这里根据源代码分析一下各自的原理和区别。
1、== 运算符
== 是一个比较运算符。可用于基本数据类型或引用数据类型。
基本数据类型直接用值比较是否相同;
举个栗子:
int a = 5;
int b = 5;
System.out.print(a == b);//结果输出true
引用数据类型,比较引用的内存地址值,引用的内存地址相同返回true,不同返回false。
例子1:
User a = new User("zhangsan"); //new一个对象,声明变量名a保存该对象的内存地址
User b = a; //声明另一个变量名b,将a保存的内存地址赋值给b
System.out.print(a == b); //二者保存的内存地址相同,都指向同一个对象,输出true
例子2:
User a = new User("zhangsan");//new一个对象,声明变量名a保存该对象的内存地址
User b = new User("zhangsan");//再new一个对象,声明变量名b保存该对象的内存地址
System.out.print(a == b);//二者指向的对象名字都叫"zhangsan",但内存地址不同,输出false
不过对于String类型,情况有点特殊:
例1:
String a = new String("5");
String b = a;
System.out.print(a == b);//输出true
例2:
String a = new String("5");
String b = new String("5");
System.out.print(a == b);//输出false
例3:
String a = "5";
String b = "5";
System.out.print(a == b);//输出true
例1,例2还好理解,例3和例2代码看起来没啥区别,结果却完全不同。
这是因为:例2的声明方式
是在堆中创建了两个String类型的对象,a,b保存的是指向不同对象的内存地址
而例3的声明方式
"5"实际上是保存在字符串常量池(StringConstantPool)中,每次声明都是先去常量池中看有没有这个常量,无则创建并返回引用地址,有则直接返回引用地址。a,b此时保存的都是指向"5"这个常量的地址
2,equals()方法
equals()是顶级根类Object中的方法
可见equals()仅仅用于引用数据类型的比较,默认的equals()方法源代码:
public boolean equals(Object obj) {
return (this == obj);
}
直接调用 == 运算符,比较两者的内存地址。
而String类型下的equals()方法是对默认equals()的重写,源代码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;//String的本质是char数组,value就是这个数组
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//比较两者的每一个char元素
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String类型的equals()比较步骤:
1,先比较两者内存地址,即默认equals()的比较(比较内存地址)
2,内存地址不同的时候,判断是否String类型对象作为比较依据(比较是否String对象)
3,以上1 false、2 true的情况下,比较两者长度是否相同(比较长度)
4,3 true的情况下,比较两者的本质——char数组相同索引下的各个元素是否完全相同(比较元素组成)
3、compareTo()方法
compareTo()是String类的比较方法,返回的不再是boolean值,而是int值。
源代码如下:
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
compareTo()的比较步骤:
1, 以长度最小的对象为基准,比较两者索引相同的每一个char元素,有不同,返回两个不同元素的ASCII编码(貌似仅限英文,其他文字是Unicode编码)之差。
例如"a".compareTo(“A”), 就是a的元素编码 - A的元素编码,返回值是32,反过来返回值是 -32.
2,如果可比较的元素编码完全相同,则比较两者长度,返回长度差。
例如"string".compareTo(“stringanother”),就是6 - 13,返回 -7;
“string”.compareTo(“string”),就是6 - 6,返回0
4、compareToIgnoreCase()
字面意思就是忽略(ignore)大小写(case)的比较(compare),不过实现方法不是简单的重写compareTo(),源代码如下:
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
这里是调用了CASE_INSENSITIVE_ORDER常量的compare方法,
看下CASE_INSENSITIVE_ORDER对应的源代码:
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
原来这是String的内部私有静态类CaseInsensitiveComparator(大小写不敏感比较器)的常量对象,对应的方法是重写的Comparator(比较器)的compare()方法。
和compareTo的比较步骤很相似:
1,以长度最小的对象为基准,比较两者索引相同的每一个char元素,如果编码不同,转化为大写继续比较,如果还是不同转化为小写再次比较,如果还是不同,返回转化为小写后的编码差值。
2,如果可比较的元素原始/大写化后/小写化后的编码完全相同,则比较两者长度,返回长度差。