一, 抽象类
1. 抽象类
Java 语言提供了两种类: 一种是具体类; 另一种是抽象子类.
2. 抽象类概念:
在面向对象的概念中, 所有的对象都是通过类来描绘的, 但是反过来, 并不是所有的类都是用来描绘对象的, 如果一个类中没有包含足够的信息来描绘一个具体的对象, 这样的类就是抽象类. 抽象类除了不能实例化对象之外, 类的其它功能依然存在, 成员变量, 成员方法和构造方法的访问方式和普通类一样.
由于抽象类不能实例化对象, 所以抽象类必须被继承, 才能被使用. 也是因为这个原因, 通常在设计阶段决定要不要设计抽象类. 父类包含了子类集合的常见的方法, 但是由于父类本身是抽象的, 所以不能使用这些方法. 在 Java 中抽象类表示的是一种继承关系, 一个类只能继承一个抽象类, 而一个类却可以实现多个接口.
在 [Java 学习基础] Java 的继承与多态中介绍多态时, 使用过几何图形类示例, 其中 Figure(几何图形) 类中有一个 onDraw(绘图)方法, Figure 有两个子类 Ellipse(椭圆形)和 Triangle(三角形),Ellipse 和 Triangle 覆盖 onDraw 方法.
作为父类 Figure(几何图形)并不知道在实际使用时有多少个子类, 目前有椭圆形和三角形, 那么不同的用户需求可能会有矩形或圆形等其他几何图形, 而 onDraw 方法只有确定是哪一个子类后才能具体实现. Figure 中的 onDraw 方法不能具体实现, 所以只能是一个抽象方法. 在 Java 中具有抽象方法的类称为 "抽象类",Figure 是抽象类, 其中的 onDraw 方法是抽象方法. 如下图所示类图中 Figure 是抽象类, Ellipse 和 Triangle 是 Figure 子类实现 Figure 的抽象方法 onDraw.
Tips: 在 UML 类图抽象类和抽象方法字体是斜体的, 如上图所示中的 Figure 类和 onDraw 方法都是斜体的.
3. 抽象类声明和实现
设计抽象方法目的就是让子类来实现的, 否则抽象方法就没有任何意义, 实现抽象类示例代码如下:
- //Ellipse.java 文件
- package com.Kevin;
- // 几何图形椭圆形
- public class Ellipse extends Figure {
- // 绘制几何图形方法
- @Override
- public void onDraw() {
- System.out.println("绘制椭圆形...");
- }
- }
- //Triangle.java 文件
- package com.Kevin;
- // 几何图形三角形
- public class Triangle extends Figure {
- // 绘制几何图形方法
- @Override
- public void onDraw() {
- System.out.println("绘制三角形...");
- }
- }
上述代码声明了两个具体类 Ellipse 和 Triangle, 它们实现 (覆盖) 了抽象类 Figure 的抽象方法 onDraw.
调用代码如下:
- //HelloWorld.java 文件
- package com.Kevin;
- public class HelloWorld {
- public static void main(String[] args) {
- // f1 变量是父类类型, 指向子类实例, 发生多态
- Figure f1 = new Triangle();
- f1.onDraw();
- // f2 变量是父类类型, 指向子类实例, 发生多态
- Figure f2 = new Ellipse();
- f2.onDraw();
- }
- }
上述代码中实例化两个具体类 Triangle 和 Ellipse, 对象 f1 和 f2 是 Figure 引用类型.
声明抽象方法会造成以下两个结果:
如果一个类包含抽象方法, 那么该类必须是抽象类.
任何子类必须重写父类的抽象方法, 或者声明自身为抽象类.
Tips: 抽象类不能被实例化, 只有具体类才能被实例化.
4. 抽象类总结规定:
抽象类不能被实例化(初学者很容易犯的错), 如果被实例化, 就会报错, 编译无法通过. 只有抽象类的非抽象子类可以创建对象.
抽象类中不一定包含抽象方法, 但是有抽象方法的类必定是抽象类.
抽象类中的抽象方法只是声明, 不包含方法体, 就是不给出方法的具体实现也就是方法的具体功能.
构造方法, 类方法 (用 static 修饰的方法) 不能声明为抽象方法.
抽象类的子类必须给出抽象类中的抽象方法的具体实现, 除非该子类也是抽象类.
二, 使用接口
接口(英文: Interface), 在 JAVA 编程语言中是一个抽象类型, 是抽象方法的集合, 接口通常以 interface 来声明. 一个类通过继承接口的方式, 从而来继承接口的抽象方法.
接口并不是类, 编写接口的方式和类很相似, 但是它们属于不同的概念. 类描述对象的属性和方法. 接口则包含类要实现的方法.
除非实现接口的类是抽象类, 否则该类要定义接口中的所有方法.
接口无法被实例化, 但是可以被实现. 一个实现接口的类, 必须实现接口内所描述的所有方法, 否则就必须声明为抽象类. 另外, 在 Java 中, 接口类型可用来声明一个变量, 他们可以成为一个空指针, 或是被绑定在一个以此接口实现的对象.
1. 接口声明和实现
在 Java 中接口的声明使用的关键字是 interface, 声明接口 Figure 示例代码如下:
- //Figure.java 文件
- package com.Kevin;
- public interface Figure {
- // 接口中静态成员变量
- String name = "几何图形";// 省略 public static final
- // 绘制几何图形方法
- void onDraw(); // 省略 public
- }
代码第 4 行是声明 Figure 接口, 声明接口使用 interface 关键字, interface 前面的修饰符是 public 或省略. public 是公有访问级别, 可以在任何地方访问. 省略是默认访问级别, 只能在当前包中访问.
代码第 6 行声明接口中的成员变量, 在接口中成员变量都静态成员变量, 即省略了 public static final 修饰符. 代码第 99 行是声明抽象方法, 即省略了 public 关键字.
某个类实现接口时, 要在声明时使用 implements 关键字, 当实现多个接口之间用逗号 (,) 分隔. 实现接口时要实现接口中声明的所有方法.
实现接口 Figure 示例代码如下:
- //Ellipse.java 文件
- package com.Kevin.imp;
- import com.Kevin.Figure;
- // 几何图形椭圆形
- public class Ellipse implements Figure {
- // 绘制几何图形方法
- @Override
- public void onDraw() {
- System.out.println("绘制椭圆形...");
- }
- }
- //Triangle.java 文件
- package com.Kevin.imp;
- import com.Kevin.Figure;
- // 几何图形三角形
- public class Triangle implements Figure {
- // 绘制几何图形方法
- @Override
- public void onDraw() {
- System.out.println("绘制三角形...");
- }
- }
上述代码声明了两个具体类 Ellipse 和 Triangle, 它们实现了接口 Figure 中的抽象方法 onDraw.
调用代码如下:
- //HelloWorld.java 文件
- import com.Kevin.imp.Ellipse;
- import com.Kevin.imp.Triangle;
- public class HelloWorld {
- public static void main(String[] args) {
- // f1 变量是父类类型, 指向子类实例, 发生多态
- Figure f1 = new Triangle();
- f1.onDraw();
- System.out.println(f1.name);
- System.out.println(Figure.name);
- // f2 变量是父类类型, 指向子类实例, 发生多态
- Figure f2 = new Ellipse();
- f2.onDraw();
- }
- }
上述代码中实例化两个具体类 Triangle 和 Ellipse, 对象 f1 和 f2 是 Figure 接口引用类型. 接口 Figure 中声明了成员变量, 它是静态成员变量, 代码第 12 行和第 13 行是访问 name 静态变量.
Tips: 接口与抽象类一样都不能被实例化.
2. 接口与多继承
在 C++ 语言中一个类可以继承多个父类, 但这会有潜在的风险, 如果两个父类在有相同的方法, 那么子类将继承哪一个父类方法呢? 这就是 C++ 多继承所导致的冲突问题.
在 Java 中只允许继承一个类, 但可实现多个接口. 通过实现多个接口方式满足多继承的设计需求. 如果多个接口中即便有相同方法, 它们也都是抽象的, 子类实现它们不会有冲突. 如下图所示是多继承类图, 其中的有两个接口 InterfaceA 和 InterfaceB, 从类图中可以见两个接口中都有一个相同的方法 void methodB().AB 实现了这两个接口, 继承了 Object 父类.
接口 InterfaceA 和 InterfaceB 代码如下:
- //InterfaceA.java 文件
- package com.Kevin;
- public interface InterfaceA {
- void methodA();
- void methodB();
- }
- //InterfaceB.java 文件
- package com.Kevin;
- public interface InterfaceB {
- void methodB();
- void methodC();
- }
从代码中可见两个接口都有两个方法, 其中方法 methodB()完全相同.
实现接口 InterfaceA 和 InterfaceB 的 AB 类代码如下:
- //AB.java 文件
- package com.Kevin.imp;
- import com.Kevin.InterfaceA;
- import com.Kevin.InterfaceB;
- public class AB extends Object implements InterfaceA, InterfaceB {
- @Override
- public void methodC() {
- }
- @Override
- public void methodA() {
- }
- @Override
- public void methodB() {
- }
- }
在 AB 类中的代码第 18 行实现 methodB()方法. 注意在 AB 类声明时, 实现两个接口, 接口之间使用逗号 (,) 分隔, 见代码第 7 行.
3. 接口继承
Java 语言中允许接口和接口之间继承. 由于接口中的方法都是抽象方法, 所以继承之后也不需要做什么, 因此接口之间的继承要比类之间的继承简单的多. 如下图所示, 其中 InterfaceB 继承了 InterfaceA, 在 InterfaceB 中还覆盖了 InterfaceA 中的 methodB()方法. ABC 是 InterfaceB 接口的实现类, 从下图中可见 ABC 需要实现 InterfaceA 和 InterfaceB 接口中的所有方法.
接口 InterfaceA 和 InterfaceB 代码如下:
- //InterfaceA.java 文件
- package com.Kevin;
- public interface InterfaceA {
- void methodA();
- void methodB();
- }
- //InterfaceB.java 文件
- package com.Kevin;
- public interface InterfaceB extends InterfaceA {
- @Override
- void methodB();
- void methodC();
- }
InterfaceB 继承了 InterfaceA, 声明时也使用 extends 关键字. InterfaceB 中的 methodB()覆盖了 InterfaceA, 事实上在接口中覆盖方法, 并没有实际意义, 因为它们都是抽象的, 都是留给子类实现的.
实现接口 InterfaceB 的 ABC 类代码如下:
- //ABC.java 文件
- package com.Kevin.imp;
- import com.Kevin.InterfaceB;
- public class ABC implements InterfaceB {
- @Override
- public void methodA() {
- }
- @Override
- public void methodB() {
- }
- @Override
- public void methodC() {
- }
- }
ABC 类实现了接口 InterfaceB, 事实上是实现 InterfaceA 和 InterfaceB 中所有方法, 相当于同时实现 InterfaceA 和 InterfaceB 接口.
4. 标记接口
最常用的继承接口是没有包含任何方法的接口.
标记接口是没有任何方法和属性的接口. 它仅仅表明它的类属于一个特定的类型, 供其他代码来测试允许做一些事情.
标记接口作用: 简单形象的说就是给某个对象打个标(盖个戳), 使对象拥有某个或某些特权.
例如: java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:
- package java.util;
- public interface EventListener
- {}
没有任何方法的接口被称为标记接口. 标记接口主要用于以下两种目的:
建立一个公共的父接口:
正如 EventListener 接口, 这是由几十个其他接口扩展的 Java API, 你可以使用一个标记接口来建立一组接口的父接口. 例如: 当一个接口继承了 EventListener 接口, Java 虚拟机 (JVM) 就知道该接口将要被用于一个事件的代理方案.
向一个类添加数据类型:
这种情况是标记接口最初的目的, 实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法), 但是该类通过多态性变成一个接口类型.
三, Java8 新特性(默认方法和静态方法)
在 Java 8 之前, 尽管 Java 语言中接口已经非常优秀了, 但相比其他面向对象的语言而言 Java 接口存在如下不足之处:
不能可选实现方法, 接口的方法全部是抽象的, 实现接口时必须全部实现接口中方法, 哪怕是有些方法并不需要, 也必须实现.
没有静态方法.
针对这些问题, Java 8 在接口中提供了声明默认方法和静态方法的能力. 接口示例代码如下:
- //InterfaceA.java 文件
- package com.Kevin;
- public interface InterfaceA {
- void methodA();
- String methodB();
- // 默认方法
- default int methodC() {
- return 0;
- }
- // 默认方法
- default String methodD() {
- return "这是默认方法...";
- }
- // 静态方法
- static double methodE() {
- return 0.0;
- }
- }
在接口 InterfaceA 中声明了两个抽象方法 methodA 和 methodB, 两个默认方法 methodC 和 methodD, 还有声明了静态方法 methodE. 接口中的默认方法类似于类中具体方法, 给出了具体实现, 只是方法修饰符是 default. 接口中静态方法类似于类中静态方法.
实现接口示例代码如下:
- //ABC.java 文件
- package com.Kevin.imp;
- import com.Kevin.InterfaceA;
- public class ABC implements InterfaceA {
- @Override
- public void methodA() {
- }
- @Override
- public String methodB() {
- return "实现 methodB 方法...";
- }
- @Override
- public int methodC() {
- return 500;
- }
- }
实现接口时接口中原有的抽象方法在实现类中必须实现. 默认方法可以根据需要有选择实现(覆盖). 静态方法不需要实现, 实现类中不能拥有接口中的静态方法.
上述代码中 ABC 类实现了 InterfaceA 接口, InterfaceA 接口中的两个默认方法 ABC 只是实现 (覆盖) 了 methodB.
调用代码如下:
- //HelloWorld.java 文件
- package com.Kevin.imp;
- import com.Kevin.InterfaceA;
- public class HelloWorld {
- public static void main(String[] args) {
- // 声明接口类型, 对象是实现类, 发生多态
- InterfaceA abc = new ABC();
- // 访问实现类 methodB 方法
- System.out.println(abc.methodB());
- // 访问默认方法 methodC
- System.out.println(abc.methodC());
- // 访问默认方法 methodD
- System.out.println(abc.methodD());
- // 访问 InterfaceA 静态方法 methodE
- System.out.println(InterfaceA.methodE());
- }
- }
运行结果:
实现 methodB 方法...
500
这是默认方法...
0.0
从运行结果可见, 代码第 17 行调用默认方法 methodC, 是调用类 AB 中的实现. 代码第 20 行调用默认方法 methodD, 是调用接口 InterfaceA 中的实现. 代码第 23 行调用接口静态方法, 只能通过接口名 (InterfaceA) 调用, 不能通过实现类 ABC 调用, 可以这样理解接口中声明的静态方法与其他实现类没有任何关系.
Tips: 学习了接口默认方法后, 有些读者还会有这样的疑问, Java 8 之后接口可以声明抽象方法和具体方法, 这就相当于抽象类一样了吗? 在多数情况下接口不能替代抽象类, 例如当需要维护一个对象的信息和状态时只能使用抽象类, 而接口不行, 因为维护一个对象的信息和状态需要存储在实例成员变量中, 而接口中不能声明实例成员变量.
四, 总结
1 接口与类的相似点:
一个接口可以有多个方法.
接口文件保存在 .java 结尾的文件中, 文件名使用接口名.
接口的字节码文件保存在 .class 结尾的文件中.
接口相应的字节码文件必须在与包名称相匹配的目录结构中.
2 接口与类的区别:
接口不能用于实例化对象.
接口没有构造方法.
接口中所有的方法必须是抽象方法.
接口不能包含成员变量, 除了 static 和 final 变量.
接口不是被类继承了, 而是要被类实现.
接口支持多继承.
3 接口特性:
接口中每一个方法也是隐式抽象的, 接口中的方法会被隐式的指定为 public abstract(只能是 public abstract, 其他修饰符都会报错).
接口中可以含有变量, 但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public, 用 private 修饰会报编译错误).
接口中的方法是不能在接口中实现的, 只能由实现接口的类来实现接口中的方法.
4 接口与抽象类的区别:
抽象类中的方法可以有方法体, 就是能实现方法的具体功能, 但是接口中的方法不行.
抽象类中的成员变量可以是各种类型的, 而接口中的成员变量只能是 public static final 类型的.
一个类只能继承一个抽象类, 而一个类却可以实现多个接口.
5 什么时候使用抽象类和接口
如果你拥有一些方法并且想让它们中的一些有默认实现, 那么使用抽象类吧.
如果你想实现多重继承, 那么你必须使用接口. 由于 Java 不支持多继承, 子类不能够继承多个类, 但可以实现多个接口. 因此你就可以使用接口来解决它.
如果基本功能在不断改变, 那么就需要使用抽象类. 如果不断改变基本功能并且使用接口, 那么就需要改变所有实现了该接口的类.
来源: https://www.cnblogs.com/Kevin-ZhangCG/p/8934259.html