在面向对象一文中,我们说了多态的一个功能是将“做什么”和“怎么做”分离开来,所采用的方法是将不同的具体实现放在不同的子类中,然后向接口中传入一个父类对象的引用。而本篇博客要说的内容则为接口(此处"接口"的理解是“可以供外部调用的方法”,与本章所述的“接口”区别)和实现的分离提供了一种更加结构化的方法。抽象类和接口赋予了Java对象抽象能力,为面向对象设计提供了很大的支持。

  在《Java编程思想》中,作者在介绍两者的使用方法和特点以外,还结合大量的Demo代码来阐述了它们在程序结构设计方面的巧妙用法。事实上,在很多的设计模式中也经常可以看到这两种机制的身影(尤其是接口)。本篇博客着重于梳理总结两者的基本特点,并对比它们之间的异同。

抽象类

  我们已经说过,在Java中一切都是对象,同时对象又是通过类来进行描述的。比如说Cat类中包含color,size等字段,以及eat()、cry()等方法,那么对任意一个Cat对象,我们就可以通过这些内容来描述它。但是并非所有的类都是用来描述对象的,比如说Animal类,我们一定要针对它的一个导出类才能具体只要某一个动物具体时什么样子的,能够如何来描述它。那么在Animal类中,就只需要定义导出类都具有的方法即可,至于这些方法的具体实现,需要他们的子类根据自己的需要来实现。

  在Java中将仅有声明而没有具体方法体的方法叫做抽象方法,包含抽象方法的类就叫做抽象类。抽象类中可以包含一个或多个抽象方法,也可以包含普通类所具备的成员变量和普通方法,但只要包含一个抽象方法,那么这个类就是抽象类,且必须使用abstract修饰。语法如下:

 abstract class Animal {
private int color; public abstract void cry(); public void die(){
System.out.println("animal has dead");
}
}

  抽象类中定义了接口(做什么),而接口的具体实现(怎么做)就需要通过继承由它的子类来完成。因此抽象类必须被继承,否则就没有意义。在使用抽象类的时候需要注意以下几点:

  • 抽象类不能被实例化,实例化的工作应该交给子类来完成
  • 子类可以实现抽象类的抽象方法,也可以不实现。如果子类不实现父类的抽象方法,则子类也必须是抽象类
  • abstract不能与final共同修饰一个类。抽象类的意义在于被继承,而被final修饰的类不能被继承,因此两者不能共同修饰同一个类。
  • 若一个对象为抽象方法,则不可以被private、final或static修饰。private方法不能被子类继承,因此无法让子类来实现这个方法;final和static修饰的方法可以被子类继承,但是不能被子类重写,因此也无法被实现。

  创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样来使用它们。抽象类还是很有用的重构工具,因为它们使得我们可以很容易地将公共方法沿着继承层次向上移动。

  下面给出一个来自《Java编程思想》的抽象类示例:

 //: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music; public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
}
 //: interfaces/music4/Music4.java
// Abstract classes and methods.
package interfaces.music4;
import polymorphism.music.Note;
import static net.mindview.util.Print.*; abstract class Instrument {
private int i; // Storage allocated for each
public abstract void play(Note n);
public String what() { return "Instrument"; }
public abstract void adjust();
} class Wind extends Instrument {
public void play(Note n) {
print("Wind.play() " + n);
}
public String what() { return "Wind"; }
public void adjust() {}
} class Percussion extends Instrument {
public void play(Note n) {
print("Percussion.play() " + n);
}
public String what() { return "Percussion"; }
public void adjust() {}
} class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
public String what() { return "Stringed"; }
public void adjust() {}
} class Brass extends Wind {
public void play(Note n) {
print("Brass.play() " + n);
}
public void adjust() { print("Brass.adjust()"); }
} class Woodwind extends Wind {
public void play(Note n) {
print("Woodwind.play() " + n);
}
public String what() { return "Woodwind"; }
} public class Music4 {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~

接口

  接口相比于抽象类在抽象程度上更进了一步,它要求产生一个完全抽象的类。在这个类中的所有的方法都是抽象方法,都未提供其具体的实现。

  Java中interface来产生一个接口,使用方法如下所示:

 interface InterfaceDemo{

     int VALUE = 5;//static & final

     void methodA();//automatically public
}

  在使用接口的时候,有以下几点需要注意:

  1. 接口内的方法默认为public且必须为public。如果人为地将其声明为非public方法,则编译器会报错;
  2. 接口中可以包含域(成员变量),但这些域都被隐式地被声明为static final的;
  3. Java中用implements关键字来表示实现一个接口。与抽象类不同,接口的实现类必须实现接口中所有的抽象方法
  4. 我们不能用new关键字来为接口创建一个实例对象,但是却可以声明一个接口的引用,并将其指向任意一个实现了该接口的类对象。它提供了一种将多个实现同一接口的类向上转型为同一基类(其实是接口)的方法,是Java中多态的一种体现形式。最常见的就是我们在定义某一种集合类的时候:List其实是一个接口,ArrayList和LinkedList都是实现了该接口的类。list引用可以任意指向这两种类的实例对象,listFunc()方法要求一个List类型的对象,则ArrayList和LinkedList的对象也可以传入其中。
List<String> list = new ArrayList<>();
list = new LinkedList<>(); public void listFunc(List<String> list) {...}

用接口实现多重继承

  我们说过在Java中一个类只能有唯一的一个父类,即extends只支持单继承,而接口则为我们提供了一种实现多重继承的方式,具体体现为以下两种方式:1、implements关键字后可以跟多个接口名,用逗号分隔,则该类同时实现多个接口;2、一个类在继承一个父类以后,依然可以继续实现一个或多个接口。在这种情况下,必须将extends关键字写在implements关键字以前。示例如下:

 interface CanFight {
void fight();
} interface CanSwim {
void swim();
} interface CanFly {
void fly();
} class ActionCharacter {
public void fight() {}
} class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}

  在以上这段代码中,CanFight和ActionCharacter都包含void fight()方法。在这段代码中并不会出现问题,因为这两个方法时完全相同的。但如果他们的签名或返回类型不同,则会产生编译错误,示例如下:

  为了避免这种错误出现,在打算组合多个接口时,应尽量避免接口中包含相同的方法名

用继承来扩展接口

  通过继承,可以在接口中添加新的方法,或将数个已有接口组合为一个接口。示例如下:

 interface Monster {
void menace();
} interface DangerousMonster extends Monster {
void destroy();
} interface Lethal {
void kill();
} class DragonZilla implements DangerousMonster {
public void menace() {}
public void destroy() {}
} interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
} class VeryBadVampire implements Vampire {
public void menace() {}
public void destroy() {}
public void kill() {}
public void drinkBlood() {}
}

  DangerousMonster是Monster的直接扩展,它产生了一个新接口。DrangonZilla中实现了这个接口。而Vampire接口通过继承将多个接口组合起来,extends的这种使用方法仅适用于接口的继承

抽象类与接口的区别

  我们来总结一下抽象类和接口差别:

  1. 抽象类中可以包含非抽象的方法,接口中的所有方法都必须为抽象方法;
  2. 抽象类中可以包含任意范围的域,但接口中只能有用static final域(静态常量数据);
  3. 继承于抽象类的子类可以不实现其中的抽象方法,而实现接口的类必须实现其中所有的抽象方法。

  除此以外,抽象类与接口在设计上也存在着差别。抽象类表现的是纵向的继承,包含“is-a”的关系;接口表现的是横向的一种扩展,包含“is-like-a”的关系。关于设计层次的不同,这篇博客中提出了三点不同,可以借鉴:

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

参考文献

[1]. Java编程思想 第四版

[2]. https://blog.csdn.net/u012340794/article/details/60882727

[3].https://blog.csdn.net/chenssy/article/details/12858267

Java课堂笔记(三):抽象类和接口的更多相关文章

  1. 0026 Java学习笔记-面向对象-抽象类、接口

    抽象方法与抽象类 抽象方法用abstract修饰,没有方法体部分,连花括号都不能有: 抽象方法和抽象类都用abstract修饰 包含抽象方法的类一定是抽象类:但不包含抽象方法的类也可以是抽象类 不能创 ...

  2. Java基础学习笔记(三) - 抽象类和接口

    一.抽象类 没有方法主体的方法称为抽象方法,包含抽象方法的类就是抽象类. Java中使用 abstract 关键字修饰方法和类,抽象方法只有一个方法名,没有方法体. public abstract c ...

  3. Java学习笔记之抽象类与接口

    抽象类(abstract) 抽象类概述:一个类被abstract修饰表示这个类是抽象类, 自己定义方法但是不实现方法,后代去实现 抽象方法:   一个方法被abstract修饰表示这个方法是抽象方法 ...

  4. Java基础知识(抽象类和接口)

    一.抽象类 在了解抽象类之前,先来了解一下抽象方法.抽象方法是一种特殊的方法:它只有声明,而没有具体的实现.抽象方法的声明格式为: 1 abstract void fun(); 抽象方法必须用abst ...

  5. Java课堂笔记(零):内容索引

    回想自己学习和使用Java的时间也是很长了.本科期间课堂上浅尝辄止地学习了点皮毛,后来也是搁置不用,未曾深入研究.研究生期间因为项目和实习的原因,基本算是重新拾起Java这门语言,并且接触到了Spri ...

  6. C# 语法三 抽象类和接口

    1.抽象类 2.接口 一 抽象类 跟普通类的区别: a)用abstract标识类.抽象方法 b)抽象方法,只能声明,不能定义 c)抽象类不能实例化 二 接口 接口用interface标识,所有的成员( ...

  7. Java笔记:抽象类、接口

    这篇笔记主要是抽象类和接口,还有简单介绍下三种设计模式:模板模式.工厂模式.命令模式 1.抽象方法和抽象类(1)抽象方法和抽象类都必须使用abstract修饰符来定义,包含抽象方法的类只能被定义成抽象 ...

  8. 线程(java课堂笔记)

    1.两种方式的差异 2.线程的生命周期 3.线程控制(线程的方法) 4.线程同步 5.线程同步锁 一. 两种方式的差异 A extends Thread :简单 不能再继承其他类了(Java单继承)同 ...

  9. java提高篇(五)-----抽象类与接口

    接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念 ...

随机推荐

  1. 关于jsp 获得当前绝对路径的方法

    方法1) request.getRequestURL(); 方法2)  request.getScheme()+"://"+request.getServerName()+&quo ...

  2. rest_framework框架的基本组件

    快速实例 Quickstart 序列化 创建一个序列化类 简单使用 开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json之类的表示形式的方式. ...

  3. python小实例

    一.跳动的心 love = '\n'.join([''.join([('love'[(x-y) % len('Love')] if ((x*0.05)**2+(y*0.1)**2-1)**3-(x*0 ...

  4. ubuntu下docker安装

    首先来一个官网安装教程链接:https://docs.docker.com/install/linux/docker-ce/ubuntu/ 目前docker主要有docker-CE 与 docker- ...

  5. 02java基础——类和方法

    1.类的定义 /* 定义类: 使用类的形式,对现实中的事物进行描述 事物: 属性,方法 属性: 变量 方法: 这个事物具备的功能 格式: public class 类名{ 属性定义 修饰符 数据类型 ...

  6. qt02 textEdit

    1.向QTextEdit中当前光标位置添加一行字符串message ui.messageTextEdit->textCursor().insertText(message+"\n&qu ...

  7. springboot easyexcel

    pom..xml <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel&l ...

  8. 前端之CSS:CSS选择器

    前端之css样式(选择器)... 一.css概述 CSS是Cascading Style Sheets的简称,中文称为层叠样式表,对html标签的渲染和布局 CSS 规则由两个主要的部分构成:选择器, ...

  9. vue开发知识点总结

    一.vue介绍 Vue.js 是一套构建用户界面(UI)的渐进式JavaScript框架,是一个轻量级MVVM(model-view-viewModel)框架. 二.数据绑定 最常用的方式:Musta ...

  10. @transactional注解在什么情况下会失效,为什么?

    一,特性: 1,一般在service里加@Transactional注解,不建议在接口上添加,加了此注解后此类会纳入spring事务管理中,每个业务方法执行时,都会开启一个事务,不过都是按照相同的管理 ...