【JavaSE】Java中的数据比较

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 实践

习题中对这些细节体会更深。

  1. 栈的压入、弹出序列
  2. 155. 最小栈 - 力扣(LeetCode)
  3. 692. 前K个高频单词

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
}

String常量池

  • 直接使用字符串常量进行赋值,这会先将字符串常量哈希到对应位置,对于相同的字符串常量会直接从字符串常量池中引用,所以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方法:

  1. 继承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方法所以还是比较地址。

  1. 重写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()对算法代码实现侵入性强。 该比较器可以根据需求去比较类中的属性,可以定义多个比较器,但是代码使用该方法则需实例化一个比较器。

一句话前者直接作用于类中,后者作用于类外。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值