--------说明抽象类和接口的区别,那我们首先应该分别了解他们的定义--------
-
接口
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。接口是 Java 中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
Java 接口的定义方式与类基本相同,不过接口定义使用的关键字是 interface,接口定义的语法格式如下:
[public] interface interface_name [extends interface1_name[, interface2_name,…]] {
// 接口体,其中可以包含定义常量和声明方法
[public] [static] [final] type constant_name = value; // 定义常量
[public] [abstract] returnType method_name(parameter_list); // 声明方法
}
对以上语法的说明如下:
- public 表示接口的修饰符,当没有修饰符时,则使用默认的修饰符,此时该接口的访问权限仅局限于所属的包;
- interface_name 表示接口的名称。接口名应与类名采用相同的命名规则,即如果仅从语法角度来看,接口名只要是合法的标识符即可。如果要遵守 Java 可读性规范,则接口名应由多个有意义的单词连缀而成,每个单词首字母大写,单词与单词之间无需任何分隔符。
- extends 表示接口的继承关系;
- interface1_name 表示要继承的接口名称;
- constant_name 表示变量名称,一般是 static 和 final 型的;
- returnType 表示方法的返回值类型;
- parameter_list 表示参数列表,在接口中的方法是没有方法体的。
注意:一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:
- 具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口,其访问将局限于所属的包。
- 方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
- 在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
- 接口没有构造方法,不能被实例化。例如:
public interface A {
publicA(){…} // 编译出错,接口不允许定义构造方法
}
一个接口不能够实现另一个接口,但它可以继承多个其他接口。子接口可以对父接口的方法和常量进行重写。例如:
public interface StudentInterface extends PeopleInterface {
// 接口 StudentInterface 继承 PeopleInterface
int age = 25; // 常量age重写父接口中的age常量
void getInfo(); // 方法getInfo()重写父接口中的getInfo()方法
}
例如,定义一个接口 MyInterface,并在该接口中声明常量和方法,如下:
public interface MyInterface { // 接口myInterface
String name; // 不合法,变量name必须初始化
int age = 20; // 合法,等同于 public static final int age = 20;
void getInfo(); // 方法声明,等同于 public abstract void getInfo();
}
-
实现接口
接口的主要用途是被实现类实现,一个类可以实现一个或多个接口,继承使用 extends 关键字,实现则使用 implements 关键字。因为一个类可以实现多个接口,这也是 Java 为单继承灵活性不足所作的补充。类实现接口的语法格式如下:
<public> class <class_name> [extends superclass_name] [implements interface1_name[, interface2_name…]] {
// 主体
}
对以上语法的说明如下:
- public:类的修饰符;
- superclass_name:需要继承的父类名称;
- interface1_name:要实现的接口名称。
实现接口需要注意以下几点:
- 实现接口与继承父类相似,一样可以获得所实现接口里定义的常量和方法。如果一个类需要实现多个接口,则多个接口之间以逗号分隔。
- 一个类可以继承一个父类,并同时实现多个接口,implements 部分必须放在 extends 部分之后。
- 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
例1
在程序的开发中,需要完成两个数的求和运算和比较运算功能的类非常多。那么可以定义一个接口来将类似的功能组织在一起。下面创建一个示例,具体介绍接口的实现方式。
(1)创建一个名称为 IMath 的接口,代码如下:
public interface IMath {
public int sum(); // 完成两个数的相加
public int maxNum(int a,int b); // 获取较大的数
}
(2)定义一个 MathClass 类并实现 IMath 接口,MathClass 类实现代码如下:
public class MathClass implements IMath {
private int num1; // 第 1 个操作数
private int num2; // 第 2 个操作数
public MathClass(int num1,int num2) {
// 构造方法
this.num1 = num1;
this.num2 = num2;
}
// 实现接口中的求和方法
public int sum() {
return num1 + num2;
}
// 实现接口中的获取较大数的方法
public int maxNum(int a,int b) {
if(a >= b) {
return a;
} else {
return b;
}
}
}
在实现类中,所有的方法都使用了 public 访问修饰符声明。无论何时实现一个由接口定义的方法,它都必须实现为 public,因为接口中的所有成员都显式声明为 public。
(3)最后创建测试类 NumTest,实例化接口的实现类 MathClass,调用该类中的方法并输出结果。该类内容如下:
public class NumTest {
public static void main(String[] args) {
// 创建实现类的对象
MathClass calc = new MathClass(100, 300);
System.out.println("100 和 300 相加结果是:" + calc.sum());
System.out.println("100 比较 300,哪个大:" + calc.maxNum(100, 300));
}
}
程序运行结果如下所示。
100 和 300 相加结果是:400
100 比较 300,哪个大:300
在该程序中,首先定义了一个 IMath 的接口,在该接口中只声明了两个未实现的方法,这两个方法需要在接口的实现类中实现。在实现类 MathClass 中定义了两个私有的属性,并赋予两个属性初始值,同时创建了该类的构造方法。因为该类实现了 MathClass 接口,因此必须实现接口中的方法。在最后的测试类中,需要创建实现类对象,然后通过实现类对象调用实现类中的方法。
-
Java抽象类
Java语言提供了两种类,分别为具体类和抽象类。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,那么这样的类称为抽象类。
在 Java 中抽象类的语法格式如下:
<abstract>class<class_name> {
<abstract><type><method_name>(parameter-iist);
}
其中,abstract 表示该类或该方法是抽象的;class_name 表示抽象类的名称;method_name 表示抽象方法名称,parameter-list 表示方法参数列表。
如果一个方法使用 abstract 来修饰,则说明该方法是抽象方法,抽象方法只有声明没有实现。需要注意的是 abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。
抽象方法的 3 个特征如下:
- 抽象方法没有方法体
- 抽象方法必须存在于抽象类中
- 子类重写父类时,必须重写父类所有的抽象方法
注意:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。
抽象类的定义和使用规则如下:
- 抽象类和抽象方法都要使用 abstract 关键字声明。
- 如果一个方法被声明为抽象的,那么这个类也必须声明为抽象的。而一个抽象类中,可以有 0~n 个抽象方法,以及 0~n 个具体方法。
- 抽象类不能实例化,也就是不能使用 new 关键字创建对象。
例1
不同几何图形的面积计算公式是不同的,但是它们具有的特性是相同的,都具有长和宽这两个属性,也都具有面积计算的方法。那么可以定义一个抽象类,在该抽象类中含有两个属性(width 和 height)和一个抽象方法 area( ),具体步骤如下。
(1)首先创建一个表示图形的抽象类 Shape,代码如下所示。
public abstract class Shape {
public int width; // 几何图形的长
public int height; // 几何图形的宽
public Shape(int width, int height) {
this.width = width;
this.height = height;
}
public abstract double area(); // 定义抽象方法,计算面积
}
(2)定义一个正方形类,该类继承自形状类 Shape,并重写了 area( ) 抽象方法。正方形类的代码如下:
public class Square extends Shape {
public Square(int width, int height) {
super(width, height);
}
// 重写父类中的抽象方法,实现计算正方形面积的功能
@Override
public double area() {
return width * height;
}
}
(3)定义一个三角形类,该类与正方形类一样,需要继承形状类 Shape,并重写父类中的抽象方法 area()。三角形类的代码实现如下:
public class Triangle extends Shape {
public Triangle(int width, int height) {
super(width, height);
}
// 重写父类中的抽象方法,实现计算三角形面积的功能
@Override
public double area() {
return 0.5 * width * height;
}
}
(4)最后创建一个测试类,分别创建正方形类和三角形类的对象,并调用各类中的 area() 方法,打印出不同形状的几何图形的面积。测试类的代码如下:
public class ShapeTest {
public static void main(String[] args) {
Square square = new Square(5, 4); // 创建正方形类对象
System.out.println("正方形的面积为:" + square.area());
Triangle triangle = new Triangle(2, 5); // 创建三角形类对象
System.out.println("三角形的面积为:" + triangle.area());
}
}
在该程序中,创建了 4 个类,分别为图形类 Shape、正方形类 Square、三角形类 Triangle 和测试类 ShapeTest。其中图形类 Shape 是一个抽象类,创建了两个属性,分别为图形的长度和宽度,并通过构造方法 Shape( ) 给这两个属性赋值。
在 Shape 类的最后定义了一个抽象方法 area( ),用来计算图形的面积。在这里,Shape 类只是定义了计算图形面积的方法,而对于如何计算并没有任何限制。也可以这样理解,抽象类 Shape 仅定义了子类的一般形式。
正方形类 Square 继承抽象类 Shape,并实现了抽象方法 area( )。三角形类 Triangle 的实现和正方形类相同,这里不再介绍。
在测试类 ShapeTest 的 main( ) 方法中,首先创建了正方形类和三角形类的实例化对象 square 和 triangle,然后分别调用 area( ) 方法实现了面积的计算功能。
(5)运行该程序,输出的结果如下:
正方形的面积为:20.0
三角形的面积为:5.0
--------那么现在切入正题:这两者之间有什么异同呢--------
1.抽象类特点
-
抽象类不能被实例化,只能被继承。
-
包含抽象方法的类一定是抽象类,但抽象类不一定包含抽象方法(抽象类可以包含普通方法)。
-
抽象方法的权限修饰符只能为 public、protected 或 default,默认情况下为 public。
-
一个类继承于一个抽象类,则子类必须实现抽象类的抽象方法,如果子类没有实现父类的抽象方法,那子类必须定义为抽象类。
-
抽象类可以包含属性、方法、构造方法,但构造方法不能用来实例化对象,只能被子类调用。
2.接口特点
- 接口中可以包含变量和方法,变量被隐式指定为 public static final,方法被隐式指定为 public abstract(JDK 1.8 之前)。
- 接口支持多继承,即一个接口可以继承(extends)多个接口,间接解决了 Java 中类不能多继承的问题。
- 一个类可以同时实现多个接口,一个类实现某个接口则必须实现该接口中的抽象方法,否则该类必须被定义为抽象类。
3.两者共同点
- 接口和抽象类都不能被实例化,主要用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
4.两者区别
- 接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
- 从某种程度上来看,接口类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,会导致系统中大部分类都需要改写。抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。
- 一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补 Java 单继承的不足。
5.抽象类应用场景
- 父类只知道其子类应该包含怎样的方法,不能准确知道这些子类如何实现这些方法的情况下,使用抽象类。
- 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免了子类设计的随意性。
6.接口应用场景
- 一般情况下,实现类和它的抽象类之前具有 "is-a" 的关系,但是如果我们想达到同样的目的,但是又不存在这种关系时,使用接口。
- 由于 Java 中单继承的特性,导致一个类只能继承一个类,但是可以实现一个或多个接口,此时可以使用接口。
7.适用场景
- 如果拥有一些方法并且想让它们有默认实现,则使用抽象类。
- 如果想实现多重继承,那么必须使用接口。因为 Java 不支持多继承,子类不能继承多个类,但可以实现多个接口,因此可以使用接口。
- 如果基本功能在不断改变,那么就需要使用抽象类。如果使用接口并不断需要改变基本功能,那么就需要改变所有实现了该接口的类。
这就是今天的分享内容啦,每天掌握一个知识点~欢迎指正~