Java中的抽象类和接口

使用工具IntelliJ IDEA Community Edition 2023.1.4

使用语言Java8/JDK1.8

目录

1.抽象类

1.1 抽象类和抽象方法

1.1.1 为什么需要抽象类

1.1.2 抽象类和抽象方法

1.1.2.1 抽象类

1.1.2.2 抽象方法

1.1.3 定义抽象类

1.1.3.1 定义抽象类注意

1.1.4 final(常量)修饰符

1.1.4.1 final修饰类

1.1.4.2 final修饰类的方法

1.1.5 final修饰符与abstract关键字的用法

1.1.6 使用final和abstract的常见问题

问题1:

问题2:

问题3:

2.接口

2.1 初识接口

2.2 定义和实现接口

2.2.1 定义一个简单的接口

2.2.2 定义一个复杂的接口

2.3 接口的应用

2.4 面向对象设计原则


1.抽象类

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

1.1 抽象类和抽象方法

1.1.1 为什么需要抽象类

事实上,父类是抽象出来的一个概念,并不存在被称为父类名的对应对象,因此创建它是没有任何意义的。那么,如何限制父类不被实例化呢?这里就需要使用Java的抽象类。

1.1.2 抽象类和抽象方法

1.1.2.1 抽象类

什么是抽象类呢?顾名思义,抽象类就是抽象的类,抽象往往是相对于具体而言的。一般来说,具体类有直接对应的对象,而抽象类没有,它往往表达的是抽象的概念。例:一只猫是具体对象,而猫科动物则是抽象概念;玉米是具体对象,而作物则是抽象概念。

abstract关键字可以用来修饰类和方法,但是不能用来修饰属性和构造方法。

抽象方法只能定在抽象类中。但是在抽象类中可以包含抽象方法,也可以包含普通方法,还可以包含普通类包含的一切成员

在Java中,当一个类被abstract修饰时,该类被称为抽象类:

语法:

  • <访问修饰符> abstract class <类名>{}//abstract关键字表示该类被定义为抽象类

抽象类和普通类最大的区别就是,普通类可以被实例化,而抽象类不能被实例化。

1.1.2.2 抽象方法

在Java中,当一个类的方法被abstract关键字修饰时,该方法被称为抽象方法。抽象方法所在类必须是抽象类。

一个方法被定义为抽象方法,意味着该方法不会有具体的实现,而在抽象类的子类中通过方法重写实现。

语法:

  • [访问修饰符] abstract <返回类型> <方法名>([参数列表]);//abstract关键字表示该方法被定义为抽象方法

抽象方法和普通方法最大的区别是,普通方法有方法体,而抽象方法没有。

1.1.3 定义抽象类

父类定义为抽象类和拥有抽象方法后后,子类就需要把自己声明为抽象类或者重写抽象方法即可。

抽象类的优势体现在哪里呢?抽象类可以看为类的一个模版,定义了子类的行为,它可以为子类提供默认实现,无需在子类中重复实现这些方法,提高了代码的可重用性。同时,子类可以分别实现父类抽象类中定义的抽象方法,实现了方法定义和方法实现的分离,这样使代码实现松耦合,更易于维护。抽象类作为继承关系下的抽象层,不能被实例化,使用的时候通常定义抽象类类型变量,其具体应用是实现抽象类的子类对象,能够方便地实现多态,例Crop crop=new AppleTree(“富士”);

1.1.3.1 定义抽象类注意
  1. 在抽象类中,可以没有、有一个或多个抽象方法,甚至可以定义全部方法都是抽象方法;
  2. 抽象方法只有方法声明,没有方法实现。有抽象方法的类必须声明为抽象类。子类必须重写所有的抽象方法才能实例化;否则子类也必须声明成抽象类;
  3. 抽象类可以有构造方法,其构造方法可以被本类的其他构造方法调用。若此构造方法不是由private修饰的,也可以被本类的子类中的构造方法调用;

1.1.4 final(常量)修饰符

final是Java的关键字,表示"最后的、最终的、不可变的"。使用final修饰符修饰的变量,只能进行一次赋值操作,并且在整个程序的生命周期中它的值都不可改变,被称为"常量"。final修饰符除了可以定义常量,还可以用来修饰类和类的方法。

1.1.4.1 final修饰类

用final修饰的类不能再被继承

语法:

  • [访问修饰符] final class <类名>{}

如果有子类继承用final修饰的类,子类就会出现编译错误。

1.1.4.2 final修饰类的方法

用final修饰的类的方法不能被子类重写。

语法:

  • [访问修饰符] final <返回值类型> <方法名>{}

通常,使用final修饰类的方法主要是从设计的角度考虑,即明确告诉可能会继承该类的其他开发人员,不希望它们重写这个方法。如果视图去重写标识final的方法,就会出现编译错误。

1.1.5 final修饰符与abstract关键字的用法

final修饰符和abstract关键字的用法如下:

  1. abstract可以用来修饰类和方法,不能用来修饰属性和构造方法。final可以用来修饰类、方法和属性,不能修饰构造方法。
  2. Java提供的很多类都是final类,如String类、Math类,它们不能再有子类。Object类中的一些方法,如getClass()、notify()、wait()都是final方法,只能被子类继承而不能被重写,但是hashCode()、toString()、equals(Object obj)不是final方法,可以被重写。

1.1.6 使用final和abstract的常见问题

问题1:
  • final修饰引用类型变量时,变量所指属性的值是否可以改变?
  • 答:使用final修饰引用型变量时,变量的值是固定不变的,而变量所指向的对象的属性值是可变的。
问题2:
  • final修饰方法的参数,参数的值是否可以改变?
  • 答:例:public void changeValue(final int i,final Value value){//1
       i=8;//2
      value.v=8;//3
    }
  • 该代码中,代码行2会出现编译错误,final修饰符可以修饰方法的参数,它表示在整个方法中,不能改变参数的数值。因此变量i和引用类型变量value的值不可改变,但是value的属性值可以改变。
问题3:
  • abstract是否可以和private、static和final共用?
  • 答:例:public satic abstract void print();//1
    private abstract void print();//2
    public final abstract void print();//3
  • 左边三句代码都是错误的。具体分析如下
    在代码行1中,抽象方法只有声明没有实现,static方法可以通过类名直接进行访问,但无法修饰一个没有实现的方法。因此abstract和static不能结合使用
    在代码行2中,抽象方法需要在子类中进行重写,但是private方法又不能被子类继承,自然无法进行重写。因此,abstract和private不能结合使用
    在代码行3中,抽象方法需要在子类中进行重写,但是final修饰的方法表示该方法不能被子类重写,前后是相互矛盾的。因此abstract和final不能结合使用

2.接口

在Java中不允许多重继承,如果要实现多继承的需求,则可以使用接口。

2.1 初识接口

生活中的接口就是一套规范,满足这个规范的设备就可以组装到一起。大家熟悉的计算机,主板上的周边原件扩展接口(Peripheral Component Interconnection,PCI)插槽就可以被理解为接口,它有统一的标准,规定了尺寸、排线等。主板厂商和各种卡的厂家都遵守了这个统一的接口规范,因此,声卡、显卡、网卡尽管内部结构和功能都不一样,但是都可以插在PCI插槽上正常工作。

在Java中接口的作用和生活中的接口类似,它是一种规范和标准,可以约束类的行为,使实现接口的类(或结构)在形式上保持一致。

接口是一些方法特征的集合,从这个角度来讲,接口可以被看做一种特殊的"抽象类",但是采用与抽象类完全不同的语法来表示,两者的设计理念也不同。抽象类用于代码复用,接口利于代码的扩展和维护。

2.2 定义和实现接口

2.2.1 定义一个简单的接口

JDK1.8版本与之前的版本相比,接口的功能更加强大灵活。

简单来说,接口是一个不能实例化的类型。接口类型的语法格式如下:

[访问修饰符] interface 接口名{
  //接口成员
}

类实现接口的语法格式如下:

class 类名 Implemements 接口名{
  //类成员
}

  • 其中 function1为抽象方法,类似于类的抽象方法,只有方法声明,以";"结尾。这里,系统会自动添加public abstract修饰。接口的实现类必须实现接口中定义的所有的抽象方法。接口中
  • function2()为默认方法,使用default来修饰。接口中的默认方法如果不能满足某个实现类的需求,可以在实现类中重写这个默认方法。
  • 接口中function3()为静态方法,使用static来修饰。它类似于默认方法,但不同的是,静态方法不允许在实现接口的类中进行重写。另外接口中定义的静态方法,只能通过接口名称调用,不能通过实现类的类名或实现类的对象调用。
  1. 从JDK1.8开始,接口允许有默认方法和静态方法,其主要目的是,允许开发人员在将新的方法添加到已有接口时无需改动已经实施该接口的所有实现类,这也就是我们所说的向后兼容。
  2. 可以避免代码冗余,让接口中的默认方法在其实现类中可以复用,而不用再在实现类中定义默认方法。
  3. 在IntelliJ IDEA开发环境下,在接口的实现类中可以快速添加需要实现或重写的方法。具体步骤是:将鼠标指针悬停在接口名上,然后右键鼠标,即可找到一个生成按钮,按下去就可以选择需要实现或重写的方法,单击"OK"按钮完成方法的自动添加。
  4. 接口也可以像父类和子类一样使用多态。

接口的语法说明:
1.接口的定义使用interface关键字,接口的命名一般以I字母开头,除此之外,命名规则与类相同。如果访问修饰符是public,则该接口在整个项目中可见;如果省略访问修饰符,则该接口只在该包中可见
2.接口中的属性都会自动使用public static final修饰,无论是否写此关键字,接口的属性都是全局静态常量,必须定义时指定初始值。例笔记中的代码行1和代码行2是等效的。
3.在JDK1.8版本之前,在接口中只能定义抽象方法。从JDK1.8版本开始,接口还允许定义静态方法和默认方法

2.2.2 定义一个复杂的接口

  • 接口本身也可以继承接口。具体语法如下

[访问修饰符] interface 接口名 extends 父接口1,父接口2,...{
  //常量定义
  //方法定义
}

接口之间可以通过extends关键字实现继承关系,一个接口可以继承多个接口,但接口不能继承类。

  • 一个普通类只能继承一个父类,但能同时实现多个接口。具体语法如下:

class 类名 extends 父类名 implements 接口1,接口2,...{
  //类的成员
}

这里,类继承一个父类,但通过implements关键字实现多个接口,此时extends关键字必须位于implements关键字之前。另外,这个类必须实现所有接口的全部抽象方法;否则必须定义为抽象类。

  • 如果一个类实现了多个接口,且这些接口有相同的默认方法,那么该如何处理?

答:在实现类中必须提供自己的默认方法,重写接口中的默认方法。例,接口A中定义了默认方法print(),接口B中也定义了不同实现的默认方法print()。如果类C实现了接口A和接口B,则类C中必须定义自己的print()方;否则,在调用C对象的print()方法时无法确定是访问接口A的print方法还是访问接口B的print()方法。

一个实现类可以重写多个接口的同名方法,如果要使用接口的成员可以接口名.super.成员。

2.3 接口的应用

通过将接口作为类的属性,就可以灵活地接受所有实现这个接口的类的对象,即使需求变化,只要符合接口规范就也能直接装配,不需要改动已有的代码,这使程序有更好的可扩展性和可维护性。

另外,接口也是对Java类的单继承性的一种补充。在Java中,类只能继承一个父类,这种单继承性使代码更加纯净,但是也使类的扩展变得困难。在实例中具体的鸟类在继承父类Bird的同时,通过实现某个特定能力的接口就可以轻松具有一种或多种特殊能力,这弥补了单继承的缺陷,使类可以灵活地扩展。接口类似于一个组件,需要时可以自由组装,因此更利于代码的扩展和维护。

事实上,在Java API中也定义了很多接口。例如,实现对象比较的Comparable接口,实现类序列化的Serializable接口等。Java API定义接口,开发人员必须按照接口定义的规则(也就是定义的方法名参数及返回值)来实现相应的功能。

2.4 面向对象设计原则

在实际开发过程中,遵循以下原则会让代码更具灵活性,更能适应变化:

1.摘取代码中变化的部分,形成接口;

2.多用组合,少用继承

  • 继承表示的是**is a**关系,Cat is a Animal。在继承关系下,子类的创建是基于父类完成的。而组合的意思是把需要的内容组合在一个类里面,这个类不需要继承任何父类就可以提供想要的行为方法,它表示的是**has a**关系,例如 Car has a engine。
  • 某种对象is a代表是一种(对象的类型)(使用继承)
    某种对象has a代表的有一种(对象有的动态物品)(使用接口)

3.面向接口编程,不依赖于具体实现

  • 接口体现了约定和实现相分离的原则,通过面向接口编程,可以降低代码间的耦合性,提高代码的可扩展性和可维护性。
  • 向接口编程就意味着:当开发系统时,主体构架使用接口。如果对一个类型有依赖应该尽量依赖接口尽量少依赖子类。因为子类一旦变化,代码变动的可能性就很大,而接口要稳定得多。在具体的代码实现中,体现在方法参数、方法的返回值、属性类型尽量使用接口等。通过接口构成系统的骨架,就可以通过更换实现接口的类实现扩展,非常灵活。
  • 面向接口编程可以实现的分离,这样做最大的好处就是能够在客户端未知的情况下修改实现代码。
  • 那么接口的应用场合是什么呢?
  • 一种用在层和层之间的调用中。层与层之间最忌讳耦合度过高或修改过于频繁。设计优秀的接口能解决这个问题。
  • 另一种用在那些不稳定的部分上。如果某些需求的变化性很大,那么定义接口也是一种解决方法。设计良好的接口就像日常使用的万用插座,不论插头如何变化,都可以使用,最后强调一点,良好的接口定义一定是来自需求的,它绝对不是程序员凭空想出来的。

4.针对扩展开发,针对改变关闭

  • 通常这个原则被称为开闭原则,也就是说软件实体应该通过扩展实现变化,而不是通过修改已有的代码实现变化。具体来讲,如果项目中的需求发生了变化,应该添加一个新的接口或类,而不是要去修改原有的代码。
  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值