JavaSE语法(10)—— 详细解读Java中的抽象类与接口

1.抽象类


1.1 什么是抽象类?

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

  这概念如何理解?现在我有三个类,如下:

public class Animal {
    public String name;
    public int age;

    public void bark(){
        //......
    }
}

class Cat extends Animal{

    //......

    public void bark(){
        System.out.println("喵喵喵~");
    }
}

class Dog extends Animal{

    //......

    public void bark(){
        System.out.println("汪汪汪~");
    }
}

  这里的CatDog继承了Animal,并且重写了bark()这个方法。但是这里的Animal是动物类,没有一个具体的叫声,所以无法具体实现bark(),索性我们就不实现这个方法了。这种没有实际工作的方法, 我们可以把它设计成一个抽象方法, 包含抽象方法的类我们称为抽象类。

  这里的Animal因为没有包含足够的信息来描绘一个具体的对象,所以我们可以把Animal设计成一个抽象类。


1.2 抽象类的语法

  一个类如果被abstract修饰称为抽象类,抽象类中被abstract修饰的方法称为抽象方法,抽象方法不用给出具体的实现体,就拿上面的例子。

public abstract class Animal {
    public String name;
    public int age;

    //抽象方法:被abstract修饰的方法,没有方法体
    public abstract void bark();
}

class Cat extends Animal{

    //......

    public void bark(){
        System.out.println("喵喵喵~");
    }
}

class Dog extends Animal{

    //......

    public void bark(){
        System.out.println("汪汪汪~");
    }
}

  注意:抽象类也是类,在抽象类中可以增加普通方法、构造方法和属性

public abstract class Animal {
    //普通属性
    public String name;
    public int age;

    //可以有构造方法
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    
    //可以增加普通方法
    public void eat(){
        //.......
    }   
    
    //抽象方法:被abstract修饰的方法,没有方法体
    public abstract void bark();
}

1.3 抽象类的特性

  (1)抽象类不能直接被实例化。

   在抽象方法内部可能存在“没有实现的方法”,故无法被实例化,如果使用new会报错:

abstract class Animal{
    //普通属性
    public String name;
    public int age;

    //可以有构造方法
    public Animal(String name,int age){
        this.name = name;
        this.age = age;
    }

    //可以增加普通方法
    public void eat(){
        System.out.println("我正在进食!");
    }

    //抽象方法:被abstract修饰的方法,没有方法体
    public abstract void bark();
}


public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
    }
}

结果:

请添加图片描述


  (2)抽象类必须要被继承,并且要重写里面的抽象方法。

   如果要使用抽象类,那么必须得继承。

class Cat extends Animal{

    public Cat(){}

    public Cat(String name,int age){
        super(name,age);
    }

    //重写bark方法
    @Override
    public void bark() {
        System.out.println(name + ":喵喵喵~");
    }
}


public class Main {
    public static void main(String[] args) {

        Animal cat = new Cat("小白",6);
        cat.bark();
    }
}

结果:(ps:从下面可以看出抽象类也是可以发生多态的)

请添加图片描述


  (3)抽象类不能被final修饰,因为抽象类必须要被继承。

在这里插入图片描述


  (4)抽象方法不能被finalstatic修饰,并且抽象方法的访问权限要大于private,因为抽象方法要被子类重写。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  (5)抽象类中可以没有抽象方法,但是有抽象方法的类必须是抽象类。



2.接口


2.1 接口是什么?

  在上面的抽象类那介绍过,有的时候类里的方法没有必要实现,因而有了抽象类。但是在抽象类里面,还是有存在其它普通成员的必要,这样就显得十分混乱。

  那有没有一种用来装这些未具体实现的方法(抽象方法)的集合,这种集合就是接口。这里要注意的是在jdk1.8之后,接口中是可以使用非抽象方法的。可以把接口抽象成一种“功能”,当一个类需要某种功能时就可以实现这个接口(案例在后文)。


2.2 接口的语法

  接口与类的定义方式差不多,只是将class换成interface

public interface 接口名称{
    //.......
}

  那接口是怎么使用的呢?类与类的关系可以是"继承",那么类与接口之间的关系是"实现"(某个类实现了某个接口)。

  继承关系中用extends,实现接口就用implements其形式👇。

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

2.3 接口的特性

  (1)接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(也就是什么修饰符都不写的情况下)。

public interface Test{

    //抽象方法1
    public abstract void method1();

    //抽象方法2 public被省略了
    abstract void method2();

    //抽象方法3 abstract被省略了
    public void method3();

    //抽象方法4   public abstract都被省略了
    void method4();
}

  注意这里只能是 public abstract,其他修饰符都会报错,为什么会报错?我认为:接口是来服务类的,要保证任何位置的类都能实现这个接口。

在这里插入图片描述


  (2)任何非抽象类实现接口的时候必须要重写接口中的所有的抽象方法。 这跟抽象类是一样的。


  (3)接口不能被实例化。

在这里插入图片描述


  (4)接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量,并且必须要初始化不然报错。

public interface Test{

    //public final static修饰
    public final static int a = 1;

    //默认的情况: public final static被省略
    int b = 2;
    
    //抽象方法1
    public abstract void method1();

    //抽象方法2 public被省略了
    abstract void method2();

    //抽象方法3 abstract被省略了
    public void method3();

    //抽象方法4   public abstract都被省略了(推荐这种写法,简洁)
    void method4();
}

  (5)接口中不能使用代码块、构造方法等初始化程序。

在这里插入图片描述


  (6)接口中能使用非抽象方法的情况。

   ①JDK 1.8 及以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。

   ②JDK 1.8 及以后,接口里可以有静态方法,用static修饰而没有用abstract修饰的方法可以实现方法体。

   ③JDK 1.9 及以后,允许将方法定义为 private,就是用private修饰而没有用abstract修饰的方法可以实现方法体。

interface Test{

    //default方法
     public default void method5(){
         System.out.println("我是用 default 修饰的方法!!!");
    }

    //静态方法
    public static void method6(){
        System.out.println("我是静态方法!!!");
    }

    //我是 private 方法
    private void method7(){
        System.out.println("我是用 private 修饰的方法!!!");
    }
}

2.4 接口回调

  接口回调就是相当于继承中的多态,不能说是相似,只能说是一模一样。

interface Inter{
    public abstract void A();
}

class Test implements Inter{
    
    //重写A()方法
    public void A(){
        System.out.println("我是Test类中的A()方法!!!");
    }
    
    //......
}


public class Main2{

    public static void main(String[] args) {
        
        //接口回调,与多态类似
        Inter test = new Test();
        test.A();
    }
}

结果:

在这里插入图片描述

  但是这时访问不了Test自己的成员,只能访问Inter中的方法,这跟继承中向上转型的用法是一样的;那如果换成是Test test = new Test()那么就可以访问全部的成员。


2.5 一个类实现多个接口

  在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。

  语法格式:

class 类名 implements 接口1,接口2,……{

    //......

}

  案例:

class Animal2{

    public String name;

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


//会飞的
interface IFlying {
    void fly();
}

//会跑的
interface IRunning {
    void run();
}

//会游泳的
interface ISwimming {
    void swim();
}




//狗不仅会跑,还会游泳
class Dog extends Animal2 implements IRunning,ISwimming{
    
    public Dog(String name){
        super(name);
    }

    //重写run()
    public void run() {
        System.out.println(this.name + "正在往前跑");
    }

    //重写swim()
    public void swim() {
        System.out.println(this.name + "正在四驱蹬腿游泳");
    }
}

//鸭子不仅会跑还会游泳,甚至会飞。
class Duck extends Animal2 implements IRunning,ISwimming,IFlying{

    public Duck(String name){
        super(name);
    }

    //重写run()
    public void run() {
        System.out.println(this.name + "正在往前跑");
    }

    //重写swim()
    public void swim() {
        System.out.println(this.name + "正在双驱蹬腿游泳");
    }

    //重写fly()
    public void fly() {
        System.out.println(this.name + "正在飞翔");
    }
}



public class Main3 {
    public static void walk(IRunning running){
        running.run();
    }

    public static void main(String[] args) {
        Dog dog = new Dog("大黄狗");
        walk(dog);

        Duck duck = new Duck("小黄鸭");
        walk(duck);
    }
}

结果:
大黄狗正在往前跑
小黄鸭正在往前跑

  这里就很好的呈现了把接口想成某一种“功能”的思想。比如动物中的狗有跑、游泳的能力,那么就让Dog类实现这两个接口,使Dog类拥有这两个“功能”。


2.6 接口间的继承

  接口也可以像类那样进行接口与接口之间的继承,甚至接口与接口之间可以多继承。

//会飞的
interface IFlying {
    void fly();
}

//会跑的
interface IRunning {
    void run();
}

//会游泳的
interface ISwimming {
    void swim();
}

interface IAnthropic extends IFlying,IRunning,ISwimming{
    //......
}

  当某一个类实现IAnthropic接口的时候就需要重写前面三个接口的所有抽象方法。


2.7 使用接口的案例:给对象数组排序(Comparable接口)

  先实现一个学生类,并对一个学生数组排序。

class Student {
    private String name;
    private int score;

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


public class Main4 {
    public static void main(String[] args) {
        
        Student student1 = new Student("张三",58);
        Student student2 = new Student("李四",100);
        Student student3 = new Student("王五",80);
        Student student4 = new Student("小红",60);

        Student[] students = {student1,student2,student3,student4};
    }
}

  我们如何对students数组进行排序呢?我们可以想起普通的数组排序使用的是Arrays.sort(),如果用在这里是否可以?

在这里插入图片描述

  可以发现报错了,我们看看这个错误是什么意思:“不能将Student转换为java.lang.Comparable类”。这是什么意思?在回答这个问题前,先看看这个“Comparable”是什么。

  我们点进源码👇:

在这里插入图片描述

  可以看到Comparable是一个接口,并且只有compareTo()这一个抽象方法。这个接口翻译过来就是“可比较的”意思,我们想要对Student排序前提是Student得有“可比较的”的能力,显然Student是没有这个能力的,那么就需要实现这个接口。

  现在回归问题“不能将Student转换为java.lang.Comparable类”。

  ①我们的Student类为什么要转换为Comparable类型?

  答:这里有“转换”这个词,什么情况下才会涉及到转换?那就是向上转型或者接口回调的情况下才会涉及到转换;

  那么为什么要进行接口回调?回想前文的接口回调,就是用来调用自己内部的方法,也就是说Arrays.sort()源码里面会将Student类转换为Comparable来调用compareTo()方法,之所以要转换是因为在Arrays.sort()源码的视角来看我不知道外面传进来的是什么具体的类型,有可能是“学生”、“老师”等类型,这时候就需要一个固定且已知的类型,很显然这个类型就是Comparable

在这里插入图片描述

  那么这里为什么要调用compareTo()?因为普通类型之间的比较大小就是数学上的那种形式,而像Student这样的对象之间比较大小就要一个规定的规则compareTo()就是你自己定的规则。

  ②我们的Student类为什么会转换失败?

  答:因为Student类没有实现接口Comparable,所有的引用类型之间的转换都要依仗于“继承”或者“实现某个接口”。换言之,向上转型或者接口回调必须得有“父类”或者“实现了接口”才能实现。


  Comparable使用:

class Student implements Comparable {
    private String name;
    private int score;

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

    //重写Object中的toString()的方法,方便打印。
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }

    //重写compareTo()方法,我们自己定一个比较规则,我们定:用分数来比较学生的大小。
    public int compareTo(Object o) {

        Student s = (Student) o;
        return this.score - s.score;
    }
}



public class Main4 {
    public static void main(String[] args) {

        Student student1 = new Student("张三",58);
        Student student2 = new Student("李四",100);
        Student student3 = new Student("王五",80);
        Student student4 = new Student("小红",60);

        //student1与student2比较大小
        int a = student1.compareTo(student2);
        System.out.println(a);

        //student2与student3比较大小
        int b = student2.compareTo(student3);
        System.out.println(b);
    }
}

结果:

-42
20

  compareTo()的返回值是一个负整数、零或正整数这三种情况之一。返回正整数时谁大谁小是根据你定的规则来的,我这里为this.score - s.score

  回到正题,熟悉了compareTo()后,我们来进行排序。

public class Main4 {
    public static void main(String[] args) {

        Student student1 = new Student("张三",58);
        Student student2 = new Student("李四",100);
        Student student3 = new Student("王五",80);
        Student student4 = new Student("小红",60);
        
        Student[] students = {student1,student2,student3,student4};
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

结果:

在这里插入图片描述

  这是升序,降序怎么搞?把compareTo()中的this.score - s.score改为s.score-this.score就行了,以下是改了之后的结果:

在这里插入图片描述

  为什么会这样呢?我就通俗易懂地来说吧:假如这时候比较到this与this的后一个元素s了。你可以认为在Arrays.sort()compareTo()返回值为正整数时(即this大于s),会交换this与s(Arrays.sort()默认是降序)。

  在Arrays.sort()的视角,返回值为正整数时它会认为this大于s,然后交换。如果我们将this.score - s.score改为s.score-this.score,(this大于s)这时compareTo()返回的就是负数,sort()会认为this比s小所以就不交换了,然而实际上this是比s大的,大的放在后面不就是降序了嘛。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值