在面向对象一文中,我们说了多态的一个功能是将“做什么”和“怎么做”分离开来,所采用的方法是将不同的具体实现放在不同的子类中,然后向接口中传入一个父类对象的引用。而本篇博客要说的内容则为接口(此处"接口"的理解是“可以供外部调用的方法”,与本章所述的“接口”区别)和实现的分离提供了一种更加结构化的方法。抽象类和接口赋予了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. 算法学习之二分查找算法的python实现

    ——参考自<算法图解> 我们假设需要查找的数组是有序的(从大到小或者从小到大),如果无序,可以在第四行后插入一句 my_list.sort() 完整代码如下 def binary_sear ...

  2. python初步学习

    一.字符编码 ASCII表是是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言,其最多只能用 8 位来表示(一个字节),即:2**8 = 256-1,所以,ASCII码最多只能表示 ...

  3. JavaEE高级-Struts2学习笔记

    Struts2是一个用来来发MVC应用的框架,它提供了Web应用程序开发过程中一些常见问题的解决方案: - 对来自用户的输入数据进行合法的验证 - 统一的布局 - 可扩展性. - 国际化和本地化 - ...

  4. Shell编程变量

    shell变量 什么是变量,存放了各种数据,在linux怎么定义了变量,name=ken,name就是变量名,ken就是变量值, 但是在shell中,有三种方式: name=ken name='ken ...

  5. 01JAVA入门

    1 Welcome to java public class ch01Welcome { public static void main(String[] args) { System.out.pri ...

  6. python List 常用方法

    list是python常用的数据类型,属于可变的数据类型.用[]表示,里面的元素用','隔开,并且里面的元素类型可以不同,对于每个元素,list都有一个索引一一对应,第一个元素的索引是0,第二个是1, ...

  7. C#基础知识之dnSpy反编译

    dnSpy工具可以在网上自行下载 软件界面如下: 现在进入话题,首先编写一个Hello World的控制台运行程序,如下图所示: 代码如下: using System; using System.Co ...

  8. .net core Consul

    创建API项目修改Program public class Program { public static void Main(string[] args) { CreateWebHostBuilde ...

  9. [洛谷P3243] 菜肴制作

    问题描述 知名美食家小 A被邀请至ATM 大酒店,为其品评菜肴. ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高到低给予1到N的顺序编号,预估质量最高的菜肴编号为1. 由于菜肴 ...

  10. handy源码阅读(六):tcp类

    首先是tcpconn和tcpserver类: struct TcpConn : public std::enable_shared_from_this<TcpConn>, private ...