Java中的数据比较
在Java中数据类型主要分为两类:基本数据类型和引用数据类型。
1.基本数据类型如何比较?
2.基本数据类型和引用类型如何比较?
3.引用类型间如何比较?
文章目录
1. 基本数据类型间如何比较?
-
基本数据类型又细化为整型、字符型、浮点型和布尔型;
-
基本数据类型间通过关系运算符就可以直接比较,对数值比较。
-
不同的基本数据类型能不能比较?
不是任意之间都能比较。基本数据类型间,除了布尔型不可以与其他类型比较,只能自己和自己比较。整型、字符型、浮点型可以相互比较(可以比较,但是比如字符和浮点比较有什么意义?合法但不合理),不同类型的比较会发生类型提升,数据类型小的会被提升到数据类型大的。字符型采用的是Unicode编码,本质还是整型。
-
特别注意浮点型比较会存在精度问题。浮点型的计算或者类型提升都会出现精度问题。
System.out.println(0.1f == 0.1); // false
详细参考🚪:Java中的浮点数比较 == equals 和 compare
2. 基本数据类型和引用类型(包装类)如何比较?
这里主要针对整型基本数据类型和其包装类的比较。
比较之前,问为什么有基本数据类型和包装类?
解释传送门🚪 – 【JavaSE】基本数据类型和其包装类
Integer a = 100; // 自动装箱
Long b = 100L;// 自动装箱
System.out.println(a == 100);//1. true
System.out.println(b == 100);//2. true
System.out.println(a.equals(100));//3. true
System.out.println(a.compareTo(100));//4. true
System.out.println(a.equals(b));//5. false
基本数据类型与包装类直接==比较时,包装类会先拆箱,然后与基本数据类型比较,比较规则和基本数据类型之间的比较规则一样。(解释1、2)
包装类都已经重写了equals和compareTo方法,使用这样的方法直接和基本数据类型比较时,先将基本类型装箱然后传参,在equals方法内会先进行类型判断进而拆箱比较,compareTo方法也是的相同类型然后拿到包装类的值在进行数值比较。(解释3、4)
// Integer equals方法和compareTo方法源码
public boolean equals(Object obj) {
if (obj instanceof Integer) {// 类型检查
return value == ((Integer)obj).intValue();
}
return false;
}
public int compareTo(Integer anotherInteger) {// 类型检查
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
2.1 小结
基本数据类型和包装类比较直接使用运算符比较即可,因为包装类会自动拆箱,最终还是基本数据类型间的比较。
注意到上述基本数据类型和包装类的代码第七行,是包装类和包装类的比较,结果为false,那通过上面equals的代码可以看到,比较前会进行类型检查(检查是不是Integer或者其子类),b是Long类型,所以为false。
那包装类能否使用运算符比较?也即引用类型能否使用运算符比较?
3. 引用类型间如何比较?
3.1 包装类间比较(关系运算符)
public static void main(String[] args) {
Integer a = 6;
Integer b = 6;
Integer c = new Integer(6);
Integer d = new Integer(6);
Integer f = 128;
Integer g = 128;
Long l = 6L;
System.out.println(a == b); //1. true
System.out.println(a.equals(b));//2. true
System.out.println(a == c);//3. true
System.out.println(c == d);//4. false
System.out.println(c.equals(d));//5. true
System.out.println(a == l);//6. 编译器红波浪警告--cannot be applied to 'java.lang.Integer', 'java.lang.Long'
System.out.println(a.equals(l));//7. false
System.out.println(f == g);//8. false
System.out.println(a > b);// 9. false
System.out.println(f != g);// 10. true
}
- 首先引用类型间使用关系运算符(== != < > <= >=)进行比较,比较的是地址!!!
- Integer类内有一个对象缓存区,缓存范围为-128到127,当该范围内的数据直接赋值装箱时,会直接从对象缓冲区引用对象。 范围之外的数据装箱时会直接在堆区实例化Integer对象,就是new Integer(num)。(解释行1,3,4,8,9,10)
- 引用类型使用==比较时两边的类型要一样。(解释行6)
- equals方法内会先进行类型判断进而拆箱比较,根据源码if不是一个类型则直接返回false。(解释5,7)
- 基本数据类型包装类中的Byte、Short、Integer、Long的高频缓存范围为-128到127;Character的高频缓存为-128到127;Float、Double没有高频缓存区。
3.2 包装类间比较(算数运算符)
public static void main(String[] args) {
Integer a = 6;
Integer b = 6;
Integer c = new Integer(6);
Integer d = new Integer(6);
Integer f = 128;
Integer g = 128;
System.out.println(a + b);// 12
System.out.println(f - g);// 0
System.out.println(c * d);// 36
}
3.1.1 小结
1、基本数据类型使用关系运算符(== != < > <= >=)比较的是数值;
2、包装类型间使用关系运算符(== != < > <= >=)比较的是地址;
3、包装类型间使用基本算数运算符运算时会先拆箱然后运算;
4、包装类之间比较最好使用equals和compareTo方法,避免因为缓冲区而出现问题,且用着两个方法时要注意类型问题。
3.1.2 实践
习题中对这些细节体会更深。
3.3 String比较
String类型在堆中存储时,其底层设置了一个字符串常量池,本质是一个哈希表。
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
}
- 直接使用字符串常量进行赋值,这会先将字符串常量哈希到对应位置,对于相同的字符串常量会直接从字符串常量池中引用,所以s1和s2的地址是相同的。
- 直接实例化new String(num)直接在堆区重新开辟一块空间,虽然新的空间内部和哈希表有相同的指向,但是新空间的地址还是新地址所以s1和s3,s3和s4不同。
- 这个和包装类的缓存区类似,在缓存区范围内就会直接引用,这样的操作都是为了节省空间。
🔎以上只是字符串常量的比较,你知道字符串拼接然后比较是怎么样的吗?
开启传送门🚪String使用 “==“ 比较!?_hello Jimmy的博客-CSDN博客
3.4 引用类型的三个比较方法
- 像包装类这种是java提供的实现类以及String类等,内部都重写了equals和compareTo方法,那如果是自定义的类如何比较?既然java内部提供的类可以重写这些方法,那自定义类是不是也可以?
- Java里面除了Object类,所有的类都是默认会继承Object父类。Object类中就提供了equals方法。既然所有的类都继承了Object类,为什么要重写equals?
3.4.1 equals方法
- 查看Object类equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
- Object类中的equals方法最终还是==比较,也即比较地址。这不满足其他类的需求所以被重写。
- equals方法只能判断相等不相等!
Integer的equals方法:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
自定义引用类型equals方法:
- 继承equals方法
class Student {
int id;
int name;
public Student(int id, int name) {
this.id = id;
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student(1,"apple");
Student student2 = new Student(1,"apple");
System.out.println(student1.equals(student2)); // false
}
}
因为继承Object类的equals方法所以还是比较地址。
- 重写equals方法
class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student(1,"apple");
Student student2 = new Student(1,"apple");
System.out.println(student1.equals(student2)); // true
}
}
📌注意重写equals方法一般都要重写hashCode方法!!!
参考🚪 – java中为什么重写equals后需要重写hashCode_equals重写后需要做什么
3.4.2 compareTo方法
泛型的比较接口类Comparable提供了compareTo抽象方法。
使用流程:实现Comparable接口重写compareTo方法
class Student implements Comparable<Student>{
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);com
}
@Override
public int compareTo(Student o) {
return this.id - o.id;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student(1,"apple");
Student student2 = new Student(1,"apple");
System.out.println(student1.compareTo(student2)); // 0
}
}
📌注意事项:
implements Comparable<Student> – 括号内指明比较的对象。
public int compareTo(Student o) – 谁调用compareTo方法谁是this。
3.4.3 compare方法
泛型的比较接口类Comparator提供了compare抽象方法。
使用流程:子类继承Comparator重写compare方法。
🎯sample 1:
class stuIdComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.id - o2.id;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student(1,"apple");
Student student2 = new Student(1,"apple");
System.out.println(comparator.compare(student1, student2)); // 0
}
}
🎯sample 2:
class stuIdComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o2.id - o1.id;// !!!
}
}
class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student(1, "apple");
Student student2 = new Student(2, "apple");
Student[] students = new Student[]{student1,student2};
stuIdComparator comparator = new stuIdComparator();
Arrays.sort(students,comparator);// 传入比较器
System.out.println(Arrays.toString(students));
}
}
📢stdout:
[Student{id=2, name='apple'}, Student{id=1, name='apple'}]
3.4.4 compareTo() 和 compare()
何为侵入性? – 指代码产生对框架的依赖,离不开框架了。这里框架等价于这两个方法。
compareTo()对类的侵入性强。(翻译翻译)类一旦实现Comparable 接口必须重写该方法,而且如果改变比较的属性则要改变方法,那么调用改方法的对象可能也要做出相应的调整,所以说该方法对类的比较影响较大。
compare()对算法代码实现侵入性强。 该比较器可以根据需求去比较类中的属性,可以定义多个比较器,但是代码使用该方法则需实例化一个比较器。
一句话前者直接作用于类中,后者作用于类外。