该系列博文会告诉你如何从入门到进阶, 一步步地学习 Java 基础知识, 并上手进行实战, 接着了解每个 Java 知识点背后的实现原理, 更完整地了解整个 Java 技术体系, 形成自己的知识框架.
1, 抽象类:
当编写一个类时, 常常会为该类定义一些方法, 这些方法用以描述该类的行为方式, 那么这些方法都有具体的方法体. 但在某些情况下, 某个父类只是知道其子类应该包含怎样的方法, 但无法准确地知道这些子类如何实现这些方法. 例如定义了一个 Shape 类, 这个类应该提供一个计算周长的方法 calPerimeter(), 但不同 Shape 子类对周长的计算方法是不一样的, 即 Shape 类无法准确地知道其子类计算周长的方法.
可能有读者会提出, 既然 Shape 类不知道如何实现 calPerimeter()方法, 那就干脆不要管它了! 这不是一个好思路: 假设有一个 Shape 引用变量, 该变量实际上引用到 Shape 子类的实例, 那么这个 Shape 变量就无法调用 calPerimeter()方法, 必须将其强制类型转换为其子类类型, 才可调用 calPerimeter0 方法, 这就降低了程序的灵活性.
如何既能让 Shape 类里包含 calPerimeter()方法, 又无须提供其方法实现呢? 使用抽象方法即可满足该要求: 抽象方法是只有方法签名, 没有方法实现的方法.
定义:
抽象方法和抽象类必须使用 abstract 修饰符来定义, 有抽象方法的类只能被定义成抽象类, 抽象类里面可以没有抽象方法.
抽象方法和抽象类的规则如下:
抽象类必须使用 abstract 修饰符来修饰, 抽象方法也必须使用 abstract 修饰符来修饰, 抽象方法不能有方法体.
抽象类不能被实例化, 无法使用 new 关键字来调用抽象类的构造器创建抽象类的实例. 即使抽象类里不包含抽象方法, 这个抽象类也不能创建实例.
抽象类可以包含成员变量, 方法(普通方法和抽象方法都可以), 构造器, 初始化块, 内部类(接口, 枚举)5 种成分. 抽象类的构造器不能用于创建实例, 主要是用于被其子类调用.
含有抽象方法的类 (包括直接定义了一个抽象方法; 或继承了一个抽象父类, 但没有完全实现父类包含的抽象方法; 或实现了一个接口, 但没有完全实现接口包含的抽象方法三种情况) 只能被定义成抽象类.
说了一大堆概念, 听得有点糊涂了, 下面我们来看一段代码:
下面定义一个 Shape 抽象类:
- public abstract class Shape {
- {
- System.out.println("执行 Shape 的初始化块");
- }
- private String color;
- // 定义一个计算周长的抽象方法
- public abstract double calPerimeter();
- // 定义一个返回形状的抽象方法
- public abstract String getType();
- // 定义 Shape 的构造器, 该构造器并不是用于创建对象, 而是被子类调用
- public Shape() {}
- public Shape(String color) {
- System.out.println("执行 Shape 的构造器");
- this.color=color;
- }
- public String getColor() {
- return color;
- }
- public void setColor(String color) {
- this.color = color;
- }
- }
上面的 Shape 类里包含了两个抽象方法: calPerimeter()和 getType(), 所以这个 Shape 类只能被定义成抽象类. Shape 类里既包含了初始化块, 也包含了构造器, 这些都不是在创建 Shape 对象时被调用的, 而是在创建其子类的实例时被调用.
抽象类不能用作创建实例, 只能当做父类被其他子类继承.
下面定义一个三角形类, 三角形类被定义成普通类, 继承 Shape 抽象类, 因此必须实现 Shape 类中的抽象方法
- public class Triangle extends Shape {
- // 定义三角形的三边
- private double a;
- private double b;
- private double c;
- public Triangle(String color,double a,double b,double c) {
- super(color);
- setSize(a, b, c);
- }
- public void setSize(double a,double b,double c) {
- if(a+b<=c||a+c<=b||b+c<=a) {
- System.out.println("三角形两边之和必须大于第三边");
- return;
- }
- this.a=a;
- this.b=b;
- this.c=c;
- }
- // 重写 Shape 类计算周长的方法
- @Override
- public double calPerimeter() {
- return a+b+c;
- }
- // 重写 Shape 类返回形状的方法
- @Override
- public String getType() {
- // TODO Auto-generated method stub
- return "三角形";
- }
- }
上面的 Triangle 类继承了 Shape 抽象类, 并实现了 Shape 类中两个抽象方法, 是一个普通类, 因此可以创建 Triangle 类的实例, 可以让一个 Shape 类型的引用变量指向 Triangle 对象.
下面编写测试代码:
- public class TestShape {
- public static void main(String[] args) {
- Shape s1=new Triangle("黑色", 3, 4, 5);
- System.out.println(s1.getColor());
- System.out.println(s1.getType());
- }
- }
输出结果:
执行 Shape 的初始化块
执行 Shape 的构造器
黑色
三角形
利用抽象类和抽象方法的优势, 可以更好的发挥多态的优势, 使得程序更加灵活.
使用抽象类有以下几点需要注意:
1, 当使用 abstract 修饰类时, 表明这个类时抽象类, 不能实例化, 只能被继承; 当使用 abstract 修饰方法时, 表明这个方法必须由子类去实现.
2,final 修饰的类不能被继承, final 修饰的方法不能被重写, 因此 final 和 abstract 不能同时出现.
3,abstract 不能用于修饰成员变量, 不能用于修饰局部变量, 即没有抽象变量, 没有抽象成员变量等说法; abstract 也不能用于修饰构造器, 没有抽象构造器, 抽象类里定义的构造器只能是普通构造器.
4, 当使用 static 修饰一个方法时, 表明这个方法属于该类本身, 即通过类就可调用该方法, 但如果该方法被定义成抽象方法, 则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误). 因此 static 和 abstract 不能同时修饰某个方法, 即没有所谓的类抽象方法.
5,abstract 修饰的方法没有方法体, 必须被之类重写才有意义, 所以抽象方法不能用 private 修饰, 也就是 private 和 abstract 不能同时使用.
抽象类的作用:
从前面的示例程序可以看出, 抽象类不能创建实例, 只能当成父类来被继承. 从语义的角度来看, 抽象类是从多个具体类中抽象出来的父类, 它具有更高层次的抽象. 从多个具有相同特征的类中抽象出一个抽象类, 以这个抽象类作为其子类的模板, 从而避免了子类设计的随意性.
抽象类体现的就是一种模板模式的设计, 抽象类作为多个子类的通用模板, 子类在抽象类的基础上进行扩展, 改造, 但子类总体上会大致保留抽象类的行为方式.
如果编写一个抽象父类, 父类提供了多个子类的通用方法, 并把一个或多个方法留给其子类实现, 这就是一种模板模式, 模板模式也是十分常见且简单的设计模式之一. 例如前面介绍的 Shape,Triangle 类, 已经使用了模板模式.
2, 接口
抽象类是从多个类中抽象出来的模板, 如果将这种抽象进行得更彻底, 则可以提炼出一种更加特殊的 "抽象类"-- 接口(interface).Java9 对接口进行了改进, 允许在接口中定义默认方法和类方法, 默认方法和类方法都可以提供方法实现, Java9 为接口增加了一种私有方法, 私有方法也可提供方法实
定义:
和类定义的不同, 定义接口不再使用 class 关键字, 而是使用 interface 关键字. 接口的基本语法如下:
[修饰符] interface 接口名称 extends 父接口 1 父接口 2 ....
{
零到多个常量定义...
零到多个抽象方法定义...
零到多个内部类, 接口, 枚举定义...
零到多个私有方法, 默认方法或者类方法定义...
}
修饰符可以是 public 或者省略, 如果省略了 public 访问控制符, 则默认采用包权限访问控制符, 即只有在相同包结构下才可以访问该接口.
接口名应与类名采用相同的命名规则, 即如果仅从语法角度来看, 接口名只要是合法的标识符即可; 如果要遵守 Java 可读性规范, 则接口名应由多个有意义的单词连缀而成, 每个单词首字母大写, 单词与单词之间无须任何分隔符. 接口名通常能够使用形容词.
一个接口可以有多个直接父接口, 但接口只能继承接口, 不能继承类.
接口中可以包含成员变量 (只能是静态常量), 方法(只能是抽象实例方法, 类方法, 默认方法或私有方法), 内部类(包括内部接口, 枚举) 定义
接口中定义的常量系统会自动为常量加上 static 和 final 两个修饰符
接口中定义的方法只能是抽象实例方法, 类方法, 默认方法或私有方法, 如果定义的不是类方法, 默认方法和私有方法, 系统将自动为普通方法增加 abstract 修饰符, 接口中的普通方法总是用 public abstract 来修饰, 但类方法, 默认方法, 私有方法都必须有方法体实现.
下面来看一个具体的接口:
- public interface Output {
- // 接口中定义的成员变量只能是常量
- int MAX_CACHE_LINE=50;
- // 接口中定义的普通方法只能是 public abstract 抽象方法
- void out();
- // 在接口中定义默认方法, 需要用 default 修饰
- default void print(String...msg) {
- for (String str : msg) {
- System.out.println(str);
- }
- }
- // 在接口中定义类方法, 需要使用 static 修饰
- static String staticTest() {
- return "接口中的类方法";
- }
- // 定义私有方法
- private void foo() {
- System.out.println("接口中的私有方法");
- }
- // 定义私有静态方法
- private static void bar() {
- System.out.println("bar 私有静态方法");
- }
- }
接口的继承:
接口的继承和类继承不一样, 接口完全支持多继承, 即一个接口可以有多个直接父接口. 和类继承相似, 子接口扩展某个父接口, 将会获得父接口里定义的所有抽象方法, 常量.
一个接口继承多个父接口时, 多个父接口排在 extends 关键字之后, 多个父接口之间以英文逗号 (,) 隔开. 下面程序定义了三个接口, 第三个接口继承了前面两个接口.
- interface InterfaceA{
- int PROP_A=5;
- void testA();
- }
- interface InterfaceB{
- int PROP_B=6;
- void testB();
- }
- interface InterfaceC extends InterfaceA,InterfaceB{
- int PROP_C=7;
- void testC();
- }
- public class InterfaceExtendsTest {
- public static void main(String[] args) {
- System.out.println(InterfaceC.PROP_A);
- System.out.println(InterfaceC.PROP_B);
- System.out.println(InterfaceC.PROP_C);
- }
- }
使用接口:
接口不能用于创建实例, 但接口可以用于声明引用类型变量. 当使用接口来声明引用类型变量时, 这个引用类型变量必须引用到其实现类的对象. 除此之外, 接口的主要用途就是被实现类实现. 归纳起来, 接口主要有如下用途.
定义变量, 也可用于进行强制类型转化
调用接口中定义的常量
被其他类实现
一个类可是实现多个接口, 用关键字 implements 实现, 类实现接口的语法格式如下:
[修饰符] class 类名 extends 父类 implements 接口 1, 接口 2...{
类体部分
}
注意: 类实现接口时必须要实现接口中所有的抽象方法
- interface interfaceA{
- void printA();
- }
- interface interfaceB{
- void printB();
- }
- public class ImplmentsTest implements interfaceA,interfaceB {
- @Override
- public void printB() {
- System.out.println("printB");
- }
- @Override
- public void printA() {
- System.out.println("printA");
- }
- }
上述代码的 ImplmentsTest 实现了两个接口, 并重写了其中的抽象方法
接口和抽象类的区别:
相同点:
接口和抽象类都不能被实例化, 它们都位于继承树的顶端, 用于被其他类实现和继承.
接口和抽象类都可以包含抽象方法, 实现接口或继承抽象类的普通子类都必须实现这些抽象方法.
不同点:
接口里只能包含抽象方法, 静态方法, 默认方法和私有方法, 不能为普通方法提供方法实现; 抽象类则完全可以包含普通方法.
接口里只能定义静态常量, 不能定义普通成员变量; 抽象类里则既可以定义普通成员变量, 也可以定义静态常量.
接口里不包含构造器; 抽象类里可以包含构造器, 抽象类里的构造器并不是用于创建对象, 而是让其子类调用这些构造器来完成属于抽象类的初始化操作.
接口里不能包含初始化块; 但抽象类则完全可以包含初始化块.
一个类最多只能有一个直接父类, 包括抽象类; 但一个类可以直接实现多个接口, 通过实现多个接口可以弥补 Java 单继承的不足.
来源: https://www.cnblogs.com/wgblog-code/p/11842701.html