Java SE 抽象类和接口(下)

注!!!

本文章所使用的编写代码和示例软件为:

IntelliJ IDEA Community Edition 2021.3.2

本文学习目标:

  1. 知道什么是Object 类。
  2. 知道Object 类的 equals 方法和 hashCode 方法,以及怎么去用。
  3. 知道 Comparable接口和 Comparator接口,以及怎么去使用。

1. Object 类

1.1 什么是 Object 类

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object 父类,简单来说:Object 类是所有类的父类,即所有类的对象都可以使用Object的引用进行接收。

举个例子:使用Object 接收所有类的对象

class Person {

}

class Student {

}
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        Student student = new Student();

        func(person);
        func(student);
    }

    public static void func(Object obj) {
        System.out.println(obj);
    }
}

 //运行结果

Demo1.Person@41629346
Demo1.Student@404b9385

 1.2 Object 类提供的方法

Object 类提供了一些定义好的方法,接下来介绍两个:equals() 方法,hashCode() 方法。

1.2.1 对象比较 equals 方法

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

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

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

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

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

 我们可以写个代码来证明:

class Person {
    public String name;
    public int age;

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


public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("小明",12);
        Person person2 = new Person("小明",12);

        System.out.println(person1 == person2);
        System.out.println(person1.equals(person2));
    }
}

//运行结果

false
false

 Person 类重写 equals 方法后重新比较

class Person {
    public String name;
    public int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = 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;
    }
}


public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("小明",12);
        Person person2 = new Person("小明",12);

        System.out.println(person1 == person2);
        System.out.println(person1.equals(person2));
    }
}

//运行结果

false
true

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

1.2.2 hashCode 方法

 hashCode 方法的功能简单来说就是帮忙算一个具体的对象地址

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 Test {
    public static void main(String[] args) {
        Person person1 = new Person("小明",12);
        Person person2 = new Person("小明",12);

        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }
}

//运行结果

990368553
1096979270

我们发现:两个对象的hash值不一样,但两个对象的名字、年龄是一样,这结果与我们期望的不符。

像重写 equals 方法,我们也可以重写 hashCode 方法。

class Person {
    public String name;
    public int age;

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

    public int hashCode() {
        return Objects.hash(name,age);
    }
}


public class Test {
    public static void main(String[] args) {
        Person person1 = new Person("小明",12);
        Person person2 = new Person("小明",12);

        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }
}

//运行结果

23458766
23458766

 我们发现:哈希值一样。

总结:

  1. hashcode方法用来确定对象在内存中存储的位置是否相同。
  2. 事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的 散列码,进而确定该对象在散列表中的位置。

 2. 接口使用实例

给对象数组排序(不灵活)

现在我们创建一个学生类,并且实例化两个学生对象如下:

public class 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 +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("小明",12);
        Student student2 = new Student("小红",13);
    }
    
}

现在我们打算比较两个对象,

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("小明",12);
        Student student2 = new Student("小红",13);

        System.out.println(student1 <student2);
    }

}

显然这种写法是错误的,student1与student2是两个引用变量,它们存的是对象的地址,不能直接进行比较,从这里我们也得到两个疑问:

  1.  当前自定义类,要根据什么样的规则进行比较?
  2. 这个规则如何定义?

这里我们可以用到Java提供的 Comparable 接口,定义一个规则,以便去实现比较。

Comparable 本身就有“比较”的意思,这个接口因此也是用于比较的。

我们进入到 Comparable 接口,可以看到

这里的 Comparable<T> 中的 T 指的是 泛型参数,就是你要比较哪个类就写哪个类,这里我们要比较两个学生对象,就写 Student 类, 接着它里面有一个抽象方法 compareTo() ,这个就是比较的规则,我们可以在子类中重写需要的规则。

我们使Student 类实现这个接口,如下:

public 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) {
        if(this.age > o.age) {
            return 1;
        } else if(this.age == o.age) {
            return 0;
        } else {
            return -1;
        }
    }
}

注意,这里的 this 指的是 谁调用这个方法就是谁

这里是按年龄进行比较,这下子我们就能比较了,我们设想输出结果为 -1

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("小明",12);
        Student student2 = new Student("小红",13);

        System.out.println(student1.compareTo(student2));
    }

}

//运行结果

-1

student1 小于 student2,因此输出-1,这与我们设想的一样。 

 那么我们如果想按照名字首字母比较,又该怎么样呢?首先,我们明确一件事,名字的数据类型为 String 是引用数据类型,它是一个类,既然是类那么它也会有很多方法

在它的目录底下,我们发现了 compareTo()方法 ,因此根据名字首字母进行比较我们可以这样写

public 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.name.compareTo(o.name);
    }
}

//这里调用的是 String 类的compareTo()方法。

既然两个对象我们已经知道如何去比较了,那么多个对象呢?对于多个对象,我们不得不想到数组,Student类 也是一个类型,那么可以创建一个 Studnet数组把多个 Student对象存起来。

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("李明",12);
        Student student2 = new Student("小红",9);
        Student student3 = new Student("王刚",11);

        Student[] stu = {student1,student2,student3};
    }
}

当初我们在学习数组排序时,曾使用过 Arrays 类的 sort()方法,只不过那会数组的类型是基本数据类型,那么现在是引用数据类型,还能行吗?我们试试

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("李明",12);
        Student student2 = new Student("小红",9);
        Student student3 = new Student("王刚",11);

        Student[] stu = {student1,student2,student3};
        System.out.println("排序前:" + Arrays.toString(stu));
        Arrays.sort(stu);
        System.out.println("排序后:" + Arrays.toString(stu));
    }
}

//运行结果

排序前:[Student{name='李明', age=12}, Student{name='小红', age=9}, Student{name='王刚', age=11}]
排序后:[Student{name='小红', age=9}, Student{name='王刚', age=11}, Student{name='李明', age=12}]

显然是可以进行排序的,并且是按照年龄升序进行排序的,这是为什么呢?

 这里需要明白一件事,就是sort()方法排序自定义类数组时会依赖 Comparable 接口,比较前,sort()方法会检测自定义类是否实现了 Comparable 类,如果没有实现就会报错,如果实现了,则会通过 Comparable 接口中的 compareTo()方法进行排序,而 compareTo方法我们在Student 类中已经重写了,就是按年龄进行排序。当一个类实现了 Comparable 接口,就意味着这个类的对象之间有了自然的比较方法。

写到这,我们的两个疑问基本解决了,但是我们有发现一个问题:当根据不同的属性进行比较时,不得不对 Student 类中重写的 compareTo()方法进行修改,这是很不方便的,那么有没有更方便的方法呢? 其实是有的,就是换一个接口

给对象数组排序(灵活)

我们可以通过 Comparator 接口实现,简单讲就是设置一个比较器,让 sort()方法按照这个比较器去实现排序

我们先定义一个学生类

public 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 +
                '}';
    }
}

再定义一个类,作为比较器,我们姑且设置它为“按年龄升序排序”比较器。这个类需要实现 Comparator 接口,并且重写 compare()方法。

public class AgeRise implements Comparator<Student> {
    @Override
//这里如果o1大于o2,则返回一个正数,否则返回一个负数。
    public int compare(Student o1, Student o2) {
        return o1.getAge() - o2.getAge();
    }
}

接着创建一个测试类,创建一个学生类数组,并且放入三个对象,创建一个比较器对象,接着把比较器对象作为 sort()方法的第二个参数,那么sort()方法再对数组进行排序时,就会按照比较器的规则进行排序。

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("黎明",12);
        Student student2 = new Student("王昂",14);
        Student student3 = new Student("李白",9);

        AgeRise ageRise = new AgeRise();
        Student[] stu = {student1,student2,student3};
        System.out.println("排序前:" + Arrays.toString(stu));
        Arrays.sort(stu,ageRise);
        System.out.println("排序后:" + Arrays.toString(stu));
    }
}

//运行结果

排序前:[Student{name='黎明', age=12}, Student{name='王昂', age=14}, Student{name='李白', age=9}]
排序后:[Student{name='李白', age=9}, Student{name='黎明', age=12}, Student{name='王昂', age=14}]

注意: 被比较的类可以不实现 Comparator 接口。

我们发现 sort()方法确实是按照比较器的规则进行比较了,那么我们想让它按名字排序,再定义一个比较器

public class NameCompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

在测试类中运行一下

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("黎明",12);
        Student student2 = new Student("王昂",14);
        Student student3 = new Student("李白",9);

        NameCompare nameCompare = new NameCompare();
        Student[] stu = {student1,student2,student3};
        System.out.println("排序前:" + Arrays.toString(stu));
        Arrays.sort(stu,nameCompare);
        System.out.println("排序后:" + Arrays.toString(stu));
    }
}

 //运行结果

排序前:[Student{name='黎明', age=12}, Student{name='王昂', age=14}, Student{name='李白', age=9}]
排序后:[Student{name='李白', age=9}, Student{name='王昂', age=14}, Student{name='黎明', age=12}]

可以看到,确实是按照名字进行排序了。

总结: 从上述两个例子,我们可以知道使用 Comparator 接口可以实现灵活的排序规则,我们不必每次都去比较的类中修改,只要定义我们需要的比较器即可,这样子就实现了解耦。

到此,“抽象类和接口”的内容已完结,若本文有不对的地方还请指出,多谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值