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

2023-12-21 20:20

本文主要是介绍【Java SE】抽象类和接口 保姆级细致教学,深入理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 抽象类
    • 抽象类的概念
    • 抽象类的语法
    • 抽象类的特性
    • 抽象类的作用
  • 接口
    • 接口的概念
    • 接口的语法
    • 接口的使用规则
    • 实现多个接口
    • 接口之间的继承,多继承
    • 接口使用实例-数组排序
      • Comparable接口的使用
      • Comparator 接口的使用
      • equals的使用
      • equals与compareTo的区别
  • 抽象类和接口的异同

抽象类

抽象类的概念

什么是抽象类呢? 嗷就是这个类它很抽象,结束!(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 {//重写接口方法@Overridepublic void run() {System.out.println(this.name + "正在朝你奔来!");}//重写父类方法@Overridepublic void eat() {System.out.println(this.name + " 喵喵!");}}class Fish extends Animal implements ISwimming {//重写接口方法@Overridepublic void swim() {System.out.println(this.name + "正在游向你!");}//重写父类方法@Overridepublic void eat() {System.out.println(this.name + "在吃虾米!");}
}class Bird extends Animal implements IRunning, IFlying {//重写接口方法@Overridepublic void fly() {System.out.println(this.name + "正在扑棱扑棱!");}//重写接口方法@Overridepublic void run() {System.out.println(this.name + "正在蹦蹦跳跳跑!");}//重写父类方法@Overridepublic 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;}@Overridepublic 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;

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

    @Overridepublic 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> {@Overridepublic 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. 都需要具体类来重写方法。

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


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

这篇关于【Java SE】抽象类和接口 保姆级细致教学,深入理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/521365

相关文章

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

Java Predicate接口定义详解

《JavaPredicate接口定义详解》Predicate是Java中的一个函数式接口,它代表一个判断逻辑,接收一个输入参数,返回一个布尔值,:本文主要介绍JavaPredicate接口的定义... 目录Java Predicate接口Java lamda表达式 Predicate<T>、BiFuncti

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与