Java抽象类

参考资料

[1]. 疯狂Java讲义(第三版) 李刚

抽象类

抽象方法和抽象类

抽象类可以继承非抽象类
基本规则

// 抽象类必须使用abstract修饰符来定义,即成为抽象类
// 这个类里面可以没有抽象方法
public abstract class Shape {
    // 抽象方法必须使用abstract修饰符来修饰,且不能有方法体
    public abstract void draw(Canvas c);
}

// 只要使用了abstract修饰符,这个类就无法被实例化
// 基本成员与普通类一样,但主要用于被其子类实现

下面定义一个Shape抽象类,用于被其他类继承:

/**
 * AbstractShape class
 *
 * @author shuaige
 * @date 2018/01/22
 */
public abstract class AbstractShape {

    /**
     * 初始化块
     */
    {
        System.out.println("执行Shape的初始块");
    }
    /**
     * color 颜色
     */
    private String color;
    /**
     * 定义一个计算周长的抽象方法
     *
     * @return 周长
     */
    public abstract double calPerimeter();

    /**
     * 定义一个返回形状的抽象方法
     * @return 返回形状
     */
    public abstract String getType();

    /**
     * 定义Shape的构造器,该构造器并不是用于创建Shape对象,而是被子类调用
     */
    public AbstractShape(){}

    /**
     * 构造器
     * @param color 颜色
     */
    public AbstractShape(String color)
    {
        System.out.println("执行Shape的构造器...");
        this.color = color;
    }

    /**
     * 返回颜色
     * @return 颜色
     */
    public String getColor()
    {
        return color;
    }

    /**
     * 设置颜色
     * @param color 颜色
     */
    public void setColor(String color)
    {
        this.color = color;
    }
}

声明一个Triangle子类,用于实现上面的父类:

/**
 * Triangle class
 *
 * @author shuaige
 * @date 2018/01/22
 */
public class Triangle extends AbstractShape {

    /**
     * 三角形的a边
     */
    private double a;
    /**
     * 三角形的b边
     */
    private double b;
    /**
     * 三角形的c边
     */
    private double c;

    /**
     * 构造器
     * @param color 颜色
     * @param a 三角形的a边
     * @param b 三角形的b边
     * @param c 三角形的c边
     */
    public Triangle(String color, double a, double b, double c)
    {
        super(color);
        this.setSides(a, b, c);
    }

    /**
     * 设置边
     * @param a 三角形的a边
     * @param b 三角形的b边
     * @param c 三角形的c边
     */
    public void setSides(double a, double b, double c)
    {
        if (a >= b+c || b >= a+c || c >= a+b)
        {
            System.out.println("三角形的两边之和必须大于第三边");
            return;
        }
        // 如果符合三角形的规则,则设置下面的值
        this.a = a;
        this.b = b;
        this.c = c;
    }

    /**
     * 重写Shape类的计算周长的抽象方法
     * @return 返回周长
     */
    @Override
    public double calPerimeter() {
        return a + b + c;
    }

    /**
     * 重写Shape类的返回形状的抽象方法
     * @return 返回形状
     */
    @Override
    public String getType() {
        return "三角形";
    }
}

声明一个Circle类,用于实现上面的父类:

/**
 * Circle class
 *
 * @author shuaige
 * @date 2018/01/22
 */
public class Circle extends AbstractShape {
    /**
     * 半径
     */
    private double radius;

    /**
     * 构造器
     * @param color 颜色
     * @param radius 半径
     */
    public Circle(String color, double radius)
    {
        super(color);
        this.radius = radius;
    }

    /**
     * 设置半径
     * @param radius 半径
     */
    public void setRadius(double radius)
    {
        this.radius = radius;
    }

    /**
     * 计算周长
     * @return 返回周长
     */
    @Override
    public double calPerimeter() {
        return 2 * Math.PI * radius;
    }

    /**
     * 返回形状
     * @return 形状
     */
    @Override
    public String getType() {
        return getColor() + "圆形";
    }

    /**
     * 实例化过程
     * @param args
     */
    public static void main(String[] args)
    {
        // 三角形
        AbstractShape shape1 = new Triangle("黑色", 3, 4, 5);
        System.out.println(shape1.getType());
        System.out.println(shape1.calPerimeter());


        // 圆形
        AbstractShape shape2 = new Circle("黄色", 3);
        System.out.println(shape2.getType());
        System.out.println(shape2.calPerimeter());
    }
}

上面的main()方法中定义了两个Shape类型的引用变量,它们分别指向Triangle对象和Circle对象。由于在Shape类中定义了calPerimeter()方法和getType()方法,无须强制类型转换为其子类型。
利用抽象类和抽象方法的优势,可以更好的发挥多态的优势,使程序更加灵活。
abstract不能用于修饰成员变量,不能用于修饰局部变量,即没有抽象变量、抽象成员变量的说法;abstract也不能用于修饰构造器,没有抽象构造器,抽象类里定义的构造器只能是普通的构造器。

抽象类的作用

抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。
父类:

/**
 * CarSpeedMeter class
 *
 * @author shuaige
 * @date 2018/01/22
 */
public abstract class AbstractSpeedMeter {
    /**
     * trunRate 转速
     */
    private double trunRate;
    public AbstractSpeedMeter()
    {

    }

    /**
     * 把返回车轮半径的方法定义成抽象方法
     *
     * @return null
     */
    public abstract double getRadius();

    public void setTrunRate(double trunRate)
    {
        this.trunRate = trunRate;
    }


    /**
     * 定义计算速度的通用算法
     *
     * @return 速度等于 车轮半径  * 2 * PI * 转速
     */
    public double getSpeed()
    {
        return Math.PI * 2 * getRadius() * trunRate;
    }
}

实现了父类的子类:

package com.qunar;


/**
 * CarSpeedMeter class
 *
 * @author shuaige
 * @date 2018/01/22
 */
public class CarSpeedMeter extends AbstractSpeedMeter{


    @Override
    public double getRadius() {
        return 0.28;
    }

    public static void main(String[] args)
    {
        CarSpeedMeter csm = new CarSpeedMeter();
        csm.setTrunRate(15);
        System.out.println(csm.getSpeed());
    }
}

抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给其子类去实现。
父类中可能包含需要调用其他系列方法的方法,这些被调用方法既可以由父类实现,也可以由其子类实现,父类里提供的方法只是定义了一个通用的算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。

Java 8 改进的接口

接口(interface),接口里不能包含普通方法,接口里的所有方法都是抽象方法,。Java 8对接口进行了改进,运行在接口中定义默认方法,默认方法可以提供方法实现。

Java 8中接口的定义

基本语法如下:

[修饰符] interface 接口名 extends 父接口1, 父接口2...
{
    零到多个常量定义...
    零到多个抽象方法定义...
    零到多个内部类、接口、枚举定义...
    零到多个默认方法或类方法定义...
}

语法说明如下:
1. 修饰符可以是public或者省略,如果省略了public访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下才可以访问该接口。
2. 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
不管是否使用public static final修饰符,接口里的成员变量总是使用这三个修饰符来修饰。接口里没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值。
下面两个变量效果一样。

int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;

系统自动为普通方法增加public abstract修饰符,因此下面两个方法效果一样。

int size();
public abstract int size();

默认方法必须使用default修饰,并且无论是否指定,系统总会默认使用public修饰。
类方法必须使用static方法修饰,并且无论是否指定,系统总会默认使用public修饰。
下面定义一个接口示例:

/**
 * 示范接口 Output
 *
 * @author shuaige
 * @date 2018/01/23
 */
public interface Output {
    /**
     * 接口里定义的成员变量只能是常量
     * 这个变量是一个类变量,因为默认修饰为public static final
     */
    int MAX_CACHE_LINE = 50;
    /**
     * 接口里定义的普通方法只能是public的抽象方法
     */
    void out();

    /**
     * 接口里定义的普通方法只能是public的抽象方法
     * @param msg 字符串参数
     */
    void getData(String msg);

    /**
     * 在接口中定义默认方法,需要使用default修饰
     * @param msgs 字符串参数
     */

    default void print(String... msgs)
    {
        for (String msg : msgs)
        {
            System.out.println(msg);
        }
    }

    /**
     * 在接口中定义默认方法,需要使用default修饰
     */
    default void test()
    {
        System.out.println("默认的test()方法");
    }

    /**
     * 接口中定义的类方法,需要使用static修饰
     * @return
     */
    static String staticTest()
    {
        return "接口里的类方法";
    }
}

调用

// 访问接口里的常量
System.out.println(Output.MAX_CACHE_LINE);
// 下面语句将引发错误
// Cannot assign a value to final variable'MAX_CACHE_LINE'
// Output.MAX_CACHE_LINE = 100;
// 调用默认类
System.out.println(Output.staticTest());

接口的继承

接口支持多继承,子接口扩展某个父接口后,将会获得父接口里定义的所有抽象方法和变量。
interfaceA 接口

/**
 * interfaceA interface
 *
 * @author shuaige
 * @date 2018/01/23
 */
public interface InterfaceA {
    /**
     * PROP_A常量
     */
    int PROP_A = 5;

    /**
     * testA方法
     */
    void testA();
}

interfaceB接口

/**
 * interfaceB interface
 *
 * @author shuaige
 * @date 2018/01/23
 */
public interface InterfaceB {
    /**
     * PROP_B常量
     */
    int PROP_B = 6;
    /**
     * testB方法
     */
    void testB();
}

interfaceC接口

/**
 * interfaceC interface
 *
 * @author shuaige
 * @date 2018/01/23
 */
public interface InterfaceC extends InterfaceA, InterfaceB{
    /**
     * PROP_C常量
     */
    int PROP_C = 6;

    /**
     * testC方法
     */
    void testC();

}

现在来使用interfaceC来调用A和B的接口内的常量

System.out.println(InterfaceC.PROP_A);
System.out.println(InterfaceC.PROP_B);
System.out.println(InterfaceC.PROP_C);

使用接口

接口不能用于创建实例,但接口可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象。除此之外,接口的主要用途就是被实现类实现。接口的用途归纳如下:
1. 定义引用型变量,也可用于进行强制类型转换。
2. 调用接口中定义的常量。
3. 被其他类实现。

类实现接口的语法格式如下:

[修饰符] class 类名 extends 父类 implements 接口1, 接口2...
{
    类体部分
}

下面进行一个实例
接口Product

/**
 * Product interface
 *
 * @author shuaige
 * @date 2018/01/23
 */
public interface Product {
    /**
     * 返回时间
     * @return 返回整数
     */
    int getProduceTime();
}

当子类实现了接口以后,还可以引用变量给父类,这样就可以使多个实现的子类之间兼容。

/**
 * Printer interface
 * 实现了Output和Product接口
 * @author shuaige
 * @date 2018/01/23
 */
public class Printer implements Output, Product{

    private String[] printData = new String[MAX_CACHE_LINE];

    /**
     * 用以记录当前需打印的作业数
     */
    private int dataNum = 0;

    /**
     * 输出
     */
    @Override
    public void out() {
        // 只要还有作业,就继续打印
        while(dataNum > 0)
        {
            System.out.println("打印机打印:" + printData[0]);
            // 把作业队列整体前移一位,并将剩下的作业数减1
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }

    /**
     *
     * @param msg 字符串参数
     */
    @Override
    public void getData(String msg) {
        if (dataNum >= MAX_CACHE_LINE)
        {
            System.out.println("输出队列已满,添加失败");
        }else{
            // 把打印数据添加到队列里,已保存数据的数量加1
            printData[dataNum++] = msg;
        }
    }

    /**
     * 返回整数
     * @return
     */
    @Override
    public int getProduceTime() {
        return 45;
    }

    public static void main(String[] args)
    {
        // 创建一个Printer对象,当成Output使用
        Output o = new Printer();
        o.getData("a");
        o.getData("b");
        o.out();
        o.getData("c");
        o.getData("d");
        o.out();
        // 调用Output接口中定义的默认方法
        o.print("1", "2", "3");
        o.test();
        // 创建一个Printer对象,当成Product使用
        Product p = new Printer();
        System.out.println(p.getProduceTime());
        // 所有接口类型的引用变量都可直接赋给Object类型的变量
        Object obj = p;
    }
}

从上面程序中可以看出,Printer类实现了Output接口和Product接口,因此Printer对象既可直接赋给Output变量,也可直接赋给Product变量。仿佛Printer类既是Object类的子类,也是Product类的子类,这就是Java提供的模拟多继承。

面向接口编程

接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好的降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。

简单工厂模式

实现类

/**
 * Computer class
 * 接受注入的Output类型class,然后调用它的方法
 * @author shuaige
 * @date 2018/01/23
 */
public class Computer {
    /**
     * Output接口
     */
    private Output out;


    /**
     *
     * @param out Output接口
     */
    public Computer(Output out)
    {
        this.out = out;
    }

    /**
     * 定义一个模拟获取字符串输入的方法
     * @param msg
     */
    public void keyIn(String msg)
    {
        out.getData(msg);
    }

    /**
     * 定义一个模拟打印的方法
     */
    public void print()
    {
        out.out();
    }
}

工厂类

/**
 * OutputFactory class
 * 工厂类,获取Output类型的类
 * @author shuaige
 * @date 2018/01/23
 */
public class OutputFactory {

    /**
     * 返回一个Output接口的实现类
     * @return Output接口的实现类
     */
    public Output getOutput()
    {
        return new Printer();
    }

    public static void main(String[] args)
    {
        // 工厂类,获取Output类型的类
        OutputFactory of = new OutputFactory();
        // 注入到实现类,供实现类调用
        Computer c = new Computer(of.getOutput());
        // 调用keyIn方法
        c.keyIn("a");
        // 调用print方法
        c.print();
    }
}

类之间的关联关系图如下:
这里写图片描述
工厂类组合输出类,计算机类又依赖工厂类。
假设现在Printer类废弃了,现在新增了一个BetterPrinter实现类,只需要把OutputFactory里面的getOutput()方法即可。

/**
 * OutputFactory class
 * 工厂类,获取Output类型的类
 * @author shuaige
 * @date 2018/01/23
 */
public class OutputFactory {
    /**
     * 返回一个Output接口的实现类
     * @return Output接口的实现类
     */
    public Output getOutput()
    {
        return new BetterPrinter();
    }

    ...
}

BetterPrinter实现类也需要实现Output接口,代码如下:

package com.qunar;
/**
 * BetterPrinter class
 * 实现了Output和Product接口
 * @author shuaige
 * @date 2018/01/23
 */
public class BetterPrinter implements Output{
    /**
     * 打印数量
     */
    private String[] printData = new String[MAX_CACHE_LINE * 2];
    /**
     * 用以记录当前需打印的作业数
     */
    private int dataNum = 0;

    /**
     * 输出
     */
    @Override
    public void out() {
        // 只要还有作业,就继续打印
        while(dataNum > 0)
        {
            System.out.println("高速打印机正在打印:" + printData[0]);
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }

    /**
     * 获取数据
     * @param msg 字符串参数
     */
    @Override
    public void getData(String msg) {
        if (dataNum >= MAX_CACHE_LINE * 2)
        {
            System.out.println("输出队列已满,添加失败");
        }else {
            // 把打印数据添加到队列中,已保存数据的数量加1
            printData[dataNum++] = msg;
        }
    }
}

现在类图结构如下:
这里写图片描述

命令模式

假设某个方法需要完成某一个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定,示例代码如下:
接口Commond :

/**
 * Commond interface
 *
 * @author shuaige
 * @date 2018/01/23
 */
public interface Commond {
    /**
     * 接口里定义的process方法用于封装“处理行为”
     * @param target
     */
    void process(int[] target);
}

实现类PrintCommand:

/**
 * PrintCommand class
 *
 * @author shuaige
 * @date 2018/01/23
 */
public class PrintCommand implements Commond{
    /**
     * 处理数组
     * @param target
     */
    @Override
    public void process(int[] target)
    {
        for (int tmp : target)
        {
            System.out.println("迭代输出目标数组的元素:" + tmp);
        }
    }
}

实现类AddCommand:

/**
 * AddCommand class
 *
 * @author shuaige
 * @date 2018/01/23
 */
public class AddCommand implements Commond{
    /**
     * 处理数组
     * @param target
     */
    @Override
    public void process(int[] target)
    {
        // 设置初始值
        int sum = 0;
        for (int tmp : target)
        {
            // 遍历相加所有元素
            sum += tmp;
        }
        System.out.println("数组元素的总和是:" + sum);
    }
}

组合类:

/**
 * ProcessArray class
 *
 * @author shuaige
 * @date 2018/01/23
 */
public class ProcessArray {
    /**
     * 组合Commond实现和传入的int[]参数
     * @param target
     * @param cmd
     */
    public void process(int[] target, Commond cmd)
    {
        cmd.process(target);
    }
}

调用示例:

/**
 * CommandTest class
 *
 * @author shuaige
 * @date 2018/01/23
 */
public class CommandTest {
    public static void main(String[] args)
    {
        ProcessArray pa = new ProcessArray();
        int[] target = {3, -4, 6, 4};
        // 第一次处理数组,具体行为取决于PrintCommand
        pa.process(target, new PrintCommand());
        System.out.println("---------");
        // 第一次处理数组,具体行为取决于AddCommand
        pa.process(target, new AddCommand());

    }
}

Commond是接口,然后PrintCommand类和AddCommand类都实现了它的process方法,最后由ProcessArray类组合PrintCommand类或者AddCommand类和数组就可以实现命令模式,即传入的是哪个处理类,就执行哪个处理类的逻辑。
类图如下:
这里写图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值