当前位置:AIGC资讯 > AIGC > 正文

图灵日记之java奇妙历险记--抽象类和接口

目录

抽象类 概念 抽象类语法 接口 概念 规则 使用 特性 实现多个接口 接口的继承 接口使用实例 Clonable接口和深拷贝 抽象类和接口的区别 Object类

抽象类

概念

在面向对象的概念中,所有对象都是通过类来描述的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息描绘一个具体的对象,这样的类就是抽象类

使用abstract修饰的方法称为抽象方法 使用abstract修饰的类称为抽象类 抽象类是不可以进行实例化的 抽象类当中可以和普通类一样定义成员变量和成员方法 当一个普通的类继承了这个抽象类,那么需要重写这个抽象类当中的所有抽象方法 抽象类的出现就是为了被继承 abstract和final不能共存 被private static修饰抽象方法也不可以

抽象类语法

在java中,一个类如果被abstract修饰称为抽象类,抽象类中被abstract修饰的方法称为抽象方法,抽象方法不用给出具体的实现体
注意:抽象类也是类,内部也可以包含普通方法和属性,甚至构造方法

接口

概念

接口是公共的行为规范标准,大家在实现时,只要符合规范标准
就可以调用
在java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

规则

接口是使用interface方法修饰的 接口当中不能有被实现的方法,意味着只能有抽象方法.但是两个方法除外:一个是static修饰的方法 一个是被default修饰的方法 接口当作的抽象方法默认都是public abstract修饰的
什么都不写的时候默认abstract修饰 接口当中的成员变量默认都是public static final 修饰的 接口不能进行实例化 类和接口之间的关系,可以使用implements来进行关联 接口也是有对应的字节码文件的

接口定义格式与定义类的格式基本相同,将class关键字换成interface关键字,就定义了一个接口

interface 接口名称 {
    
}

使用

接口不能直接使用,必须要有一个"实现类"来实现该接口,实现接口中所有的抽象方法

class 类名称 implements 接口名称{
  // ...
}

注意:子类和父类之间是extends继承关系,类和接口之间是implements实现关系

特性

接口类型是一种引用类型,但是不能直接new接口的对象,无法实例化 接口的每个方法都是public修饰的抽象方法,即接口中的方法被隐式的指定为public abstract(只能是public abstract, 其他修饰符都会报错) 接口的方法是不能在接口实现的,只能由实现接口的类来实现
但在接口的方法里非要实现需要static或者default修饰
static修饰的方法不能重写
default修饰方法可重写可不重写 重写接口方法时,不能使用默认的访问权限
因为接口方法里默认就是public abstract,又因为子类的访问权限大于等于父类,子类只能被public修饰 接口中可以含有变量,但是接口中的变量会被隐式的指定为public static final 变量 接口不能有代码块(实例代码块和静态代码块)和构造方法 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是class 如果类没有实现接口中的所有的抽象方法,则类必须被设置为抽象

实现多个接口

接口解决java多继承的问题

abstract class Animal{
    public String name;
    Animal(String name) {
        this.name = name;
    }
    abstract public void eat();
    abstract public void swim();
}

动物里并不是所有的动物都可以游泳,所以把swim写在Animal这个类里面不好,但是如果你把swim写到一个类就行了嘛?但是java不支持同时继承多个类,所以写进类里也不行,所以我们把他封装成了接口

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类
必须设置为抽象类

接口的继承

在java中,类和类之间是单继承的,一个类可以实现多个接口,接口和接口之间可以多继承.即用接口可以达到多继承的目的

interface walk {
    public void walk();
}
interface run extends walk {
    public void run();
}

run接口有walk接口的特性,可以使用extends关键字来实现复用的效果,extends理解为拓展的意思,即run拓展了walk的功能
run接口不仅具备run接口本身的功能,而且具备walk这个接口的功能

interface A {
    public void a();
}
interface B extends A{
    public void a();
}
class test implements B{
    @Override
    public void a() {

    }
}

接口B和接口A有一样的方法,当接口B拓展接口A时,类test获得B接口,要实现a这个方法

实现的是接口B的方法a

两个关系:

类和接口之间的关系–>>implements实现 接口和接口之间的关系–>>extends拓展

接口之间的继承相当于把多个接口并在一起

接口使用实例

    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        System.out.println(a>b);
    }
public class Test {
    public static void main(String[] args) {
        Person person01 = new Person();
        Person person02 = new Person();
        System.out.println(person01>person02);
    }
}

class Person {
    public String name;
    public int age;
    public int height;
}

引用类型比较就不可以了,因为比较两个引用类型没有意义,但是你想比较引用类型里的成员变量的话,他也不知道是哪一个

public class Test {
    public static void main(String[] args) {
        Person person01 = new Person();
        Person person02 = new Person();
        System.out.println(person01.compareTo(person02));
    }
}

class Person implements Comparable<Person>{
    public String name;
    public int age;
    public int height;

    @Override
    public int compareTo(Person o) {
        if(this.age>o.age) {
            return 1;
        } else if(this.age==o.age) {
            return 0;
        }
        else {
            return -1;
        }
    }
}

后续文章会讲,此处仅供参考

但是如果用如上代码,在比较身高等其他值的时候就不方便

public class Test {
    public static void main(String[] args) {
        Person person01 = new Person("张三",1,1);
        Person person02 = new Person("李四",2,2);
        System.out.println(new AgeCompare().compare(person01, person02));
    }
}

class AgeCompare implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.age-o2.age;
    }
}

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

比较器来进行年龄比较,之后同样的方法比较身高等

第一种方式比较对类的侵入性比较强,一旦写好了规定的比较方式,之后只能使用这种方式来进行比较
第二种方式就会比较灵活,单独创建一个类实现比较方法,调用方法时,传入要比较的两个对象即可


public class Test {
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(111,"a");
        people[1] = new Person(101,"b");
        people[2] = new Person(121,"c");
        System.out.println(Arrays.toString(people));
        System.out.println("===========================");
        Arrays.sort(people);
        System.out.println("===========================");
        System.out.println(Arrays.toString(people));

    }
}


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

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

同理,对自定义类型的数组排序此处也会报错

Array.sort的源代码追根溯源发现比较是依托compareTo来实现比较的,所以我们可以在类里自己实现compareTo方法


public class Test {
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(111,"a");
        people[1] = new Person(101,"b");
        people[2] = new Person(121,"c");
        System.out.println(Arrays.toString(people));
        System.out.println("===========================");
        Arrays.sort(people);
        System.out.println("===========================");
        System.out.println(Arrays.toString(people));

    }
}


class Person implements Comparable<Person>{
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

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

    @Override
    public int compareTo(Person o) {
        return this.age-o.age;

    }
}


sort方法的重载中还可以传入比较器


public class Test {
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(111,"a");
        people[1] = new Person(101,"b");
        people[2] = new Person(121,"c");
        System.out.println(Arrays.toString(people));
//        Arrays.sort(people);
        AgeCompare ageCompare = new AgeCompare();
        Arrays.sort(people,ageCompare);
        System.out.println("===========================");
        System.out.println(Arrays.toString(people));
    }
}
//名字比较器
class NameCompare implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o2.name.compareTo(o1.name);
    }
}
//年龄比较器
class AgeCompare implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o2.age-o1.age;
    }
}

class Person implements Comparable<Person>{
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

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

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

也可以自己实现排序方法来进行比较,如下

public class Test {
    public static void bubbleSort(Comparable[] comparables) {
        for (int i = 0; i < comparables.length-1; i++) {
            for (int j = 0; j < comparables.length-i-1; j++) {
                if(comparables[j].compareTo(comparables[j+1])>0) {
                    Comparable tem = comparables[j];
                    comparables[j] = comparables[j+1];
                    comparables[j+1] = tem;
                }
            }
        }
    }
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(111,"a");
        people[1] = new Person(101,"b");
        people[2] = new Person(121,"c");
        System.out.println(Arrays.toString(people));
        bubbleSort(people);
        System.out.println(Arrays.toString(people));

    }
}

class Person implements Comparable<Person>{
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

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

    @Override
    public int compareTo(Person o) {
        return this.age-o.age;
    }
}

Clonable接口和深拷贝


public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal("张三",10);
        Animal animal1 = animal.clone();
    }
}

class Animal {
    String name;
    int height;
    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", height=" + height +
                '}';
    }

    public Animal(String name, int height) {
        this.name = name;
        this.height = height;
    }

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


创建了一个Animal类,实例化对象animal,想利用clone克隆animal到animal1,但是发生报错

可能会抛出异常,这个异常叫作不支持克隆异常
编译时期的异常

处理这种异常,对main函数也申请如上操作

发现依旧报错


观察clone方法里返回值是Object,但是main方法里animal1是Animal类,类型不匹配,需要强转一下
对main方法如下处理解决刚才的异常问题


public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Animal animal = new Animal("张三",10);
        Animal animal1 = (Animal)animal.clone();
    }
}

按照剧本来走的话

animal1克隆animal,一切祥和

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Animal animal = new Animal("张三",10);
        Animal animal1 = (Animal)animal.clone();
        System.out.println(animal);
        System.out.println(animal1);
    }
}

运行起来却又报错

我们要想对自己写的类型进行克隆的时候,要实现Clonable 接口


运行成功,但是当我们点进Clonable 接口去看源代码的时候

就会发现这个接口是空的,所以我们实现这个接口的意义在哪里
刚才这种接口我们叫作空接口或者标记接口
他的作用在于类实现了Clonable 接口这种空接口,代表这个类是可以被克隆的


public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Stu stu = new Stu("张三",1);
        Stu stu1 = (Stu)stu.clone();

        System.out.println(stu.index.i);
        System.out.println(stu1.index.i);
        stu1.index.i = 0;
        System.out.println(stu.index.i);
        System.out.println(stu1.index.i);

    }
}

class Index {
    public int i = 1;
}

class Stu implements Cloneable{

    public String name;
    public int weight;

    Index index = new Index();
    public Stu(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

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

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


修改stu1里通过引用修改对象index里的成员变量i,但是stu里index的i值也改变

stu1克隆stu,stu和stu1两者共用同一个引用,而非另外开辟空间,所以当你通过引用改变stu1里index的i值时,也时改变stu里面index的i值,这种是浅拷贝,要实现stu里index和stu1里的index的引用不同,要进行深拷贝,Index也要支持克隆

如上,让Index也支持克隆,但这样仅仅是支持克隆,但是并未让Stu类里的克隆方法里进行实现对index的克隆,如下

这样就之后,改变stu1里index的i值就不会影响stu的了


public class Test {
    public static void main(String[] args) throws CloneNotSupportedException{
        Stu stu = new Stu("张三",1);
        Stu stu1 = (Stu)stu.clone();

        System.out.println(stu.index.i);
        System.out.println(stu1.index.i);
        stu1.index.i = 0;
        System.out.println(stu.index.i);
        System.out.println(stu1.index.i);

    }
}

class Index implements Cloneable{
    public int i = 1;

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

class Stu implements Cloneable{

    public String name;
    public int weight;

    Index index = new Index();
    public Stu(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Stu tmp = (Stu) super.clone();
        tmp.index = (Index) this.index.clone();
        return tmp;
    }
}

抽象类和接口的区别

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

Object类

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

public class Test {
    public static void main(String[] args){
        Animal animal = new Animal("张三");
        Animal animal1 = new Animal("张三");
        System.out.println(animal1 == animal);
    }
}

class Animal {
    String name;

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

如上比较两个对象是否相等,这种比较是比较引用,所以会打印false
Object里equals方法用来比较对象是否相等

public class Test {
    public static void main(String[] args){
        Animal animal = new Animal("张三");
        Animal animal1 = new Animal("张三");
        System.out.println(animal.equals(animal1));
    }

}


结果仍是false


源码和第一次比较方式是一样的,在对引用进行比较相等,所以我们在Animal类里重写这个方法,自己来设计比较方法

class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        Animal animal = (Animal) obj;
        return animal.name.equals(this.name);
    }
}
public class Test {
    public static void main(String[] args){
        Animal animal = new Animal("张三");
        Animal animal1 = new Animal("张三");
        System.out.println(animal.equals(animal1));
    }


}


更新时间 2024-01-18