当前位置: 首页 > news >正文

Java对象比较与克隆:Comparable、Comparator接口与深拷贝实现

接口使用实例以及Object类中的equals,hashcode方法

    • 1.Comparable 接口实现
    • 2.Comparator 接口实现
    • 3.Clonable 接口和深拷贝
    • 4.对象比较equals方法
    • 5.hashcode方法

1.Comparable 接口实现

实现该接口时需要加上一个泛型(类比 C++的模板)

Class Student implements Comparable<Student>,我们比较什么类型就填什么类型在<>

我们了解了 Comparable 接口之后,会知道里面存在一个方法 compareTo()

因此 compareTo()需要我们去重写,这其实就是一个比较规则,需要怎么比较类型可以由我们来完成,我们举出一个例子如下

class Student implements Comparable<Student>{public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Student o) {//通过年龄比较return this.age - o.age;//通过名字比较//return this.name.compareTo(o.name);}
}public class Test {public static void main(String[] args) {Student std1 = new Student("小李",20);Student std2 = new Student("小刘",19);System.out.println(std1.compareTo(std2));}
}//运行结果为 1

有人会好奇,为什么用名字比较会这么写 return this.name.compareTo(o.name);

可以发现,String 类型中也有 compareTo()方法,因此我们直接调用即可

当我们将用到数组对对象进行排序时,中间的对比过程正是运用了类似这个步骤

class Student /*implements Comparable<Student>*/{public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString(){return "Student {name=" + name + ", age=" + age + "}";}/*@Overridepublic int compareTo(Student o) {return this.age - o.age;//return this.name.compareTo(o.name);}*/
}public class Test {public static void main(String[] args) {Student std[] = new Student[3];std[0] = new Student("小李",20);std[1] = new Student("小刘",19);std[2] = new Student("小新",18);Arrays.sort(std);System.out.println(Arrays.toString(std));}
}

我们继续使用上面那个例子,将 compareTo()方法以及接口屏蔽 会发现运行到Arrays.sort(std);这里的时候发生异常

ClassCastException报错信息中 为类型转换异常

class demo3.Student cannot be cast to class java.lang.Comparable也就是说 Student 不能被转换为 Comparable 型,

图片中的意思是:

这是报错信息中的一个方法,Object[] a 是我们传 Student std[] 的形参

这是原本正确运行需要进行的,但是 我们现在连 Comparable 接口都还没有实现,怎么能强转为 Comparable 类型呢,更不用说调用 compareTo()方法了

因此当我们放开 Comparable 实现和 compareTo()方法重写,那么 Arrays.sort()就会按照我们是按名字还是按年龄写的 compareTo()方法去排序。实际上也就是把比较规则放出来,别人才能拿来比较

那么我们得到的结论就是:

只要是自定义的类型,涉及到了大小比较,那就得一定实现 Comparable 接口

至于 Arrays.sort()内部是怎样实现的,我给出一个大概的模拟实现,实际内部更复杂

									类似冒泡排序的排序方法
public static void mySort(Comparable[] comparables){for (int i = 0; i < comparables.length-1; i++) {for (int j = 0; j < comparables.length-1-i; j++) {if(comparables[j].compareTo(comparables[j+1]) > 0) {Comparable tmp = comparables[j];comparables[j] = comparables[j+1];comparables[j+1] = tmp;}}}
}

为什么要用 Comparable[] comparables 作为形参呢,因为就是我们前面说的向上转型一样转为 Comparable 类型,我们就可以直接使用这个接口中的方法。

再整体看一下这段代码

class Student implements Comparable<Student>{public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString(){return "Student {name=" + name + ", age=" + age + "}";}@Overridepublic int compareTo(Student o) {//方法二:将this和o调换位置也可以变为降序return this.age - o.age;//return this.name.compareTo(o.name);}
}public class Test1 {public static void mySort(Comparable[] comparables){for (int i = 0; i < comparables.length-1; i++) {for (int j = 0; j < comparables.length-1-i; j++) {//方法一:这里大于零做的是一个升序的排序,改为小于零就是降序了if(comparables[j].compareTo(comparables[j+1]) > 0) {//swap(comparables, j, j+1);Comparable tmp = comparables[j];comparables[j] = comparables[j+1];comparables[j+1] = tmp;}}}}// public static <T> void swap(T[] arr, int i, int j) {//     T tmp = arr[i];//     arr[i] = arr[j];//     arr[j] = tmp;// }public static void main(String[] args) {Student std[] = new Student[3];std[0] = new Student("小李",20);std[1] = new Student("小刘",19);std[2] = new Student("小新",18);mySort(std);System.out.println(Arrays.toString(std));//System.out.println(std1.compareTo(std2));}
}

但是这个接口的方法存在一定的局限性,就是我有时候想用年龄去排可以,但我有时候又想用名字去排,但这个方法不能共存,只能一个 compareTo()方法(比较器/也就是比较规则)

补充建议

其实像上面提到的用年龄比较

    @Overridepublic int compareTo(Student o) {//通过年龄比较return this.age - o.age;}

我们实际上还可以用它类型自带的 compare()方法,下面我们举一个例子

  • compareTo()score 从高到低排序
  • 如果分数相同,则按 年龄从小到大排序
    @Overridepublic int compareTo(Student o) {int cmp = Double.compare(o.score,this.score);if(cmp == 0){cmp = Integer.compare(this.age,o.age);}return cmp;}

这里我们就用到了整型自带的Integer.compare()方法和 double 型自带的Double.compare()方法;为什么建议用类型自带的方法呢而不用以下这种

@Override
public int compareTo(Student o) {if (this.score < o.score) {return 1;              // 分数低的排在后面} else if (this.score > o.score) {return -1;             // 分数高的排在前面} else {return o.age - this.age; // 分数相同时,年龄大的排前}
}

问题:compareTo 的返回值逻辑不够直观

🔹 那样写更安全(避免浮点数误差问题)
🔹 逻辑清晰,可读性高
🔹 不会因为 == 比较浮点数出问题

2.Comparator 接口实现

我们可以创建一个新的类 NameComparator

import java.util.Comparator;public class NameComparator implements Comparator<Student>{@Overridepublic int compare(Student o1,Student o2){return o1.name.compareTo(o2.name);}
}

以及另外一个新的类 AgeComparator

import java.util.Comparator;public class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {//按照降序排return o1.age - o2.age;}
}

这个方法不需要依赖 Student类 去实现任何接口

AgeComparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(std[0],std[1]));

直接实例化调用,如果需要升序降序我们可以再去分类写几个升序降序的类即可;即我们可以同时存在多个类似地比较方法,只需要放在不同类去实现接口重写 compare()方法就行!!!

3.Clonable 接口和深拷贝

当我们先了解到下面要讲的 Object 类之后可以再回头看看

Object 类中有一个 clone 方法,这个方法的作用就是字面意思,克隆一个对象

举一个例子:

public class Person implements Cloneable{public int age;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}
}

使用步骤:

  1. 创建 Person 类,然后实例化一个 person1,接着再克隆给对象 person2

2.重写 Object 父类的 clone() 方法(因为该方法在 Object 中是 protected 访问权) [Alt+Ins 键 选择重写克隆 clone()方法即可]

4.注意返回值是父类 Object,所以我们需要向下转型为 Person

5.在 Person类 中实现 Cloneable 接口

6.抛去异常处理public static void main(String[] args) throws CloneNotSupportedException

补充一下,Cloneable 是空接口,空接口也叫标记接口,表示当前类可以被克隆

public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person();person1.age = 19;Person person2 = (Person)person1.clone();System.out.println(person2.toString());}

我们再拓展一下,新增一个 Money 类

class Money{public double money = 19.9;
}public class Person implements Cloneable{public int age;public String name;public Person(int age,String name){this.age=age;this.name=name;}Money m = new Money();@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Person{" +"age=" + age +", name='" + name + '\'' +", money=" + m.money +'}';}
}

此时在栈和堆中是这样的,引用同一个 m 对象,我们因此称之为浅拷贝

如果 m 对象也被拷贝了,那就是深拷贝

所以我们就进行如下操作变为深拷贝

  1. 先让 Money 能够被克隆
class Money implements Cloneable{public double money = 19.9;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
  1. 然后再修改一下克隆方法
protected Object clone() throws CloneNotSupportedException {Person tmp = (Person)super.clone();tmp.m = (Money)m.clone();return tmp;
}

我们再进行测试一下即可

public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person(19,"小李");person1.age = 19;Person person2 = (Person)person1.clone();System.out.println("修改前:"+person1.toString());System.out.println("修改前:"+person2.toString());person2.m.money = 100;System.out.println("修改后:"+person1.toString());System.out.println("修改后:"+person2.toString());}

返回的结果:

修改前:Person{age=19, name=‘小李’, money=19.9}

修改前:Person{age=19, name=‘小李’, money=19.9}

修改后:Person{age=19, name=‘小李’, money=19.9}

修改后:Person{age=19, name=‘小李’, money=100.0}

此时的 Person2 的 m 对象是另外一个独立的对象,与 Person 引用的 m 不是同一个

4.对象比较equals方法

在Java中,==进行比较时:

a.如果==左右两侧是基本类型变量,比较的是变量中值是否相同

b.如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同

c.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:

// Object类中的equals方法
public boolean equals(Object obj) {return (this == obj); // 使用引用中的地址直接来进行比较
}

我们自己写一个比较引用类型中的基本类型比较(eg:Person类中的name和age)

public boolean equals(Object obj) {if (obj == null) {return false ;}if(this == obj) {return true ;}
// 不是Person类对象if (!(obj instanceof Person)) {return false ;}Person person = (Person) obj ; // 向下转型,比较属性值return this.name.equals(person.name) && this.age==person.age ;
}

结论:比较对象中内容是否相等的时候,一定要重写equals方法。

5.hashcode方法

我们回忆一下Object中的toString方法

// Object类中的toString()方法实现:
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

我们看到了hashCode()这个方法,他帮我算了一个具体的对象位置,这里面涉及数据结构,但是我们还没学数据结构,没法讲述,所以我们只能说它是个内存地址。然后调用Integer.toHexString()方法,将这个地址以16进制输出。

hashcode方法源码

public native int hashCode();

该方法是一个native方法,底层是由C/C++编写,无法观察到

我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,我们可以来看示例代码:

class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}
}
public class TestDemo4 {public static void main(String[] args) {Person per1 = new Person("Sirens", 20) ;Person per2 = new Person("Sirens", 20) ;System.out.println(per1.hashCode());System.out.println(per2.hashCode());}
}
//执行结果
460141958
1163157884

注意事项: 两个对象的hash值不一样。

像重写equals方法一样,我们也可以重写hashcode()方法。此时我们再来看看。

class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
public class TestDemo4 {public static void main(String[] args) {Person per1 = new Person("Sirens", 20) ;Person per2 = new Person("Sirens", 20) ;System.out.println(per1.hashCode());System.out.println(per2.hashCode());}
}
//执行结果
460141958 
460141958

注意事项:哈希值一样。

结论:

1、hashcode方法用来确定对象在内存中存储的位置是否相同

2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

以上是我关于Java的笔记分享,也可以关注关注我的静态网站🥰

感谢你读到这里,这也是我学习路上的一个小小记录。希望以后回头看时,能看到自己的成长~

http://www.dtcms.com/a/496402.html

相关文章:

  • 网站开发游戏php网站开发建设
  • MySql:库的操作
  • ssh中neovim无法复制文本 clipboard
  • 网站优化系统红河做网站的公司
  • Switch升级 20.40 版本导致 2162-0002大气层报错的解决办法
  • 做暖暖小视频网站类似凡科建站的平台
  • 做网站的好公司大数据营销的特征有哪些
  • 高端网站建设加盟wordpress采集后排版
  • 【修订中】chatgbt的方法
  • 福州网络推广建站移动网站开发基础知识
  • 手机医疗网站模板广西网站建设价格多少
  • 大语言模型的幻觉问题:机理、评估与抑制路径探析
  • 社区网站建设资金申请汽车配件网上商城
  • java8中常用的工具函数
  • 家庭HMI:重塑智能家居的人机交互新范式
  • python网站开发简单吗网站维护很难吗
  • Cortex-M 中断机制基础
  • Linux C/C++ 学习日记(27):KCP协议(三):源码分析与使用示例
  • 网站怎么解析到域名wordpress 恢复
  • hype做网站动效最新网站建设哪家公司好
  • C++ 多线程实战 14|如何系统性避免死锁
  • 平顶山股票配资网站建设网站首页轮播图怎么做
  • C4D域-顶点贴图及布料的综合运用:打造精准布料动画控制
  • 昆明专业网站设计公司iis v6 新建网站
  • 北理工网站开发与运用营销型网站建设深圳
  • MVVMLight
  • 网站在服务器wordpress执行生命周期
  • 南昌做网站哪家公司比较好怎么设置wordpress底栏文字
  • 成都住房和城乡建设局 网站甘肃省建设银行网站
  • 《自动控制原理》第 2 章 线性控制系统的数学描述:2.3、2.4