java中的抽象类与接口(面试常考,重要)!!(二)

简介: java中的抽象类与接口(面试常考,重要)!!(二)

接口使用实例(Comparable 接口与Comparator接口)

Comparable接口

刚才的关于例子比较抽象, 我们再来一个更能实际的例子.

给对象数组排序

给定一个学生类

class Student {
    private String name;
    private int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }
}

再给定一个学生对象数组

Student[]students=new Student[]{
        new Student("张三",95),
        new Student("李四",96),
        new Student("王五",97),
        new Student("赵六",92),
        };

现对这个对象数组中的元素进行排序(按分数降序).

按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

Arrays.sort(students);
System.out.println(Arrays.toString(students));
// 运行出错, 抛出异常.
        Exception in thread"main"java.lang.ClassCastException:Student cannot be cast to java.lang.Comparable

我们呢会发现此时会发生类型转换异常,仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定? 需要我们额外指定.

假设我们不去指定的话,我们也不知道到底是按照学生姓名比较,还是学生的年龄来进行比较.

此时就需要让我们的 Student 类实现 Comparable 接口, 并重写 其中的 抽象的compareTo 方法,下面来看代码:

1.//自定义类型比较大小需要实现Comparable接口,<>为泛型  
2.//对于Comparable接口来说,一般都是在类的内部定义的
3.class Student implements Comparable<Student> {  
4.    public String name;  
5.    public int age;  
6.    public int score;  
7.  
8.    public Student(String name, int age, int score) {  
9.        this.name = name;  
10.        this.age = age;  
11.        this.score = score;  
12.    }  
13.  
14.    //重写toString方法  
15.    @Override  
16.    public String toString() {  
17.        return "Student{" +  
18.                "name='" + name + '\'' +  
19.                ", age=" + age +  
20.                ", score=" + score +  
21.                '}';  
22.    }  
23.  
24.    //因为此时实现了Compareable接口,则需要重写其内部的抽象方法compareTo  
25.    @Override  
26.    public int compareTo(Student o) {  
27.        //通过分数来进行排序,如果想通过年龄等直接修改score替换成age即可  
28.        //如果大于的时候return 1,小于return -1,说明是按照从小到大的顺序排列的
29.        //如果大于的时候return -1,小于return 1,说明是按照从大到小的顺序排列的
30.        if(this.score > o.score) {  
31.            return 1;  
32.        }else if(this.score == o.score) {  
33.            return 0;  
34.        }else {  
35.            return -1;  
36.        }  
37.    }  
38.}  
39.public class TestDemo2 {  
40.  
41.    public static void main(String[] args) {  
42.        //如果想要对Student类的引用进行大小比较,就需要Student类去实现Comparable接口  
43.        Student student1 = new Student("bit",18,79);  
44.        Student student2 = new Student("gao",29,70);  
45.        Student student3 = new Student("shasha",17,99);  
46.          
47.        Student[] students = new Student[3];  
48.        students[0] = student1;  
49.        students[1] = student2;  
50.        students[2] = student3;  
51.  
52.        //sort方法默认从小到打排序  
53.        Arrays.sort(students);  
54.        System.out.println(Arrays.toString(students));  
55.    }  
56.}

此时我们通过分数进行排序,在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象. 然后比较当前对象和参数对象的大小关系(按分数来算).


如果当前对象应排在参数对象之前, 返回小于 0 的数字;

如果当前对象应排在参数对象之后, 返回大于 0 的数字;

如果当前对象和参数对象不分先后, 返回 0;


再次执行程序, 结果就符合预期了.


// 执行结果

[Student{name=‘gao’, age=29, score=70}, Student{name=‘bit’, age=18, score=79}, Student{name=‘shasha’, age=17, score=99}]

注意:

但是上述的比较方式是有局限性的,因为上述的比较方式中是直接把比较写死的,可以说只能比较年龄,不能比较姓名等其他东西,但是我现在就想比较除掉年龄的其他东西该怎么办?此时就用到了比较器.

对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.

Comparator接口(比较器)

Comparable接口 与Comparator接口的区别

首先我们知道的是Compareable接口是定义在类的内部 来实现对象的比较的,而我们比较器Comparator是专门定义在类的外部的

代码示例

还是之前的学生类,不同的是Student类不再实现Compareable接口:


此时我们想根据分数来对学生进行排序,那就在类的外部重新定义一个public类去实现Comparator接口,重写Comparator接口中的compare方法,以此来实现我们对于分数的比较和排序:来看代码:

首先来看Comparator接口中的compare方法:

image.png

来看对于分数比较的代码

//ScoreComparator.java类
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.score - o2.score;
    }
}

如果还想要对于学生的名字进行比较:来看代码实现:

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

这块的代码就不能再使用o1.name-o2.name这样的写法了,原因是我们的name是String类型,是不能进行相加减的,所以此处应该使用compareTo方法,有些同学可能就会纳闷了,compareTo方法出现于Compareable接口中,为什么要在这里使用这个呢?

答案如下:


我们先来看String类的源码:

image.png

可以看到String类实现了我们的Compareable接口,并重写了我们Compareable接口中的compareTo方法

image.png

所以关于名字的比较器我们可以知道它虽然实现了Comparator接口但是其内部使用的compareTo方法来源于Compareable接口.

下面来看主函数中是如何实现对于名字和分数的比较的:

class Student {
    public String name;
    public int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("bit", 89);
        students[1] = new Student("abc", 19);
        students[2] = new Student("htf", 59);
        System.out.println("===============没有排序前==============");
        System.out.println(Arrays.toString(students));
        System.out.println("===============根据分数排序后==============");
        ScoreComparator scoreComparator = new ScoreComparator();
        //传两个参数进来
        Arrays.sort(students, scoreComparator);
        //输出结果为:[Student{name='abc', score=19}, Student{name='htf', score=59}, Student{name='bit', score=89}]
        System.out.println(Arrays.toString(students));
        System.out.println("===============根据姓名进行排序后==============");
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students, nameComparator);
        //输出结果为:[Student{name='abc', score=19}, Student{name='bit', score=89}, Student{name='htf', score=59}]
        //以上姓名的排序是按照首字母进行排序的
        System.out.println(Arrays.toString(students));
    }
}

接口间的继承(扩展)

接口可以继承一个接口,也可以继承(扩展)多个接口,已达到复用的效果. 使用 extends 关键字.当然这里的继承关系我们把它理解成扩展的意思更为准确

interface IRunning { void run();
}
interface ISwimming { void swim();
}
// 两栖的动物, 既能跑, 也能游
interface IAmphibious extends IRunning, ISwimming {
  //此时IAmphibious这个接口相当于有了两个方法,一个是run,一个是swim
}
class Frog implements IAmphibious {
 //此时Frog这个类必须重写IAmphibious这个接口的两个抽象方法,一个run方法,一个swim方法
}

通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog 类, 就继续要实现 run 方法, 也需要实现 swim 方法.

总结

抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题).


核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.


如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口.

        class Animal {
            protected String name;
            public Animal(String name) {
                this.name = name;
            }
        }

再次提醒:

抽象类存在的意义是为了让编译器更好的校验, 像 Animal 这样的类我们并不会直接使用, 而是使用它的子类. 万一不小心创建了Animal 的实例, 编译器会及时提醒我们.

image.png

相关文章
|
3天前
|
Java 开发者
Java 8新特性之Lambda表达式与函数式接口
【7月更文挑战第59天】本文将介绍Java 8中的一个重要新特性——Lambda表达式,以及与之密切相关的函数式接口。通过对比传统的匿名内部类,我们将探讨Lambda表达式的语法、使用方法和优势。同时,我们还将了解函数式接口的定义和用途,以及如何将Lambda表达式应用于函数式编程。
|
2天前
|
Java
在Java多线程领域,精通Lock接口是成为高手的关键。
在Java多线程领域,精通Lock接口是成为高手的关键。相较于传统的`synchronized`,Lock接口自Java 5.0起提供了更灵活的线程同步机制,包括可中断等待、超时等待及公平锁选择等高级功能。本文通过实战演练介绍Lock接口的核心实现——ReentrantLock,并演示如何使用Condition进行精确线程控制,帮助你掌握这一武林秘籍,成为Java多线程领域的盟主。示例代码展示了ReentrantLock的基本用法及Condition在生产者-消费者模式中的应用,助你提升程序效率和稳定性。
10 2
|
2天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
8 2
|
3天前
|
Java 开发者
Java中的接口和抽象类
Java中的接口和抽象类
15 3
|
3天前
|
Java
Java接口
Java接口
10 1
|
3天前
|
Java
Java抽象类
Java抽象类
12 1
|
5天前
|
Java 应用服务中间件 HSF
Java应用结构规范问题之AllLoggers接口获取异常日志的Logger实例的问题如何解决
Java应用结构规范问题之AllLoggers接口获取异常日志的Logger实例的问题如何解决
|
4天前
|
Java Spring 容器
Java获取接口的所有实现类方法
这篇文章介绍了在Java中获取接口所有实现类的方法,包括使用JDK的ServiceLoader(SPI机制)和Spring Boot中的@Autowired自动注入及ApplicationContextAware接口两种方式。
22 1
|
7天前
|
Java 程序员
详解Java中的抽象类与接口的区别
【8月更文挑战第24天】
19 0
|
7天前
|
Java 编译器 开发工具
JDK vs JRE:面试大揭秘,一文让你彻底解锁Java开发和运行的秘密!
【8月更文挑战第24天】JDK(Java Development Kit)与JRE(Java Runtime Environment)是Java环境中两个核心概念。JDK作为开发工具包,不仅包含JRE,还提供编译器等开发工具,支持Java程序的开发与编译;而JRE仅包含运行Java程序所需的组件如JVM和核心类库。一个简单的&quot;Hello, World!&quot;示例展示了两者用途:需借助JDK编译程序,再利用JRE或JDK中的运行环境执行。因此,开发者应基于实际需求选择安装JDK或JRE。
30 0
下一篇
云函数