夯实Java基础(十)----抽象类和接口
1、本章前言
之前介绍了Java的三大特征有封装、继承、多态,这样说其实我们少讲了一个,那就是抽象性,在平时的教程中认为只有三种特征,因为它们把抽象放到继承里了,认为抽象类是继承的一种,这也使抽象性是否是Java的一大特征具有争议。而Java语言中对抽象概念定义的机制就是抽象类和接口,正由于它们才赋予Java强大的面向对象的功能。他们两者之间对抽象概念的支持有很大的相似,但是也有区别。所以接下来我们就来学习它们两的使用和区别。(如果在平时面试时遇到问Java有几大特征,我们就说封装、继承、多态这三特征即可)。
2、抽象类
在Java面向对象的概念中,我们知道所有的对象都是通过类来描绘的,类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,会占用内存空间。但是有时候并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类是用来描述一种类型应该具备的基本特征与功能,而具体如何去完成这些行为则由其子类通过方法重写来完成。简单举个例子:比如我们创建一个Animal类,然后用这个类来创建一个动物对象,但是我们并不知道这具体是哪一种动物,只知道动物的一些基本特征和行为,比如有吃喝拉撒睡等,此时这个Animal对象是抽象的。所以我们需要一个具体的类来描述该动物,如用狗、猫来对它进行特定的描述,才知道它具体是什么动物。在描述的同时,狗和猫的特征也是不一样的,比如猫喜欢吃鱼,而狗喜欢吃骨头,所以此时不应该在动物类中将这些特征体现出来,而是要在Animal类的子类中给出一个声明即可,也就是重写父类中的方法。
在Java中用abstract
关键字来修饰的类就是抽象类,当然这个关键字也可以用来修饰方法,表明该方法是抽象方法。
『以下是抽象类的一些特点(非常重要!!!)』:
- 抽象类不能实例化,必须要由继承它的子类来创建实例。
- 抽象方法只有方法的声明,没有方法体。抽象类中的抽象方法必须要在子类中重写。
- 抽象类中既可以有抽象方法,也可以有普通方法,普通方法可不用重写。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中可以有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
- 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
- 只要包含一个抽象方法的类,该类必须要定义成抽象类。
- abstract不能用来修饰属性、构造器等结构。
- abstract不能与final并列修饰同一个类。
- abstract不能与private、static、final或native并列修饰同一个方法。
- 抽象类:被
abstract
所修饰的类。 - 抽象方法 :被
abstract
所修饰的方法,它是没有方法体的方法。
①、抽象类的语法格式:
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
②、抽象方法的语法格式:
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
注意:抽象方法没有方法体
③、抽象类的简单示例,代码如下所示:
public abstract class Animal {
//可以有属性
String name;
Integer age;
//抽象方法
public abstract void eat();
//普通方法
public void sleep() {
System.out.println("动物需要睡觉...");
}
//抽象类可以有构造器
public Animal() {
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫喜欢吃鱼...");
}
@Override
public void sleep() {
System.out.println("猫需要睡觉...");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗喜欢吃骨头...");
}
@Override
public void sleep() {
System.out.println("狗需要睡觉...");
}
}
//测试类
class Main{
public static void main(String[] args) {
Animal a1 = new Dog();
a1.eat();
a1.sleep();
Animal a2 = new Cat();
a2.eat();
a2.sleep();
}
}
3、接口
在我们的生活中,也常常会接触到接口这个词,比如我们的电脑边上提供了USB接口插槽,只要其它设备也是遵循USB接口的规范,那么就可以互联,并正常通信。至于这个电脑、以及其他设备是哪个厂家制造的,内部是如何实现的,我们都无需关心。
这种设计是将规范和实现分离,这也正是Java接口的好处。Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面向接口的低耦合设计,为系统提供更好的可扩展性和可维护性。
接口的本质是契约,标准规范 ,其实 准确来说接口定义的就是一种规范。体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的is-a
关系,而接口实现则是 "能不能"的has-a
关系。
- 例如:你能不能用USB接口进行连接,或是否具备USB通信功能,就看你是否遵循USB接口规范
- 例如:Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品有没有实现Java设计的JDBC规范
接口的英文名称是 interface。接口是属于和类类型同等级别的结构,但是它不是类,却和类类型有着共同点,在接口中可以含有变量、方法。接口使用interface
这个关键字来进行修饰。
接口:用interface关键字修饰的类。
接口中的变量:接口中的变量会被隐式地指定为public static final
变量,并且只能是public static final变量,用private修饰会报编译错误。
接口中的方法:接口中的方法会被隐式地指定为public abstract
方法,且只能是public abstract方法,如果用其他关键字,如private、protected、static、 final等修饰都会导致报编译错误。
接口的实现:使用implement
关键字。
也就是说接口中的变量全都是常量,方法都是抽象方法(JDK1.8以前)。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”。
Java8中的接口:在jdk8之前,interface之中可以定义变量和方法,变量必须是public、static、final的,方法必须是public、abstract的。在jdk8及以后,允许我们在接口中定义static方法和default方法。
- default方法:必须用
default
关键字修饰,而且可以被实现类重写,它只能通过接口实现类的对象来调用。 - static方法:只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。
Java9中的接口:jdk9后接口中允许定义private的方法,用于服务于本接口其他方法。
①、接口的声明格式:
【修饰符】 interface 接口名{
//接口的成员
[public static final] 数据类型 变量名;//静态常量,默认修饰符public static final
[public abstract] 返回值类型 方法名(形参列表);//抽象方法,默认自带修饰符public abstract
default 方法名(形参列表){}//jdk8后,缺省方法,扩展方法,默认public修饰,实现类可以自行选择是否实现此方法
static void testStatic(){}//jdk8后,默认public修饰,通常定义服务于此接口的实现类的一些工具类方法
private void testPrivate(){}//jdk9后出现,服务于本接口其他方法。
}
接口的实现格式(接口可以多实现,类只能单继承):
修饰符 class 实现类名 implement 接口{
//重写接口中的方法
public 返回值类型 方法名(形参列表){
方法体
}
}
修饰符 class 实现类名 extends 父类 implements 接口名1,接口名2,...{
//重写接口和抽象类中的抽象方法
}
接口的一些特点(都非常重要!!!):接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
- 接口没有构造方法,不能创建对象。
- 成员变量默认自带修饰符public static final,即为静态常量。
- 抽象方法默认自带修饰符public abstract(jdk8之前版本接口中方法只能是抽象方法)
- 接口是用来被实现的,其实现类必须重写它的所有抽象方法,除非实现类是个抽象类
- 接口可以多实现,一个类可以同时实现多个接口
- 接口可以继承接口,接口之间支持多继承
- 在JDK1.8时,接口中允许声明默认方法和静态方法:
- 公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
- 公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
- 在JDK1.9时,接口又增加了私有方法,用于服务于本接口其他方法
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名的成员变量,那么在调用的时候会报错,模糊不清。
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写该方法的情况下,默认调用父类中的同名同参数的方法。-->类优先原则。
- 如果实现类实现了多个接口(没有继承),而多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,会报错。-->接口冲突。这就需要我们在实现类中重写此方法。
- 如果需要调用父类中方法或接口中默认方法,父类使用 super . 方法名,接口使用 接口名.super.方法名。
这里讲的非常的细,其实我们只需要了解即可。
接口的简单举例,代码如下:
/**
* 定义接口
*/
public interface InterfaceDemo {
//接口成员变量(前面默认加了public static final)
int MAX_VALUES = 1000;
//接口抽象方法(前面默认加了public abstract)
void abstractMethod();
//接口默认方法(必须加default)
default void defaultMethod() {
System.out.println("接口default方法...");
}
//接口静态方法
static void staticMethod() {
System.out.println("接口static方法...");
}
}
//定义接口的实现类
class InterfaceImpl implements InterfaceDemo {
@Override
public void abstractMethod() {
System.out.println("重写接口中的abstract方法...");
}
@Override
public void defaultMethod() {
System.out.println("重写接口中的default方法...");
}
}
//定义测试类
class Main {
public static void main(String[] args) {
InterfaceImpl ifi = new InterfaceImpl();
ifi.abstractMethod();
ifi.defaultMethod();
System.out.println("接口中的常量值:" + InterfaceDemo.MAX_VALUES);
InterfaceDemo.staticMethod();
}
}
运行结果如下所示:
4、抽象类与接口的对比
抽象类和接口的主要区别:
- 从设计层面上:抽象类是对类的抽象,是一种模板设计,所以抽象类常用作当做模板类使用。接口是行为的抽象,是一种行为的规范。
- 从应用层面上:接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用。
特点 | 抽象类 | 接口 |
---|---|---|
继承限制 | 单继承 | 一个类可以实现多个接口,而且接口也可以继承多个接口 |
成员变量 | 有 | 只能是公共的静态的常量【public static final】(不写默认会加上) |
构造器 | 有 | 无 |
代码块 | 可以有 | 无 |
抽象方法 | 可以有 | 只能是公共的抽象方法【public abstract】 |
静态方法 | 可以有 | JDK1.8之后可以有公共的静态方法 |
默认方法 | 可以有 | JDK1.8之后可以有公共的默认方法,必须用default修饰 |
私有方法 | 可以有 | JDK1.9之后可以有私有方法 |
访问修饰符 | 抽象方法可以有public、protected和default | 接口方法默认修饰符是public,不可以使用其它修饰符 |
相同点 | 都不能直接实例化 | 都不能直接实例化 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!