• 《JAVASE系列》抽象类与接口


    JAVASE系列》抽象类与接口

    前言

    本章重点讲解java语言中的抽象类与接口,本章依然是javase的重点难点

    参考书籍:《java核心技术卷1》

    你若盛开,清风自来。

    1.抽象类

    1.1抽象类是什么?

    在我们之前学习类的继承与多态的时候,我们会注意到,我们希望子类去继承父类的方法并发生重写与向上转型来实现多态,我们为了编译器不报错,每次都实现了父类的方法。

    例如:

    class Animal{
        public void eat(){
            System.out.println("动物吃");
        }
    }
    class Dog extends Animal{
        public void eat(){
            System.out.println("吃骨头");
        }
    }
    public class Main{
        public static void main(String[] args) {
            Animal animal = new Dog();
            animal.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们不得不去写Animal这个父类的eat方法。当我们希望父类只是用来被实现多态,就显得不是那么方便了。因此引入了抽象类的概念,使得抽象类成为一个单纯为了实现继承与实现多态的类。

    抽象类概念:

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

    1.2 抽象的语法与细节

    关键字 abstract实现抽象类

    被abstract 修饰的类就是抽象类,被abstract修饰的方法就是抽象方法

    abstract class Animal{
        private int age;
        private String name;
        public void sleep(){
            System.out.println("睡大觉");
        }
         public static void hehe(){
            System.out.println("笑一笑");
        }
        abstract public void eat();
    }class Dog extends Animal{
        public void eat(){
            System.out.println("吃骨头");
        }
    
    }
    public class Main{
        public static void main(String[] args) {
            Animal animal = new Dog();
            animal.eat();
        }
    }![在这里插入图片描述](https://img-blog.csdnimg.cn/05bc58bc0ee54b91875842f5dc8ce18f.png#pic_center)
    
    class Dog extends Animal{
        public void eat(){
            System.out.println("吃骨头");
        }
    
    }
    public class Main{
        public static void main(String[] args) {
            Animal animal = new Dog();
            animal.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在这里插入图片描述

    在这里插入图片描述

    • 可见,一个类被abstract修饰后变为抽象类,里面的成员变量与成员方法与普通类一样。

    • 我们通过抽象类来实现向上转型,成功地实现了多态。

    • 但是抽象类不能被实例化

    • 并且继承该抽象类的子类必须重写父类的所有抽象方法,否则编译不通过。

    值得注意的是:

    1. abstract 修饰的方法的目的是为了被子类实现重写进而实现多态,而final关键字修饰的方法则不能重写,所以不能出现以下代码:
    abstract public final void eat();
    
    • 1

    ​ 报错信息:(比较直接明了)

    在这里插入图片描述

    1. abstract修饰的方法也不能用private修饰,不能将抽象方法定义为私有的,要不然如何实现向上转型呢?所以不能出现以下代码:

      abstract private void eat();
      
      • 1

      报错信息:(比较直接明了)

      在这里插入图片描述

    抽象类继承抽象类

    抽象类是可以继承抽象类的。

    abstract class Animal{
        private int age;
        private String name;
        public void sleep(){
            System.out.println("睡大觉");
        }
        public static void hehe(){
            System.out.println("笑一笑");
        }
        abstract public void eat();
    }
    abstract class mammal extends Animal{
        abstract public void run();
    }
    class dog extends mammal{
        public void eat(){
            System.out.println("狗狗吃骨头");
        }
        public void run(){
            System.out.println("狗狗在跑");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    当一个抽象类A继承了抽象类B,可以不重写抽象类B的抽象方法。

    普通类C继承了A,就得重写A和B的抽象方法(欠下的债,迟早得还)

    1.3抽象类的意义与作用

    抽象的意义是为了被继承,实现多态。

    抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.

    使用抽象类相当于多了一重编译器的校验.

    使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类的时候,编译器是不会报错的. 如果父类是抽象类的时候,就会在实例化的时候提示错误, 让我们尽早发现问题.

    2.接口

    2.1 接口是什么?

    接口在我们日常生活中比比皆是,例如USB插口,电源插口,可以说这是具有规范的接口。

    在这里插入图片描述

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

    2.2 接口的语法于细节

    关键字Interface实现接口

    语法注意:

    接口中的语法于细节

    1. 接口名一般以大写字母 I 开头
    2. 接口中的成员方法只能是抽象方法,所以的方法默认都是public abstract
    3. 接口中的成员变量默认为public static final,也就是说只能为常量,并且必须初始化
    4. 接口中也可以实现方法,但是必须用default修饰。
    5. 接口中的静态方法可以有具体的实现。
    6. 接口不能进行实例化对象,也不能有构造方法。
    7. 接口中不能有静态代码块
    interface Itest{
        public static final int c = 20;   //正确定义方式,注意初始化
        int a = 10;                      // 默认实现方式
        default void method2(){           //default修饰的接口普通方法
            System.out.println("接口默认方法");
        }
        public abstract void method1();   //正确定义接口的抽象方法
        void method();                    //默认实现方式
        public static void method3(){     //接口的静态方法实现
            System.out.println("接口静态方法");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    接口使用时的语法与细节:

    1. 普通类可以通过implements继承接口。

    2. 一个类可以继承一个抽象类,同时实现多个接口

    3. 接口也可以实现向上转型,进而实现多态

    4. 类和接口之间是implements,接口与接口之间是extends(拓展),接口与接口之间用逗号隔开

    interface Itest2{
        public abstract void heihei();
    }
    interface Itest1{
        public abstract void haha();
    }
    interface Itest extends Itest1,Itest2{
        public static final int c = 20;   //正确定义方式
        int a = 10;                      // 默认实现方式
        default void method2(){           //default修饰的接口普通方法
            System.out.println("接口默认方法");
        }
        public abstract void method1();   //正确定义接口的抽象方法
        void method();                    //默认实现方式
        public static void method3(){     //接口的静态方法实现
            System.out.println("接口静态方法");
        }
    
    }
    abstract class Animal{
        private int age;
        private String name;
        public void sleep(){
            System.out.println("睡大觉");
        }
        public static void hehe(){
            System.out.println("笑一笑");
        }
        abstract public void eat();
    }
    abstract class mammal extends Animal{
        abstract public void run();
    }
    class Dog extends mammal implements Itest{
        public void eat(){
            System.out.println("狗狗吃骨头");
        }
        public void run(){
            System.out.println("狗狗在跑");
        }
    
        @Override
        public void method1() {
            System.out.println("Itext接口的抽象方法重写");
        }
    
        @Override
        public void method() {
            System.out.println("Itext接口的抽象方法重写");
        }
        public void haha(){
            System.out.println("Itext1的接口抽象方法重写");
        }
        public void heihei(){
            System.out.println("Itest2的接口抽象方法重写");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    2.3三个重要的接口(重点)

    2.3.1 Clonable接口与深拷贝

    一个对象要能够被克隆,需要先有克隆接口。

    class Student implements Cloneable{
        private String name;
        private int age;
        private double sorce;
        public void eat(){
            System.out.println("吃饭");
        }
        public void study(){
            System.out.println("学习");
        }
        public void sleep(){
            System.out.println("睡觉");
        }
        public Student clone(){
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们可以查看这个接口内容

    在这里插入图片描述

    可以看到这个接口中没有任何内容。Cloneable 也就是作为一个标记接口的作用存在,它的作用是使得对象已被标记,是可克隆对象。

    我们需要重写的是Object的克隆方法,并且抛出处理克隆异常。

    class Dog implements Cloneable{
        public void eat(){
            System.out.println("狗狗吃骨头");
        }
        public Object clone()throws CloneNotSupportedException{
            return super.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    但是如果认为克隆就如此简单,那就错误了,克隆并不是那么聪明,当需要克隆的对象中含有引用成员变量时,则容易出现问题。

    例如:

    class Dog implements Cloneable{
        public void eat(){
            System.out.println("狗狗吃骨头");
        }
        public Object clone()throws CloneNotSupportedException{
            return super.clone();
       }
    }
    class Animal implements Cloneable{
        public int age;
        public Dog dog = new Dog();
        Animal(){
            System.out.println("构造方法");
        }
        public void method(){
            System.out.println("普通方法");
        }
        public Object clone() throws CloneNotSupportedException{
            return super.clone();
        }
    }
    public class Main {
        public static void main(String[] args) throws CloneNotSupportedException {
            Animal animal = new Animal();
            Animal animal1 = (Animal) animal.clone();
            System.out.println(animal.dog);
            System.out.println(animal1.dog);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行结果:

    在这里插入图片描述

    这两个对象中的引用成员变量同时指向了同一个对象,与我们想要克隆的想法大相径庭

    所以我们需要实现深拷贝,深拷贝是在代码层面上完成的

    public Object clone() throws CloneNotSupportedException{
            Animal animal = (Animal) super.clone();
            animal.dog = (Dog) this.dog.clone();
            return animal;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    这样我们地让每一次克隆时,dog这个变量都不会指向同一个对象,达到克隆的目的。

    2.3.2 接口Comparable

    在java中,我们不能直接用对象进行比较,因为java不支持地址比较大小。

    我们想要利用对Array.sort()进行对对象进行排序也显得困难。

    为此引入了comparable接口,来实现对对象之间的比较。

    为什么String成员能进行排序呢?

    查看String的源码:

    在这里插入图片描述

    String类已经实现了comparable接口了。

    comparable接口:

    在这里插入图片描述

    我们在实现了Comparable接口,需要重写compareTo方法来实现比较的功能:

    如果返回值大于0,则对象1>对象2

    如果返回值等于0,则对象1=对象2

    如果返回值小于0,则对象1<对象2

    例如:实现对象之间年龄的比较与排序:

    class Student implements Comparable<Student>{
        private String name;
        private int age;
        private double sorce;
        Student(String name,int age,double sorce){
            this.age = age;
            this.name = name;
            this.sorce = sorce;
        }
    
        @Override
        public int compareTo(Student o) {
            return this.age - o.age;
        }
        public void print1(){
            System.out.println(name+" "+age+" "+sorce);
        }
    }
    public class Main {
        public static void main(String[] args) {
            Student student1 = new Student("jiajia",21,88.5);
            Student student2 = new Student("jiejie",22,88.9);
            if(student1.compareTo(student2)>0){
                System.out.println("yes");
            }else{
                System.out.println("NO");
            }
            Student[] students = new Student[3];
            students[0] = new Student("xiaobai",22,18.5);
            students[1] = new Student("xiaoming",20,18.5);
            students[2] = new Student("xiaoqiang",21,18.5);
            Arrays.sort(students);
            for (int i = 0; i < 3; i++) {
                students[i].print1();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    成功地实现了比较与排序的功能。

    但是它的缺点也十分明显,直接在类中实现Comparable接口并重写compareTo方法,对类的侵入性十分强。

    2.3.3比较器comparator

    为了避免对对象的比较时,对类产生侵略性,所以我们可以采用比较器的方式来实现。

    实现对学生类的年龄比较

    class Agecompare implements Comparator<Student>{
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getAge()-o2.getAge();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    进而直接用比较器实现对象之间的比较与排序。

    class Student{
        private String name;
        private int age;
        private double sorce;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public double getSorce() {
            return sorce;
        }
    
        public void setSorce(double sorce) {
            this.sorce = sorce;
        }
    
        Student(String name, int age, double sorce){
            this.age = age;
            this.name = name;
            this.sorce = sorce;
        }
    
        public void print1(){
            System.out.println(name+" "+age+" "+sorce);
        }
    }
    class Agecompare implements Comparator<Student>{
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getAge()-o2.getAge();
        }
    }
    public class Main {
        public static void main(String[] args) {
            Student student1 = new Student("jiajia",21,88.5);
            Student student2 = new Student("jiejie",22,88.9);
            Agecompare agecompare = new Agecompare();
            if(agecompare.compare(student1,student2)>0){
                System.out.println("yes");
            }else{
                System.out.println("NO");
            }
            Student[] students = new Student[3];
            students[0] = new Student("xiaobai",22,18.5);
            students[1] = new Student("xiaoming",20,18.5);
            students[2] = new Student("xiaoqiang",21,18.5);
            Arrays.sort(students,agecompare);
            for (int i = 0; i < 3; i++) {
                students[i].print1();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    3. 抽象类与接口的区别

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

    核心区别:

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

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

    4. Object类

    Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。进而实现多态也是可以的。

    4.1equals方法

    equals方法是Object类的重要方法,他做到了直接实现对象的比较,例如String类的比较。

    在java当中的比较:(重点)

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

    1. 如果==左右两侧是基本类型变量,比较的是变量中值是否相同

    2. 如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同

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

      查看Object类中的equal源码:

      在这里插入图片描述

    我们如果要做到对象之间进行比较,需要重写equals方法

    如果没有重写:即便两个对象的属性相同,返回的结果也是flase

    // Object类中的toString()方法实现:
        public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
    // Object类中的equals方法
        public boolean equals(Object obj) {
        return (this == obj);   // 使用引用中的地址直接来进行比较
       }
    class Person{
          private String name ; 
           private int age ; 
           public Person(String name, int age) {
           this.age = age ; 
    
    
           this.name = name ;
          }
    }
    public class Test {
         public static void main(String[] args) {
            Person p1 = new Person("gaobo", 20) ; 
            Person p2 = new Person("gaobo", 20) ; 
            int a = 10;
            int b = 10;
            System.out.println(a == b);             // 输出true
            System.out.println(p1 == p2);           // 输出false
            System.out.println(p1.equals(p2));      // 输出false
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    重写equals方法:

    class Person{
           private String name ; 
           private int age ; 
           public Person(String name, int age) {
              this.age = age ; 
              this.name = name ;
          }
           @Override
           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 p1 = new Person("gaobo", 20) ; 
            Person p2 = new Person("gaobo", 20) ; 
            int a = 10;
            int b = 10;
            System.out.println(a == b);             // 输出true
            System.out.println(p1 == p2);           // 输出flase
            System.out.println(p1.equals(p2));      // 输出true
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    总结:

    本章主要注意抽象类与接口的细节语法,以及两者的差别,同时学习三个常用接口。equals方法等

    感谢阅读,一起进步!

  • 相关阅读:
    Java使用redis-Redis是并发安全的吗?
    SpringCloud Alibaba整合Ribbon负载均衡
    Vue-router的动态路由:获取传递的值
    报表控件Stimulsoft Report中的 自定义QR 码教程
    LeetCode39- 组合总和
    自动驾驶汽车下匝道路径优化控制策略研究
    小白福利!教你用低代码实现一个简单的页面跳转功能
    SpringBoot项目中使用MultipartFile来上传文件(包含多文件)
    DPU加速AI应用“遍地开花”,中科驭数亮相2023全球AI芯片峰会
    聊聊Promise的使用
  • 原文地址:https://blog.csdn.net/simple_person/article/details/127642624