【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实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2