抽象类,接口,抽象类和接口之间的区别,API中的经典接口(Cloneable,Comparable,Comparator)(十三)

365 阅读11分钟

抽象类

1 为什么要有抽象类?

​ 在编写项目时,会发现类的层次结构很多,(父类还有父类),这个时候发现靠上层的父类,会越来越抽象,即它里面的方法一般都没有办法编写具体的实现代码。

​ 如果按照现有的知识,有返回值的方法又不能不写方法体。如果父类中干脆不写这个方法的话,多态引用时就无法调用子类的方法。

2 抽象类的格式

【修饰符】 abstract class 类名 {
}

3 抽象方法的格式

【修饰符】abstract 返回值类型 方法名(【形参类型】);

注意:抽象方法没有方法体,直接在方法签名后面加;

4.抽象类的特点和要求

(1)抽象类是不能直接创建对象的,即不能实例化。

(2)如果一个类中包含了抽象方法,那么这个类必须声明为抽象类。

(3)如果一个类继承了抽象类,那么它要么也被声明为抽象类,要么就必须重写实现父类中的所有抽象方法。

(4)一个类中也可能没有抽象方法。(这样做的目的是为了防止被创建对象)

​ (5)抽象类一定有构造器。虽然它自己不能直接new对象,但是创建子类对象时可能会调用父类(抽象类)对父类的成员变量进行初始化。

public abstract class Graphic {
    public abstract  double getArea();
}
//圆类
public class Circle extends Graphic{
    private double radius;
    @Override
    public double getArea() {
        return Math.PI*radius*radius;
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
}
//长方形类
public class Rectangle extends Graphic{
    private double length;
    private double width;

    public Rectangle() {
    }

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
       this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double getArea() {
        return width*length;
    }
}

虽然Graphic类是抽象类,不能直接创建它的对象,但是我们可以创建Graphic[]这样的数组对象。

接口

1 什么是接口?

​ 接口是统一的标准规范。两者之间链接的一种方式。

​ Java程序是需要和其他程序交互的,例如:数据库。数据库有很多品牌:mysql,oracle,sql server,db 2,redis,mangodb....这些数据库内部都不一样,Java程序员想要去链接数据库的化妹妹一个数据库都需要提供不同的API,这就会造成程序员学习成本大大增加,对于jdk开发人员来说,他们也需要额外开发很多套API。

(1)sql语言标准

(2)数据库厂商必须提供一些具体的API。这些API必须有统一的接口。接口由Java提供,数据库厂商来实现这些接口。

2 如何声明接口

【修饰符】 interface 接口名{

}

3 如何实现接口

【修饰符】 class 类名 implement 接口名{

}
//或着
【修饰符】 class 类名 implements 接口名1,接口名2{

}

实现接口的类,叫做实现类。

4 接口的特点

(1)接口不能直接创建对象,就是不能实例化。(它只是一个标准,new它的对象没有意义)。

(2)接口中的成员有哪些?

公共静态常量

​ (public static final可省略,默认就是这个类型)

公共抽象方法

​ (因为接口是标准,只是说明遵循这个标准的类(产品)中,应该具备什么功能,但是这个功能的具体实现,由产品的具体商家来实现。因为不同的产品,功能的具体事项是不一样的)

③公共的静态方法

​ 其中的public 可以省略,static不能省略。

public static 返回值类型 方法名(【形参列表】){方法体}

在jdk1.8之前,只能有①和②;在jdk1.8之后,接口又增加了两类成员③和④。jdk1.9之后,接口又增加了⑤。

⑤私有方法

为什么要增加静态方法?

因为早期出现了很多成对的API,即类和接口,例如:Path接口和Paths类,Collection接口和Collections工具类。Paths是工具类,它里面全是静态方法,这些静态方法为Path接口以及它们的实现类服务的。那么如果按着之前设计,就会多了很多的.java文件,而我们知道工具类的静态方法调用时不需要对象的,接口也不能创建对象,那么干脆把这些静态方法声明到接口中。这些方法都和接口有关,把这些方法放在接口中更好管理。放在接口中更好管理,遵循高内聚的原则。

④公共的默认方法

​ public可以省略,default不能省略。

 public default 返回值类型 方法名(【形参列表】){方法体}

为什么要增加默认方法?

原因一:

​ 如果某个接口的所有实现类,某个功能的实现代码是一样的,那么每一个类协议跟,有点太冗余了。

原因二(主要):

​ 随着版本的更替,早期的接口出现了一些问题,需要增加功能(方法),按照之前的语法要求,只能增加抽象方法。这就会导致原来所有的接口的实现类全部需要改写,增加实现这个抽象方法,这个工作量太大,那么就改为增加默认方法,哪怕默认方法中是空实现。如果实现类觉得这个接口的默认方法的实现不适合它,实现类可以选择进行重写。 类中重写接口的默认方法时,default要去掉。

jdk1.9之后,接口又增加了一种成员:

⑤私有方法

​ private不能省略,之前接口中所有成员都是public,因为接口时标准,标注是给所有人遵守的,给大家访问。

为什么要增加私有方法?

因为自从Java允许增加静态方法和默认方法之后,如果静态方法和默认方法有多个之后,特别重载之后出现了很多重复代码,如果按照之前的语法,抽取出来的方法也必须是public,一旦是公共的,外边就可以调用了。但是这个抽取的方法的代码只是别的功能的一部分,不适合给外边调用,所以声明为private更合适的。

现在接口变得与抽象类越来越像?不同的是:

①接口中没有非常量的成员变量

②接口中是没有构造器的

③抽象类支持单继承,接口支持多实现,就是一个类可以实现多个接口。

(3)实现类在实现接口时,必须重写(实现)接口的所有抽象方法,否则只能声明为抽象类。

(4)Java的类是有单继承限制的,Java的接口是支持多实现的。即一个Java类可以同时实现多个接口。

(5)接口可以继承接口,并且支持多继承

【修饰符】 interface 子接口名 extends 父接口名{

}

关系总结: 类与类:继承关系,单继承 类与接口:实现关系,多实现 接口与接口:继承关系,多继承 接口 不能继承或实现类。

5 接口的方法调用

(1)接口的静态(static)方法调用格式

接口名.静态方法
//实现类.接口的静态方法(是错误的)

调用父类的静态方法:

​ 父类.父类的静态方法

​ 子类.父类的静态方法

子类和父类的关系是is-a的关系,比较亲密,可以比喻为父亲和儿子。

实现类与接口的关系是has-a/like-a的关系,没有那么紧密。

(2)接口的默认(default)方法如何调用

实现类的对象.默认方法
父接口的变量(实际存储的是实现类的对象).默认方法

不光

​ 父类 变量 = 子类对象;

属于多态引用。

父接口 变量 = 实现类对象;

也属于多态引用。

关于默认方法调用,运行时执行的是接口中的方法实现还是实现类的方法实现?

如果实现类重写了,就执行重写的代码,否则就执行接口中的默认实现。

6 实现类在实现接口时

(1)当接口的默认方法,没有冲突的情况,实现类可以选择重写或者补充些

(2)当接口的默认方法,有冲突的情况,实现类必须进行重写

冲突:多个继承的接口中具有相同方法名的默认方法(方法名和形参列表都相同)

如果有冲突,如何重写?

①和两个接口完全没有无关的重写

②选择保留其中一个接口的实现

接口名.super.默认方法名

(3)当接口既继承父类,又实现接口,当父类中的默认方法(default)与父接口中的默认方法名相同时,

①默认保留父类的方法;

②也可以对父类方法进行重写;

③如果要保留接口的方法,需要

接口名.super.默认方法名

7 接口中的常量调用格式

接口名.常量对象
    //当实现类中有同名变量时,也可以使用这个形式返回到父类中的常量对象。

为什么要设计抽象类,又要设计接口?

(1)抽象类与子类之间是可以表示is-a的关系。Person抽象类,与子类Student,Teacher等,这些子类属于近亲关系。Flyable接口,里面包含fly方法,实现类Bird,Plane,Kite等之间就没有近亲关系,只是具有相同的某个行为特征。

(2)抽象类包含多个子类的共同特征,除了方法之外,还有相同的成员变量,这些成员变量也可以抽取到父类中,抽象类可以包含非常量,而接口是不允许的。

(3)接口的好处就是可以多实现。

API经典接口

1.java.lang.Cloneable接口

​ 当我们需要重写Object类的clone方法时,必须实现的一个接口。Object类的clone()方法:protected Object Clone() throws CloneNotSupportedException 子类重写这个方法时,必须实现Cloneable接口,否则就报CloneNotSupportedException异常。clone()方法可以返回一个新的对象,这个对象中存储的值与源对象一致,但是地址不同。

2.java.lang.Comparable接口

和对象的比较大小有关

java.lang.Comparable接口的抽象方法: int compareTo(Object o) 当this对象 “大于” o对象时,返回正整数 当this对象 “小于” o对象时,返回负整数 当this对象 “等于” o对象时,返回0

我们希望它们有共同的方法:比较大小的方法,只能让它们实现同一个接口。比如:java.lang.Comparable,那么参数列表接受进来的是提升为Object类型的数组,那么我们将他进行类型转换成Comparable类型,就可以使用被我们实现的compareTo方法。

public class MyArrays {
    //int[]类型的数组,只能接收int[]类型数组
    public static String toString(int[] arr){
        String result = "[";
        for(int i=0; i<arr.length; i++){
            if(i==0){
                result += arr[i];
            }else{
                result += "," + arr[i];
            }
        }
        return result + "]";
    }

    //Object[]类型的数组,可以接收任意的对象数组
    public static String toString(Object[] arr){
        String result = "[";
        for(int i=0; i<arr.length; i++){
            if(i==0){
                result += arr[i];
            }else{
                result += "," + arr[i];
            }
        }
        return result + "]";
    }

    public static void sort(int[] arr){
        for (int i=1; i<arr.length; i++){
            for (int j=0; j<arr.length-i; j++){
                if(arr[j] > arr[j+1]){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] =temp;
                }
            }
        }
    }

    public static void sort(Object[] arr){
        for (int i=1; i<arr.length; i++){
            for (int j=0; j<arr.length-i; j++){
                //元素arr[j]的编译时类型是Object
                //需要先将arr[j]转为Comparable
//                if(arr[j] > arr[j+1]){
                if(((Comparable)arr[j]).compareTo(arr[j+1])>0){
//不管任何类型的对象,只要实现了Comparable接口和这个方法,就可以使用这个方法
                    //防止出现类型转换失败问题
                    Object temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}

3.java.lang.Comparator接口

​ 用于比较大小的接口,当我们需要给连个对象比较大小时,就可以给这个对象的类型实现java.lang.Comparable接口,重写int compareTo(T o)方法。

​ 但是,有时候,要比较的对象的类并不是我们自己写的,是第三方提供,而且没有给我们实现Comparable接口。那么这个时候我们只能借助Java.util.Comparator接口来进行补救。或者当我们项目中同时有多个需求,例如学生类,在一个位置是要求按照成绩排序,在另一个位置要求按照年龄排序,第三个位置按照姓名排序。我们只能在学生类中实现Comparator接口,按照成绩排序,在另一个地方单独再写一个类实现Comparable接口,在第三个地方再写一个类实现Comparable接口。(可以在不同类中实现Comparable接口/外比较器,但是Comparator接口只能给实体类实现/内比较器)

​ java.lang.Comparator这个接口中有一个抽象方法int compare(T o1,T o2),返回值类型也是int ,当o1 “大于” o2时,返回正整数 ​ 当o1 “小于” o2时,返回负整数 ​ 当o1 “等于”o2时,返回0

public class TestComparator {
    public static void main(String[] args) {
        Teacher[] arr = new Teacher[2];
        arr[0] = new Teacher("张三",12000);
        arr[1] = new Teacher("李四",11000);

        //排序:
       // MyArrays2.sort(arr);//报错:ClassCastException类型转换异常
                            //因为Teacher没有实现java.lang.Comparable接口,不能转为这个类型
//        先创建一个Comparator的实现类对象
        TeacherComparator tc = new TeacherComparator();
        MyArrays2.sort(arr , tc);

        //遍历数组
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

//为Teacher类的对象比较,写一个Comparator接口的实现类
class TeacherComparator implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        //假设两个Teacher对象按照薪资比较大小
        //先将o1,o2向下转为Teacher
        Teacher t1 = (Teacher) o1;
        Teacher t2 = (Teacher) o2;
       /* if(t1.getSalary() > t2.getSalary()){
            return 1;
        }else if(t1.getSalary() < t2.getSalary()){
            return -1;
        }
        return 0;*/
        return Double.compare(t1.getSalary(), t2.getSalary());
    }
}

//假设Teacher类的代码不能修改了。他没有实现java.lang.Comparable接口,
class Teacher{
    private String name;
    private double salary;

    public Teacher() {
    }

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}
public class MyArrays2 {
    public static void sort(Object[] arr){
        for (int i=1; i<arr.length; i++){
            for (int j=0; j<arr.length-i; j++){
                //元素arr[j]的编译时类型是Object
                //需要先将arr[j]转为Comparable
                if(((Comparable)arr[j]).compareTo(arr[j+1])>0){// if(arr[j] > arr[j+1]){
                    Object temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }

    public static void sort(Object[] arr, Comparator com){
        for (int i=1; i<arr.length; i++){
            for (int j=0; j<arr.length-i; j++){
                //元素arr[j]的编译时类型是Object
                //需要先将arr[j]转为Comparable
                if(com.compare(arr[j], arr[j+1]) >0){
                    Object temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
}