Java—爆肝1W字❤只为弄懂 抽象类和接口

Posted 一朵花花

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java—爆肝1W字❤只为弄懂 抽象类和接口相关的知识,希望对你有一定的参考价值。

抽象类

定义和语法

包含抽象方法的类,叫做抽象类 需要用abstract修饰这个类

在Java中,一个类如果被 abstract 修饰类称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法可以没有具体的实现

抽象类中可以包含其他非抽象方法,也可以包含字段,非抽象方法和普通方法的规则是一样的,可以被重写,也可以被子类直接调用

//抽象类
abstract class Shape2{
	// 抽象方法
    public abstract void draw();
    //抽象类可以增加属性
    public int a;
    // 抽象类可以增加方法
    public void func(){
    }
}

理解抽象类

1.不能实例化对象

/*
abstract class Shape2{
  public abstract void draw();
}
*/

Shape2 shape = new Shape();
编译出错
error:shape 是抽象的,无法实例化

2.抽象方法不能为 private的

抽象方法没有加访问限定符时,默认是public

abstract class Shape2{
   private abstract void draw();
}
编译出错
error:非法的修饰限定符组合:private + abstract

3.抽象方法不能被 fina l和 static 修饰

因为抽象方法要被子类重写

abstract class Shape {
	abstract final void methodA();
	abstract public static void methodB();
}
   
编译出错
error:非法的修饰限定符组合:final + abstract
      非法的修饰限定符组合:static + abstract

4.抽象类必须被继承

且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,就要使用 abstract修饰

abstract class Shape2{
   //如果一个方法没有具体实现,那么这个方法可以是一个抽象方法
   public abstract void draw();
   //一个方法是抽象方法,那么存放它的类一定要声明成 抽象类
}
class Cycle extends Shape2{
   @Override
   public void draw() {
       System.out.println("画一个⚪");
  }
}
//如果一个类继承了抽象类,那么该类一定要实现抽象类里的方法
class Triangle extends Shape2{
   @Override
   public void draw() {
       System.out.println("画一个▲");
   }
}
class Flower extends Shape2{
   @Override
   public void draw() {
       System.out.println("画一个❀");
   }
}

作用

抽象类存在最大的意义:就是为了被继承
抽象类本身不能被实例化,要想使用,必须创建该抽象类的子类,然后让子类重写抽象类中的抽象方法

抽象类总结:

  1. 抽象类不可以被实例化,即 不能:Shape2 shape = new Shape();
  2. 类内的数据成员和普通类没有区别,即:抽象类内部可以包含普通方法和属性,甚至构造方法
    唯一的就是其成员不能被实例化
  3. 抽象类生来就是被继承的
  4. 若一个类继承了抽象类,那么该类必须重写抽象类当中的抽象方法
  5. 抽象类 / 抽象方法一定不能被 final 修饰
  6. 一个方法是抽象方法,那么存放它的类一定要声明成 抽象类
    抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  7. 当抽象类A 继承抽象类B,A可以不重写B的方法,但一旦A再被继承,那么一定还要重写抽象方法
  8. 抽象方法一定不能被 final 和 static 修饰

接口

概念

在现实生活中,接口的例子很多,比如电脑的USB接口,电源插座…

电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备

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

接口是抽象类的更进一步,抽象类中还可以包含非抽象方法,和字段,而接口中包含的方法都是抽象方法,字段只能包含静态常量

接口特性

1.接口当中的方法,都是抽象方法
2.接口中可以有具体实现的方法,需要用default修饰,JDK1.8 加入的

.

3.接口中定义的成员变量,默认是常量

.

4.接口是不可以用来实例化的

.

5.接口和类的关系:implements

一旦一个类实现了接口,那么一定要重写接口当中的方法
.

6.可解决Java单继承问题

.

7.可发生向上转型

.

前提是:把一个对象赋值给接口类型之前,一定要保证这个类实现了这个接口
例如下边代码:Cycle实现了Shape,故可以发生向上转型
.
接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
.

注意事项:

  • 使用 interface 定义一个接口
  • 接口中的方法一定是抽象方法,故 abstract 可以省略
    接口中的方法一定格式 public,故 public 可以省略
  • 类使用 implements 继承接口,此处的含义是"实现"
  • 接口不能被实例化
  • 只要这个类实现了该接口,那么就可发生向上转型
  • 一旦一个类实现了接口,那么一定要重写接口当中的方法
    接口中的方法是不能在接口中实现的,只能由实现接口的类来实现

实现多个接口

有时,我们需要一个类同时继承多个父类,在有些编程语言中可通过多继承方式来实现,但Java中,只支持"单继承"一个类只能 extends 一个父类,但可同时实现多个接口,也能达到类似多继承的效果

Java 面向对象编程中最常见的用法:一个类继承一个父类,同时实现多种接口

代码理解:

class Animal{
    protected String name;
    //提供构造方法
    public Animal(String name){
        this.name = name;
    }
}
//定义接口
interface IFlying{
    void fly();
}
interface IRunning{
    void run();
}
interface ISwimming{
    void swim();
}
//猫
// 先 extends 后 implements
class Cat extends Animal implements IRunning {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}
//鱼
class Fish extends Animal implements ISwimming {
    public Fish(String name) {
        super(name);
    }
    @Override
    public void swim() {
        System.out.println(this.name + "正在用尾巴游泳");
    }
}
//青蛙
class Frog extends Animal implements IRunning, ISwimming {
    public Frog(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在往前跳");
    }
    @Override
    public void swim() {
        System.out.println(this.name + "正在蹬腿游泳");
    }
}
//鸭子
class Duck extends Animal implements IRunning, ISwimming,IFlying{
    public Duck(String name){
        super(name);
    }
    @Override
    public void fly() {
        System.out.println(this.name + "正在飞飞飞");
    }
    @Override
    public void run() {
        System.out.println(this.name + "正在跑啊跑");
    }
    @Override
    public void swim() {
        System.out.println(this.name + "正在游啊泳");
    }
}

猫🐱:具有跑能力
鱼🐟:具有游泳能力
青蛙🐸:具有跑、游泳能力
鸭子🦆:具有飞、跑、游泳能力
.
可见,把 run,swim,fly 写在Animal里是不合适的,因为不是每一种动物都具备这些能力

接口主要是对方法的一个抽象,使人时刻牢记多态的好处,让程序猿忘记类型,有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力

再看一个例子:

//机器人
class Robot implements IRunning{
    @Override
    public void run() {
        System.out.println("我是机器人,看谁跑得快!");
    }
}

public class InterfaceDemo2 {
    public static void walk(IRunning running) {
        System.out.println("dudududdududu");
        running.run();
    }
    public static void main(String[] args) {
        IRunning iRunning = new Robot();
        //iRunning.run();
        walk(iRunning);
    }
}

输出结果:


在上述 walk 方法内部,我们并不关注到底是哪种动物,只要参数是会跑的就可以,甚至参数可以不是 “动物”,只要会跑即可

接口使用实例

给一个数组排序,直接使用Array.sort即可

public static void main(String[] args) {
   int[] array = {8,6,2,3,4};
   Arrays.sort(array);
   System.out.println(Arrays.toString(array));
}

假设给定一个学生类 Student

class Student {
    public String name;
    public int age;
    public int score;

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

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

再创建一个学生数组,对这个数组中的元素进行排序

public static void main(String[] args) {
    Student student1 = new Student("花",18,100);
    Student student2 = new Student("A",20,95);
    Student student3 = new Student("B",23,88);
    
    Student[] students = new Student[3];
    students[0] = student1;
    students[1] = student2;
    students[2] = student3;
    Arrays.sort(students);
    System.out.println(Arrays.toString(students));
}

思考:如果使用 Array.sort 方法能否成功对 student 数组排序?

显然,是不可以的 ,类型转换异常


普通数组,是可以使用 sort 方法进行比较数组成员的,但上述比较的是两个学生对象,就不能直接进行比较了,需要我们实现 Comparable 接口,并实现其中的 compareTo 方法

鼠标放在 implements 后按 Alt + Enter


选择 Implement methods


选定之后,会出现如下代码,只需自己重新实现即可


以年龄比较为例:

class Student implements Comparable<Student> {
    public String name;
    public int age;
    public int score;
    
    public Student(String name,int age,int score){
        this.name = name;
        this.age = age;
        this.score = score;
    }
    
    @Override
    public int compareTo(Student o) {
        if (this.age > o.age){
            return 1;
        }
        else if(this.age < o.age){
            return -1;
        }
        else{
            return 0;
        }
    }
}
public static void main(String[] args) {
    Student student1 = new Student("花",18,100);
    Student student2 = new Student("A",20,95);
    Student student3 = new Student("B",17,88);
    if(student1.compareTo(student2) < 0) {
        System.out.println("student1的年龄 < student2的年龄");
    }
}

这样便可以根据年龄对 Student 进行比较了

此时,再加上 sort 方法,也可实现

public static void main(String[] args) {
    Student student1 = new Student("花",18,100);
    Student student2 = new Student("A",20,95);
    Student student3 = new Student("B",17,88);

    Student[] students = new Student[3];
    students[0] = student1;
    students[1] = student2;
    students[2] = student3;
    Arrays.sort(students);
    System.out.println(Arrays.toString(students));
}

会发现输出结果,是以年龄由小到大排列的


在 sort 方法中会自动调用 compareTo 方法,compareTo 的参数是 Object,其实传入的就是 Student 类型的对象
自定义类型要可比较,必须实现 comparable 或 comparator 接口

Clonable 接口和深拷贝

Clonable 是 Java 内置的一个接口
Object 类中存在一个 clone 方法,调用这个方法可以创建一个对象的 “拷贝”,但是要想合法调用 clone 方法,必须要先实现 Clonable 接口,否则就会抛出 CloneNotSupportedException 异常

数组的拷贝:

public static void main(String[] args) {
   	int[] array = {8,5,2,7,4,6,9};
   	int[] array2 = array.clone();
    array2[0] = 66;
    System.out.println(Arrays.toString(array));
    System.out.println(Arrays.toString(array2));
}

由输出结果,可得,上述拷贝为深拷贝

理解浅拷贝:
两个引用同时指向一个对象
通过array2[0] ,把person里的某一个数据修改了,那么array[0]去访问这个数据时,也被修改了!


要想达到深拷贝,不仅要克隆数组本身,还要把对象也克隆一份


举例:


而上述的 array 可使用 clone 方法,由于 array 的父类是 object,即相当于 array 是默认继承于 object,故 array就有clone方法
当前的person也是默认继承于object,但 person 是自定义类型,不能直接使用 clone 方法

若想克隆自定义类型:

1.实现接口 Cloneable

class Person implements Cloneable {
    public int age;
}

但会发现,Cloneable 是一个空接口


空接口 也叫做:标记接口
含义:只要一个类实现了这个接口,那么就标记这个类是可以进行clone的

2.重写 clone 方法,默认的即可

class Person implements Cloneable {
    public int age;

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

此时person就拥有了clone方法

选中之后会有报错,鼠标放在clone后,按Alt + Enter,选异常(此处随便选一个,后面会详细讲解异常)


再次报错!!!
分析:clone 方法的返回值是 object,因此需要进行强制转换


此时就可以验证深拷贝了:

public static void main(String[] args) throws CloneNotSupportedException {
    Person person1 = new Person();
    Person person2 = (Person) person1.clone();
    System.out.println(person1.age);
    System.out.println(person2.age);
    System.out.println("=======修改========");
    person2.age = 52;
    System.out.println(person1.age);
    System.out.println(person2.age);
}

输出结果:


可以发现,拷贝后,没有发生改变,考虑 age是简单类型的原因,

再新写一个 Money类
Cloneable 拷贝出的对象是一份 “浅拷贝”

class Money{
    double money = 13.14;
}
class Person implements Cloneable {
    public int age;
    Money m = new Money();

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


验证浅拷贝:

public static void main(String[] args) throws CloneNotSupportedException Java 基础语法爆肝两万字解析 Java 的多态抽象类和接口

Java抽象类和接口4000+字深度剖析

抽象的基础类和接口

Java抽象类和接口

java 抽象类和接口的差别

Java学习笔记—抽象类和接口