Java中引用类型变量(对象)的比较

文章目录

  • 1. == 和 equals()方法
    • 比较非自定义类
    • 比较自定义类
  • 2. Comparable接口
    • Comparable接口的使用
    • 使用Comparable比较自定义类的优点和缺点
  • 3. Comparator接口
    • Comparator接口的使用
    • 使用Comparator比较自定义类的优点

1. == 和 equals()方法

== 比较的是两个对象的地址是否相同(即两个引用是否指向同一个对象)。
equals()方法比较的是对象中的内容是否相同。(equals()是Object类里的一个方法,且所有的类都默认继承Object类)

比较非自定义类

以String类举例:

 	public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = new String("hello");
        System.out.println(s1 == s2);  
        System.out.println(s1.equals(s2));  
    }

程序运行结果:
false;
true;

原因如下图:(s1与s2引用指向的对象不一样,但value的内容相同)
Java中引用类型变量(对象)的比较_第1张图片


通过上面代码的运行结果,我们对== 和 equals()方法的比较方式已经有了一定的理解。那么以下代码,程序的运行结果又是什么呢?(大家可以先暂停思考一下)

	public static void main(String[] args) {
        String s1 = new String("hello");
        String s2 = "hello";
        String s3 = "hello";
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
        System.out.println(s2 == s3);
        System.out.println(s2.equals(s3));
    }

程序运行结果:
false
true
true
true

我相信,代码运行的结果可能在一部分人的意料之外,那么原因是什么呢?
对于第2和第4个结果,大家应该没有什么疑问,毕竟每个String对象中的内容都为“hello”,程序打印的结果毫无疑问是true。

第3个比较的结果之所以为true,是因为在堆区中存在一个String常量池,所有非new出来的String对象,都会被存到常量池中,当使用另一个内容相同且非new出来的String对象时,其引用会指向常量池里的同一个对象,即常量池只保存一份值相同的String对象
根据以上叙述,我们可以知道,s1所指向对象是通过new实例化的对象,在内存中与s2、s3的存储位置是不同的,因此它们的地址也一定不同。

具体的内存分布情况如下:
Java中引用类型变量(对象)的比较_第2张图片

比较自定义类

假设有一个如下的自定义类和对应类的两个实例化对象。当它们使用 ==和equals()方法进行比较,那么结果会是什么样呢?

public class Demo {

    public static void main(String[] args) {
        Student s1 = new Student("lisi",12);
        Student s2 = new Student("lisi",12);
        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
    }

}

class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void func() {}
}

程序运行结果:
false
false

这里代码运行的结果其实也不难理解,==比较的是引用所指对象的地址,s1和s2是两个不同的对象,因此地址肯定不一样;而equals()方法比较的是对象里的内容,因为Student类存在多个属性,所以他并不会直接去类中的属性,而是依然比较两个对象的地址。
由此我们不难推测,String类其实也存在多个属性,String对象之所以能够使用equals()方法进行比较,是因为String类已经重写了equals()方法。

因此,我们在使用equals()方法比较自定义类时,一定要重写该方法,对类中的成员变量进行依次比较。
重写equals()方法代码如下:

	@Override
    public boolean equals(Object obj) {
        // 检查是否是同一个对象
        if(this == obj) return true;
        // 检查比较的对象是否为 null
        if(obj == null) return false;
        // 检查比较对象 是否为同类
        if(this.getClass() != obj.getClass()) return false;

        // 比较两个类中的所有属性
        Student student = (Student) obj;
        if(this.age != student.age) return false;
        if(this.name == null && student.name == null) {
            return true;
        }else if(this.name == null || student.name == null) {
            return false;
        } else {
            return this.name.equals(student.name);
        }
    }

重写equals()后程序的运行结果:
false
true

2. Comparable接口

Comparable接口的使用

与equals()方法不同的是,equals()比较结果返回的参数是boolean类型,这个方法只能知道两个对象的内容是否相同。
而实现Comparable接口,重写compareTo()方法,比较结果返回的参数是int类型,它可以通过比较对象中的具体某个属性,而比较出两个对象的大小。

Comparable实现的模版如下:

class Student implments Comparable<E> {
	//属性
	//方法
	
// 返回值:
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
	int compareTo(E o) {
	
	}
}

Student对象比较具体的实现代码如下:

public class Demo {

    public static void main(String[] args) {
        Student s1 = new Student("zhangsan",12);
        Student s2 = new Student("lisi",18);
        System.out.println(s1.compareTo(s2));
    }

}

class Student implements Comparable<Student>{
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }

    public void func() {}
}

实现Comrable接口,重写compareTo()方法后程序的运行结果:
-6

若要通过比较name区分Student对象的大小,需要比较字符串String中每个字符的大小,大家可以自行实现。


使用Comparable比较自定义类的优点和缺点

优点:
1.对于自定义类对象的数组,若有对元素排序的需求,可以直接调用Arrays.sort()方法对数组进行排序。
2.若需要将自定义对象建成一个堆,只需在类中实现Comrable接口,重写compareTo()方法即可。

缺点:
一旦确定了比较的属性,后续如果比较需求有变动,就需要重新修改类中属性比较的代码,而一旦该类已被广泛使用,随意地修改类中的compraeTo()方法可能会使别人错误地使用此方法,从而引起某些代码上的逻辑错误。

3. Comparator接口

Comparator接口的使用

使用Comparator接口对自定义对象进行比较,需要我们自定义一个比较器类,实现Comparator接口,重写compare()方法,对需要类中的属性进行比较,即可得到两个对象的大小关系。(可根据需要自定义多个不同的比较器类,对类中的不同属性进行比较)

比较器模版如下:

class cmp implements Comparator<T>{
// 返回值:
// < 0: 表示 o1 指向的对象小于 o2 指向的对象
// == 0: 表示 o1 指向的对象等于 o2 指向的对象
// > 0: 表示 o1 指向的对象等于 o2 指向的对象
int compare(T o1, T o2) {
	// 方法体
}

Student对象比较具体的实现代码如下:

public class Demo {

    public static void main(String[] args) {
        Cmp_age cmp = new Cmp_age();
        Student s1 = new Student("zhangsan",15);
        Student s2 = new Student("lisi",18);

        System.out.println(cmp.compare(s1, s2));
    }

}

class Cmp_age implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

代码运行结果:
-3

使用Comparator比较自定义类的优点

1.比较的灵活性较强,可根据需要构造对应的比较器,不会影响自定义类。
2.若需要将自定义对象建成一个堆,只需实例化PriorityQueue类时,将自定义的比较器类作为参数传入即可。

你可能感兴趣的:(java,笔记)