简介Object类+接口实例(深浅拷贝、对象数组排序)

简介: 简介Object类+接口实例(深浅拷贝、对象数组排序)

19736df347694661b0dddf89d642fa45.png


前言

上期我们深入探讨了Java中的接口,其实Java中内置了很多非常有用的接口,为了能够进一步加深对接口的认识,也为能够灵活掌握这些常见接口的使用,我们就这期单独谈谈接口的实例。


一、初识Object类

在后面介绍接口的使用时,我们绕不开要使用Object类,为了后面能够更好的理解,我们这里就将Object类放到前面讲解。

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收;所有的类都可以重写Object的方法。Object类中方法都特别重要,由于这里我们还没学习多线程,所以就目前介绍一下如下四种:

🍑1、toString()

toString方法的作用是将对象转换为字符串形式。通常为了方便输出对象的内容,需要重写toString方法。

class Student {
    private String name;
    private int age;

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

  //重写toString
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
    Student student = new Student("张三",18);
    System.out.println(student);
    }

🍑2、hashCode()

如果不重写Object类的toString方法,默认会调用Object类的toString方法,源码如下:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

假如不重写toString方法,这次我们再来运行:

这次我们并没有输出对象的内容,而是输出了一串数字,观察源码(我们先不理解@前的部分)显然这是Integer.toHexString(hashCode())搞的鬼,那么hashCode究竟是什么呢?

加上这段代码System.out.println(student.hashCode());我们再来测试一下:

631c27f19b0b4e929d1070741afb14c9.png

我们暂且将其理解为计算对象的位置,暂将这段地址看做对象的地址。其实hashcode这个方法对于我们目前来说是用不上的,这里先埋个伏笔,之后学了Hashmap再来详细介绍它的用法。

🍑3、equals()

equals方法在Object类中的源码:

public boolean equals(Object obj) {
    return (this == obj);
}

源码中的equals比较的是对象的引用是否相同,显然这样默认的equals方法在对象的比较中是几乎用不到的,所以一般我们会对这个equals方法进行重写以满足实际的比较需求。

比如如果我们将两个名字一样的学生看作相同的对象,我们就可以这样重写equals方法:

public boolean equals(Object obj) {
    if ( obj == null ) {//空对象和任何非空对象不同
        return false;
    }
    if ( this == obj ) {//引用相同对象必相同
        return true;
    }
    if(! (obj instanceof Student)) {//如果obj不是Student类的实例
        return false;       //那么必然不可能等于this
    }
    Student student = (Student)obj;
    if(this.name.equals(student.name)) {//这里利用了String类中重写的equals方法
        return true;
    }
    return false;
}

测试:

//……
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",18);
        Student student2 = new Student("张三",25);
        if(student1.equals(student2)) {
            System.out.println(student1+"和"+student2+"是同一个人!");
        }
    }
}

🍑4、clone()

clone()方法是用来复制一个对象,关于如何复制,这里涉及到了接口的知识,下面来详细介绍:👇


三、对象的深浅拷贝

1、Clonable接口

Clonable接口源码:

public interface Cloneable {
}

:Clonable是一个空接口,也叫标记接口,只要一个类实现了这个接口就标志这个类是可以克隆的,否则不可以克隆。

2、重写clone方法

clone方法是Object类下protected修饰的一个native方法,如果希望提供从类(被克隆的类)外部复制其对象的功能,则可以覆盖Object.clone()作为公共对象,只需在内部调用super.clone()仍使用默认实现即可。(这里不太好理解,大家简单了解,主要是clone()的使用)

//浅拷贝实现方式
@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}
//深拷贝实现方式不唯一,需要具体情况具体分析。

完成了上面两个步骤我们就可以开始进行对象的拷贝了,根据重写方法的实现方式,我们将拷贝分为浅拷贝和深拷贝,下面分别详细介绍:

🍑1、浅拷贝

浅拷贝: 按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址(引用) ,因此如果其中一个对象改变了这个引用下的数据,就会影响到另一个对象。(浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。)

理论太过枯燥,下面我们直接测试代码:

//--------------浅拷贝----------------
class Money implements Cloneable{
    public double m = 15.3;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Money{" +
                "m=" + m +
                '}';
    }
}

class Student implements Cloneable{
    public String name;
    public int age;
    public Money money = new Money();

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
//测试浅拷贝
public class Test {
  //throws CloneNotSupportedException先记忆固定格式,后期讲解
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student1 = new Student("张三",18);
        Student student2 = (Student) student1.clone();
        System.out.println("修改前:"+student1);
        System.out.println("修改前:"+student2);
        System.out.print("\n");
        student1.money.m=10.0;
        System.out.println("修改后:"+student1);
        System.out.println("修改后:"+student2);

    }
}

🍑2、深拷贝

深拷贝: 在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。(深拷贝把要复制的对象所引用的对象都复制了一遍。)

实现深拷贝的核心就是对clone方法重写的实现,下面我们还以上面的Student-Money为例,重写一下深拷贝的方法:

@Override
protected Object clone() throws CloneNotSupportedException {
  //1.先克隆student对象
    Student stutclone = (Student) super.clone();
    //2.克隆student对象中的money引用对象,并将克隆的新引用赋值给stuclone
    stutclone.money = (Money) this.money.clone();
    //3.将得到的深拷贝对象引用返回
    return stutclone;
}

同样的测试用例,测试结果:

🍑3、深浅拷贝的特点

通过上面的例子,相信大家对深浅拷贝已经渐入佳境,下面就针对上面观察到的现象对深浅拷贝做一个简单的小结,加深一下印象。

浅拷贝特点 :

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。

(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

深拷贝特点 :

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。

(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。

(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。

(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。


二、对象数组排序

在设计程序时我们会经常遇到给对象数组排序的问题,此时我们就可以用到Java为我们提供的两个比较接口对对象数组进行排序。

🍑1、通过Comparable接口排序

当我们使用Arrays.sort(数组名)排序对象数组时,内部用到Comparable接口,所以我们进行排序时也要对排序类实现Comparable接口。

Comparable接口源码:

public interface Comparable<T> {
    public int compareTo(T o);
}

注意事项:

  1. 源码中的代表泛型,这里我们写成比较对象的类名,先不理解会用即可。
  2. 对于一个对象可能会有多个成员属性,所以排序时我们要重写Comparable中的compareTo方法用来指定排序规则。

compareTo比较时返回值对于不同的情况可能会五花八门,这就给我们重写方法带来了障碍,所以这里有一种简单的技巧:

  1. 当我们比较的是整形家族的成员时,升序写成this.成员名-o.成员名;。 降序写成o.成员名-this.成员名;。.
  2. 当我们比较的是字符串类型的成员时,升序写成this.成员名.compareTo(o.成员名);。降序写成o.成员名.compareTo(this.成员名);

比如下面对一个学生对象的数组进行排序:

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

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
  //按年龄排序
    @Override
    public int compareTo(Student o) {
        return this.age-o.age;
    }

public class Test {
    //按年龄排序
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhangsan",18);
        students[1] = new Student("lisi",39);
        students[2] = new Student("wangwu",26);
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

按姓名排序:(重写compareTO方法即可)

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

🍑2、通过Comparator接口排序

我们通常将实现Comparator接口的类称为一个比较器,通过Arrays.sort(数组名,比较器类名)实现对对象数组的排序。

下面仍以学生对象为例,这次通过实现Comparator接口实现对其排序:

import java.util.Arrays;
import java.util.Comparator;

class Student {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }


    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
//姓名比较器
class NameCompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}
//年龄比较器
class AgeCompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge()- o2.getAge();
    }
}
//测试用例
public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        NameCompare nameCompare = new NameCompare();
        AgeCompare ageCompare = new AgeCompare();
        students[0] = new Student("zhangsan",18);
        students[1] = new Student("lisi",39);
        students[2] = new Student("wangwu",26);
        Arrays.sort(students,nameCompare);//按姓名排序
        System.out.println(Arrays.toString(students));
        Arrays.sort(students,ageCompare);//按年龄排序
        System.out.println(Arrays.toString(students));
    }
}

实现Comparable接口就可以实现对象的比较,那么为什么还要引出Comparator接口呢?

其实,实现Comparable接口的方式比实现Comparator接口的耦合性更强,也就是说,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。从这个角度说,通过实现Comparator接口的比较,使用起来会更加灵活。


小结

本章浅浅介绍了Object类中的四种方法,另外本期重点:

重点一: 是能够理解并且灵活的使用Clonable接口搭配clone方法进行深浅拷贝。

重点二: 是能够灵活应用Comparator和Comparable给对象数组进行排序。

重点三: 通过这些例子再次加深对接口的理解。


相关文章
|
12天前
|
Java
Java中的Object类 ( 详解toString方法 | equals方法 )
Java中的Object类 ( 详解toString方法 | equals方法 )
|
26天前
|
Java
【Java】Object类简单解析
【Java】Object类简单解析
15 1
|
1月前
|
前端开发 JavaScript Java
Java基础10-深入理解Class类和Object类(二)
Java基础10-深入理解Class类和Object类(二)
23 5
|
1月前
|
Java C++
Java基础10-深入理解Class类和Object类(一)
Java基础10-深入理解Class类和Object类(一)
23 4
|
18天前
|
存储 JSON JavaScript
JavaScript 魔法镜:透视对象Object与执行上下文的内在奥秘
JavaScript 魔法镜:透视对象Object与执行上下文的内在奥秘
|
1月前
|
Java API
JavaSE——常用API进阶一(1/3)-Object类(Object类的作用、Object类的常见方法-toString方法、equal方法、clone方法)
JavaSE——常用API进阶一(1/3)-Object类(Object类的作用、Object类的常见方法-toString方法、equal方法、clone方法)
17 0
|
1月前
|
Java 编译器 数据处理
JavaSE——面相对象高级一(4/4)-继承相关的注意事项:权限修饰符、单继承、Object类、方法重写、子类访问成员的特点......
JavaSE——面相对象高级一(4/4)-继承相关的注意事项:权限修饰符、单继承、Object类、方法重写、子类访问成员的特点......
31 0
|
9月前
|
Java
【面试题精讲】Object类的常见方法有哪些?
【面试题精讲】Object类的常见方法有哪些?
|
2月前
|
Java
Java Object 类
5月更文挑战第16天
|
2月前
|
存储 算法 Java
滚雪球学Java(42):探索对象的奥秘:解析Java中的Object类
【5月更文挑战第17天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 2
滚雪球学Java(42):探索对象的奥秘:解析Java中的Object类