Java的抽象类与接口理解

1413-南同学

发表文章数:13

热门标签

, ,
首页 » Java » 正文

抽象类


概述

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

抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如我们在创建动物对象的时候,我们不知道是我们要创建的到底是什么动物我们就可以用抽象类来定义。

抽象类,其实就是把子类中的共有的特性给提取出来,用于继承实现复用。

特点:

  • 抽象类不能被实例化,实例化的工作应该交由它的子类来完成,它只需要有一个引用即可。
  • 抽象类不一定有抽象方法,有抽象方法的类一定为抽象类。
  • 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类
  • 有成员变量,成员变量可以是变量,也可以是常量。
  • 有构造方法,用于子类访问父类数据的初始化
  • 有成员方法,成员方法可以是抽象的,也可以是非抽象的。
    抽象方法:限定子类必须完成某些动作
    非抽象方法:提高代码的复用性

代码

//抽象动物类
public abstract class Animal {
	//抽象方法
	public abstract void eat();
	
	public void sleep() {
		System.out.println("睡觉");
	}
}

//猫类
public class Cat extends Animal{
	//重写父类中的方法
	@Override
	public void eat() {
		// TODO 自动生成的方法存根
		System.out.println("猫吃鱼");
	}
	
}

//狗类
public abstract class Dog extends Animal{
	//子类没有重写父类(抽象类)中的所有方法,所以它需要为抽象类
}


//测试类
public class AnimalDemo {
	public static void main(String[] args) {
		//创建对象
		/*Animal a = new Animal();//抽象类不能实例化
*/		
		//按照多态的形式实例化抽象类
		Animal a = new Cat();
		a.eat();
		a.sleep();
	}
}

注意

  1. abstract不能与final并列修饰同一个类
  2. abstract 不能与private、static、final或native并列修饰同一个方法
  3. 子类中的抽象方法不能与父类的抽象方法同名

接口


概述

Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,它就是一种规范。怎么理解呢,就是我提供这样一个标准,你通过接入(实现)我这个接口,你能实现我提供的标准,拿到“许可证”,具体你拿到了这个证做什么那看你了。例如,假如你有一个店面,现在你要想经营一个餐饮店,那你是不是至少需要一个餐饮经营许可证(别的人要也想开餐饮店,那他是不是也是需要接入这个接口),这个餐饮经营许可证就像一个接口,你接入(实现)了这个接口,这表示你可以经营了,但是你具体是卖早餐还是卖午餐则需要你自己去选择。

特点

接口的特点
      A:定义接口使用的是interface关键字
      B:类和接口之间是实现关系,用implements关键字表示
      C:接口不能实例化
            参照多态的形式使用实现类来实例化。(重写方法)
      D:接口的实现类
            要么重写接口中的所有的抽象方法
            要么是一个抽象类

多态的几种形式
      具体类多态(几乎不用)
      抽象类多态(常用)
      接口多态(最常用)

接口的成员特点

  • 成员变量:
    有成员变量,而且变量只能是常量。
    默认修饰符:public static final
  • 构造方法:
    没有构造方法。
  • 成员方法:
    有成员方法,而且都是抽象的。
    默认修饰符:public abstract

代码

参考上边抽象类的代码,现在如果我想让狗可以缉毒,那么我该怎么做?那我是不是定义一个缉毒的接口(规范),让Dog类去接入实现就可以了。下边看代码

```java
//抽象动物类
public abstract class Animal {
	//抽象方法
	public abstract void eat();
	
	public void sleep() {
		System.out.println("睡觉");
	}
}

//猫类
public class Cat extends Animal{
	//重写父类中的方法
	@Override
	public void eat() {
		// TODO 自动生成的方法存根
		System.out.println("猫吃鱼");
	}
	
}

//狗类
public abstract class Dog extends Animal implements AntiDrug{
	//子类没有重写父类(抽象类)中的所有方法,所以它需要为抽象类
	@Override
	public void antiDrug(){
	System.out.println("狗能缉毒了");
	}
}


//测试类
public class AnimalDemo {
	public static void main(String[] args) {
		//创建对象
		/*Animal a = new Animal();//抽象类不能实例化
*/		
		//按照多态的形式实例化抽象类
		Animal a = new Cat();
		a.eat();
		a.sleep();
	}
}

public interface AntiDrug{//定义缉毒的接口
	public abstract void antiDrug();
} 

抽象类与接口的区别


成员区别:

抽象类:
      以是变量,也可以是常量
      有构造方法
      成员方法可以是抽象方法,也可以是非抽象的方法
接口:
      成员变量只能是常量
      没有构造方法
      成员方法只能是抽象方法

关系区别:

类与类:继承关系,只能单继承,不过可以多层继承
类与接口:实现关系,可以单实现,也可以多实现
接口与接口:继承关系,可以单继承,也可以多继承

设计理念的区别:

1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。
3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

为了更好的阐述他们之间的区别,下面将使用一个例子来说明。

我们有一个Door的抽象概念,它具备两个行为open()和close(),此时我们可以定义通过抽象类和接口来定义这个抽象概念:

抽象类

abstract class Door{
    abstract void open();
    abstract void close()}

接口

interface Door{
    void open();
    void close();
}

至于其他的具体类可以通过使用extends使用抽象类方式定义Door或者Implements使用接口方式定义Door,这里发现两者并没有什么很大的差异。
但是现在如果我们需要门具有报警的功能,那么该如何实现呢?

解决方案一:给Door增加一个报警方法:clarm();

abstract class Door{
    abstract void open();
    abstract void close();
    abstract void alarm();
}

或者

interface Door{
    void open();
    void close();
    void alarm();
}

这种方法违反了面向对象设计中的一个核心原则 ISP (Interface Segregation Principle)1,在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方 法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变而改变,反之依然。

解决方案二

既然open()、close()和alarm()属于两个不同的概念,那么我们依据ISP原则将它们分开定义在两个代表两个不同概念的抽象类里面,定义的方式有三种:

  1、两个都使用抽象类来定义。

  2、两个都使用接口来定义。

  3、一个使用抽象类定义,一个是用接口定义。

  由于java不支持多继承所以第一种是不可行的。后面两种都是可行的,但是选择何种就反映了你对问题域本质的理解。

如果选择第二种都是接口来定义,那么就反映了两个问题:
1、我们可能没有理解清楚问题域,AlarmDoor在概念本质上到底是门还报警器。
2、如果我们对问题域的理解没有问题,比如我们在分析时确定了AlarmDoor在本质上概念是一致的,那么我们在设计时就没有正确的反映出我们的设计意图。因为你使用了两个接口来进行定义,他们概念的定义并不能够反映上述含义。

第三种,如果我们对问题域的理解是这样的:AlarmDoor本质上Door,但同时它也拥有报警的行为功能,这个时候我们使用第三种方案恰好可以阐述我们的设计意图。AlarmDoor本质是们,所以对于这个概念我们使用抽象类来定义,同时AlarmDoor具备报警功能,说明它能够完成报警概念中定义的行为功能,所以alarm可以使用接口来进行定义。如下:

abstract class Door{
    abstract void open();
    abstract void close();
}

interface Alarm{
    void alarm();
}
 
class AlarmDoor extends Door implements Alarm{
    void open(){}
    void close(){}
    void alarm(){}
}

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实抽象类表示的是"is-a"关系,接口表示的是"like-a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

参考文章:https://blog.csdn.net/chenssy/article/details/12858267


  1. ISP(Interface Segregation Principle):面向对象的一个核心原则。它表明使用多个专门的接口比使用单一的总接口要好。 一个类对另外一个类的依赖性应当是建立在最小的接口上的。一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。 ↩︎

未经允许不得转载:作者:1413-南同学, 转载或复制请以 超链接形式 并注明出处 拜师资源博客
原文地址:《Java的抽象类与接口理解》 发布于2020-12-03

分享到:
赞(0) 打赏

评论 抢沙发

评论前必须登录!

  注册



长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu3.3主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录