【Java SE】抽象类和接口 保姆级细致教学,深入理解

抽象类

抽象类的概念

什么是抽象类呢? 嗷就是这个类它很抽象,结束!(bushi

当一个类没有足够的信息来描绘它的对象时,也就是不能实例化出具体的对象时,我们称之为抽象类。

拿我们前面用过很多遍的Animal类来举例,我们能实例化出一个Animal对象吗? 动物是个很抽象的概念,它可以是猫,可以是狗,可以是鸟等等,所以并不能实例化出一个动物对象。同样,我们在Animal类中实现了 eat() 方法,不同的动物有各自不同的 eat() 方法, 那么,我们的Animal类的 eat() 方法就没有具体实现了,因为它只能在具体的子类中才能有实现。所以我们把他定义为抽象方法,不具体实现,这个类也成了抽象类。

//将Animal实现为抽象类
abstract class Animal {
    String name;
    int age;

    public abstract void eat();
}

class Cat extends Animal {
    String hair;
    public void eat() {
        System.out.println(this.name + "吃猫粮!");
    }
}

class Dog extends Animal {
    String breed;
    public void eat() {
        System.out.println(this.name + "吃狗粮!");
    }
}

抽象类的语法

如上面代码所示,被 abstract 修饰的类被称为抽象类, 类中被abstract修饰的成员方法称为成员方法被称为抽象方法

//抽象类
abstract class Animal {
	//成员变量
    String name;
    int age;
	//抽象方法
    public abstract void eat();
    //普通方法
    public void setName(String name) {
    	this.name = name;
   	}
}

抽象类的特性

1. 抽象类不能直接实例化对象,必须被继承,且子类必须重写父类的抽象方法,否则,子类也只能是抽象类,也要用abstract修饰。

Animal animal = new Animal();
//编译出错

在这里插入图片描述
按住 Alt + Enter 重写eat方法,就可以了~
在这里插入图片描述
这样我们就可以实例化Cat对象了!

  1. 抽象方法不能被final ,static ,private修饰,因为抽象方法必须在子类中被重写,它的使命就是被重写,被这些修饰符修饰的方法不能在子类中重写。

当抽象方法不加访问限定符时,它默认时 public 修饰的

  1. 抽象类中可以没有抽象方法,但是有抽象方法的类必须定义为抽象类。
  2. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量。

抽象类的作用

我们知道普通类也可以被继承,普通方法也可以被重写进而发生多态,那为什么还需要抽象类呢?

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

因为抽象类是不能实例化对象的,当我们不小心实例化了抽象的父类的对象,编译器会报错,而如果我们实例化的是普通类父类,编译器不会报错!
类似于final修饰的变量,不允许用户去修改,在不小心修改时编译器帮我们来报错提醒。 所以,使用抽象类可以预防出错


接口

接口的概念

什么是接口? 我们听过USB接口吧,可以插U盘,鼠标,键盘等等,只要插头符合USB协议,就都能使用USB接口。

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

接口的语法

接口的语法跟类定义类似,只需要把class换成interface即可。

我们来用IDEA创建一个接口:
在这里插入图片描述
这次选择Interface,输入接口名称,回车!
在这里插入图片描述

public interface 接口名称 {
	void method();
	//这两种是一样的,更推荐上面这种,更加简洁
	public abstract void method();
}

在这里插入图片描述

Tips:
1.创建接口时, 接口的命名一般以大写字母 “I ”开头。
2.接口的命名一般使用 “形容词” 词性的单词。比如我们常见的Comparable


接口的使用规则

  1. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性,其实接口中的方法不写修饰符是默认public abstract修饰的,但如果你改成其他的,比如private就会报错。同时因为是默认public abstract修饰的,所以方法不能有具体实现。
//报错! 不能用private修饰
private interface IMyInterface {
	
}

public interface IMyInterface {
	void method() {

	}
	//编译器报错,抽象方法不能有具体实现
}

  1. 接口类型也是一种引用类型,但是不能直接new接口的对象,不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。 值得关注的是,接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
public interface IMyInterface {
    void method();
    public abstract void method2();
}

public class Test {
    public static void main(String[] args) {
		IMyInterface myInterface = new IMyInterface();
		//报错,不能实例化接口对象
    }
}

正确使用方法是:定义一个类来实现接口,使用implement关键字


class 类名 implements 接口名 {
    //在类中必须重写接口的所有方法
}

在这里插入图片描述
现在编译器在报错,那是因为没有重写接口方法,我们现在按下Alt+Enter,选择 implement method 实现方法,回车。
在这里插入图片描述
选择所有的方法,点击OK, 完成!
在这里插入图片描述


  1. 接口中不能有静态方法和构造方法。这也是接口不能直接new对象的一个原因,因为它没有构造方法。接口只是一种规范标准。
public interface IMyInterface {
    void method();
    static void method();
    //报错,静态方法不能被重写
    IMyInterface() {
    	
    }
    //报错,不能有构造方法
    
}

  1. 重写接口中方法时,不能使用default访问权限修饰 ,也就是不能不写修饰符,那该写什么呢? 答案是必须用public修饰。

在这里插入图片描述
分析一下,为什么? 还记得我们在多态里说重写的时候吗?我们说重写方法的访问权限必须大于等于父类的方法,既然接口里的方法都是默认public修饰的,那么重写方法只能是public修饰了。

那有人就要说了,那不是子类重写父类方法的规则吗?还能用到接口吗?
我们发现在编译后,接口也会生成.class文件,这跟类是类似的,而实现类implements实现接口也类似于子类extends继承父类,虽然不是一个东西不能相提并论,但还是有相似点的,咱们对照理解。


  1. 接口中可以含有变量,但是接口中的变量会被隐式的指定为public static final修饰。即接口中的变量是不能被修改的,得看作常量。
  2. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类。

实现多个接口

在Java中,类和类之间是单继承的,一个类只能继承一个父类,不能多继承。但是接口与接口之间却可以做到多继承,一个接口可以继承多个接口,一个类也能实现多个接口。

下面我们同样通过Animal类来理解多接口和多继承。

因为我们说动物类不能具体实例化,他的eat方法在每个子类的实现不同,所以我们把他写成抽象方法,自然Animal类也就成了抽象类。

abstract class Animal {
    //成员变量
    String name;
    int age;
    //抽象方法
    public abstract void eat();
    //普通方法
    private void setName(String name) {
        this.name = name;
    }
}

然后,提供几个接口,我们知道动物有“会跑的”,“会飞的”,“会游泳的”,所以我们分别提供这三个接口,让具有这种特性的子类来实现它们

public interface IRunning {
    void run();
}

public interface IFlying {
    void fly();
}

public interface ISwimming {
    void swim();
}

猫猫会跑,鱼会游泳,鸟会飞也会跑,鸭子既能跑又会游泳。我们说猫,鱼,鸟,鸭子都继承于Animal,但是,猫具有会跑的特性,鱼具有会游泳的特性,鸟具有会跑和会飞两种特性,鸭子具有会跑和会游泳两种特性。

类和类之间我们说继承,类和接口直接我们说类具有接口的特性或者类实现了接口的功能


class Cat extends Animal implements IRunning {
	//重写接口方法
    @Override
    public void run() {
        System.out.println(this.name + "正在朝你奔来!");
    }
	//重写父类方法
    @Override
    public void eat() {
        System.out.println(this.name + " 喵喵!");
    }

}

class Fish extends Animal implements ISwimming {
	//重写接口方法
    @Override
    public void swim() {
        System.out.println(this.name + "正在游向你!");
    }
	//重写父类方法
    @Override
    public void eat() {
        System.out.println(this.name + "在吃虾米!");
    }
}

class Bird extends Animal implements IRunning, IFlying {
	//重写接口方法
    @Override
    public void fly() {
        System.out.println(this.name + "正在扑棱扑棱!");
    }
	//重写接口方法
    @Override
    public void run() {
        System.out.println(this.name + "正在蹦蹦跳跳跑!");
    }
	//重写父类方法
    @Override
    public void eat() {
        System.out.println("早起的鸟儿有虫吃!");
    }
}

接口之间的继承,多继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的
接口可以继承一个接口, 达到复用的效果. 使用extends关键字。

举个例子,两栖类动物既会游泳又会跑,我们实现一个两栖类(Amphibious)的接口,这个接口需要同时具备Swimming和Running的特性,所以它继承这两个接口。

interface IAmphibious extends ISwimming, IRunning {
    void climb();
}

注意接口继承是不需要重写接口里的方法的,都在实现接口的类里进行重写接口继承就是缝合怪,把多个接口拼接组装在一起,方便使用。


接口使用实例-数组排序

Comparable接口的使用

如果我们相对一个整形数组排序,有很多方法,什么冒泡排序选择排序,在Java中对基本数据类型排序我们有Arrays.sort()方法,如图:
在这里插入图片描述
我们知道基本数据类型的数组都是通过比较大小来排序的,那如果,对一个引用类的数组,数组里存的是对象,该这么排序呢?还能不能比较大小?

class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    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;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan", 19);
        Student student2 = new Student("lisi", 18);
        Student student3 = new Student("wangwu", 20);
        Student[] students = {student1, student2, student3};
        Arrays.sort(students);
        //报错!!
        System.out.println(Arrays.toString(students));
    }
}

在这里插入图片描述

我们的解决办法是,让Student类实现Comparable接口,并重写它的compareTo()方法,通过调用compareTo来比较两个对象。
在这里插入图片描述
这里Comparable接口后面的<T>是泛型标志,后面介绍,这里理解为占位符,谁实现它就传谁的类型,这里我们写Student,并重写compareTo方法。
在这里插入图片描述
这里compareTo方法中只返回了0,我们还需要根据需求添加,因为compareTo方法如果调用者大于传入的参数对象,则返回大于0的数,相等返回0,小于则返回小于0的数

如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0;

假如我们通过年龄来比较:

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

在这里插入图片描述
students数组按年龄排好序了,有个疑问,我们只是写了compareTo方法,为什么就能排序了呢?

对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力,通过重写 compareTo 方法的方式, 就可以定义比较规则,在 sort 方法中会自动调用 compareTo 方法,通过比较大小来排序。参考冒泡选择排序。

不过,这样写其实不太好,因为,这样写的话,我以后调用sort只能按照年龄大小来排序了,如果我们想按照姓名来排序,或者按照成绩来排序该怎么办??? 所以这样写的缺点是:对类的侵入性太大,不建议这样写!


Comparator 接口的使用

好的写法是,自己另外实现比较器类,实现Comparator接口,重写compare方法。在sort时调用两个参数的需要传比较器的sort方法!

//年龄比较器
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

在这里插入图片描述
注意我在这里通过年龄比较,name是String类型,调用了String的compareTo方法。String的compareTo方法如图,了解即可:
在这里插入图片描述
我们要调用的sort方法如图:
在这里插入图片描述

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan", 19);
        Student student2 = new Student("lisi", 18);
        Student student3 = new Student("wangwu", 20);
        Student[] students = {student1, student2, student3};
        System.out.println("排序前");
        System.out.println(Arrays.toString(students));
        //实例化比较器对象
        AgeComparator ageComparator = new AgeComparator();
        //调用需要比较器的sort方法
        Arrays.sort(students, ageComparator);
        System.out.println("排序后");
        System.out.println(Arrays.toString(students));
    }
}

运行结果:
在这里插入图片描述

这样,我们就不用在需要不同排序规则时对Student类中的compareTo方法改来改去,降低了对类的侵略性! 例如现在如果Student类中新加属性score成绩,我们需要按成绩排序,只需要实现类,类中实现Comparator接口,按成绩来比较即可。


equals的使用

equals我们在字符串中经常使用,用它来判断两个字符串内容是否相同。如图:
在这里插入图片描述
这个equals是String类中自带的,源码如下:
在这里插入图片描述

而要用equals来判断两个对象属性是否相同,能直接用吗?来试试。
在这里插入图片描述

我们的两个对象内容完全一样,却打印了false,为什么?坏了?我们来看看这个equals,点进去!
在这里插入图片描述

原因是这里的equals是继承自Object类(所有类的父类)的,Student本身没有这个方法,它返回的是直接判断两个引用是否相等,即判断地址是否相等,两个对象地址当然不同,永远都是false

如果要用equals,就需要重写equals方法。
我们鼠标点击右键,找到万能的Generate,选择equals and hashcode,回车,然后一路next,就好了。
在这里插入图片描述
现在比较就可以了:
在这里插入图片描述


equals与compareTo的区别

这两个都能用来比较两个对象,但是equals用来判断两个对象内容或者属性是否都相等,而compareTo是通过对象的某个属性进行比较,可以判断是否相等,也可以判断大小


抽象类和接口的异同

类似点

  1. 编译后都会生成.class字节码文件
  2. 都能将子类对象通过向上转型由抽象类引用或者接口引用。
  3. 都需要具体类来重写方法。

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


码字不易,点个赞再走吧,收藏不迷路,持续更新中~

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值