【Java复健指南12】OOP高级03-抽象类与接口

抽象类

引出

问题:

​ 父类方法有时候具有不确定性

小结:

当父类的某些方法,需要声明,但是又不确定如何实现

时,可以将其声明为抽象方法,那么这个类就是抽象类

例子:

public class Abstract01 {
    public static void main(String[] args) {

    }
}

abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }
    //思考:这里eat 这里你实现了,其实没有什么意义
    //即: 父类方法不确定性的问题
    //===> 考虑将该方法设计为抽象(abstract)方法
    //===> 所谓抽象方法就是没有实现的方法
    //===> 所谓没有实现就是指,没有方法体
    //===> 当一个类中存在抽象方法时,需要将该类声明为abstract类
    //===> 一般来说,抽象类会被继承,有其子类来实现抽象方法.
//    public void eat() {
//        System.out.println("这是一个动物,但是不知道吃什么..");
//    }
    public abstract void eat()  ;
}

介绍

1)用abstract关键字来修饰一个类时,这个类就叫抽象类

​ 访问修饰符abstract类名{
​ }

2)用abstract关键字来修饰一个方法时,这个方法就是抽象方法

​ 访问修饰符 abstract 返回类型 方法名(参数列 表);//没有方法体

3)抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()

4)抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多

注意事项

1)抽象类不能被实例化

public class AbstractDetail01 {
    public static void main(String[] args) {
        //抽象类,不能被实例化
        //new A();
    }
}
abstract class A {
    
}

2)抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法

//抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法,还可以有实现的方法。
abstract class A {
    public void hi() {
        System.out.println("hi");
    }
}

3)一旦类包含了abstract方法,则这个类必须声明为abstract【反之不一定成立】

//一旦类包含了abstract方法,则这个类必须声明为abstract
abstract class B {
    public abstract void hi();
}

4)abstract只能修饰类和方法,不能修饰属性和其它的。

//abstract 只能修饰类和方法,不能修饰属性和其它的
class C {
   // public abstract int n1 = 1;
}

5)抽象类可以有任意成员【因为抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等

//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {
    public int n1 = 10;
    public static  String name = "jk";
    public void hi() {
        System.out.println("hi");
    }
    public abstract void hello();
    public static void ok() {
        System.out.println("ok");
    }
}

6)抽象方法不能有主体,即不能实现。

abstract void aaa()//{}

7)如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。

//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
abstract class E {
    public abstract void hi();
}
abstract class F extends E {

}
class G extends E {
    @Override
    public void hi() { //这里相等于G子类实现了父类E的抽象方法,所谓实现方法,就是有方法体
    }
}

8)抽象方法不能使用private、final和static来修

饰,因为这些关键字都是和重写相违背的

抽象模板模式

需求

1)有多个类,完成不同的任务job

2)要求能够统计各自完成任务的时间

3)请编程实现

直觉

先用最容易想到的方法

例如有TestTemplate、AA、BB三个类

在AA和BB中分别定义两种计算任务(比如1加到100和1乘到100)

以AA为例,那么其中会有一个job方法

该方法定义了具体的计算任务,并调用诸如System.currentTimeMillis()这样的模块计算运行时间

public void job() {
    //得到开始的时间
    long start = System.currentTimeMillis(;long num = 0;
    for (long i = 1; i <= 800000; i++) {
    	num += i;
    }
    //得的结束的时间
    long end = System.currentTimeMillis(;
    System.out.println("执行时间"+(end - start));
}

BB同理

问题

按上面的思路,AA和BB都会有一个job方法,里面计算时间的代码是重复的【重复点1】

这个好办,把这些代码抽象为一个父类即可,该父类中有一个例如calculateTime()的方法,专门计算运行时间

那么这个方法在该父类中应该类似下面的形式

public void calculateTime(){//实现一个方法去调用job
        long start = System.currentTimeMillis();
        job();//触发动态绑定机制,是aa调用就跳到AA,bb同理
        //获取结束时间
        long end = System.currentTimeMillis();
        System.out.println("AA执行时间:"+(end-start));
    }

现在又有一个问题,job()是AA和BB都有的方法,那么抽象到父类中,父类也肯定有一个job()

但是AA和BB中,job方法内的计算任务是不一样的,他们有可能都重写父类的方法,也可能不写

无论是哪种情况,父类都必须实例化一个默认的job()方法,这又造成代码的冗余【重复点2】

那么抽象类就有用了

将父类设计为一个抽象类,那么再写一个抽象方法job,抽象父类无需初始化它,子类用的时候再各自重写即可

优雅

解决

设计一个抽象类(Template),能完成如下功能:

1)编写方法calculateTime().可以计算某段代码的耗时时间

2)编写抽象方法job()

3)编写一个子类Sub,继承抽象类Template,并实现job方法。

4)编写一个测试类TestTemplate,看看是否好用。

抽象类Template

abstract public class Template {//抽象类-模板设计模式

    public abstract void job();//抽象方法

    public void calculateTime(){//实现一个方法去调用job
        long start = System.currentTimeMillis();
        job();//触发动态绑定机制,是aa调用就跳到AA,bb同理
        //获取结束时间
        long end = System.currentTimeMillis();
        System.out.println("AA执行时间:"+(end-start));
    }
}

子类AA

public class AA extends Template{
    //计算任务
    //1加到10000
    public void job(){//实现父类Template的抽象方法job
        int num = 0;
        for (int i = 0; i <= 800000; i++) {
            num += i;
        }
    }
}

子类BB

public class BB extends Template{
    //计算任务
    public void job(){
        int num = 0;
        for (int i = 0; i <= 8000000; i++) {
            num *= i;
        }
    }
}

测试

public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();

        BB bb = new BB();
        bb.calculateTime();
    }
}
=================================================
AA执行时间:2
BB执行时间:15

接口

定义

接口就是给出一些没有实现的方法,封装到一起,到某个

类要使用的时候,在根据具体情况把这些方法写出来。

语法

interface 接口名{

//属性
//方法
//    1.抽象方法
//    2.默认实现方法
//    3.静态方法

}
class 类名 implements 接口{
	自己属性;
	自己方法;
	必须实现的接口的抽象方法;

}

小结:
1.在Jdk7.0前,接口里的所有方法都没有方法体。

2 Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。

接口初体验

例如,在实际开发中,项目经理定义一些接口,由程序员去具体实现。【假设需求是连接数据库】

接口类
public interface DBInterface { //项目经理负责写的

    public void connect();//连接方法
    public void close();//关闭连接
}
MySQL类

具体去实现项目经理定义的接口

//假设这是A程序员的工作 
public class MysqlDB implements DBInterface {
    @Override
    public void connect() {//改一点名字都不行,必须与接口定义的一样
        System.out.println("连接mysql");
    }

    @Override
    public void close() {
        System.out.println("关闭mysql");
    }
}
Oracle类
//假设这是B程序员的工作,连接Oracle
public class OracleDB implements DBInterface{

    @Override
    public void connect() {
        System.out.println("连接oracle");
    }

    @Override
    public void close() {
        System.out.println("关闭oracle");
    }
}
使用
public class Interface03 {
    public static void main(String[] args) {

        MysqlDB mysqlDB = new MysqlDB();
        t(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        t(oracleDB);
    }

    public static void t(DBInterface db) {
        db.connect();
        db.close();
    }
}
=====================================
连接mysql
关闭mysql
连接oracle
关闭oracle
总结

在开发中使用接口可以统一程序员的工作成果,提高效率。

细节与注意事项

1)接口不能被实例化。

  • 因为接口本身是希望别的类来实现它,然后再创建实现了接口的具体类的实例
  • 因此接口不能实例化

2)接口中所有的方法是public方法。接口中抽象方法可以不用abstract修饰

3)一个普通类实现接口,就必须将该接口的所有方法都实现。

4)抽象类实现接口,可以不用实现接口的方法。

5)一个类同时可以实现多个接口

//一个类同时可以实现多个接口
class Pig implements IB,IC {
    @Override
    public void hi() {
    }
    @Override
    public void say() {
    }
}

6)接口中的属性只能是final的,而且是public

static final修饰符。

​ 比如:int a=1;

​ 实际上是public static final int a=1;(必须初始

7)接口中属性的访向形式:

接口名.属性名

8)一个接口不能继承其它的类,但是可以继承多个别的接口

//接口不能继承其它的类,但是可以继承多个别的接口
interface ID extends IB,IC {
}

9)接口的修饰符只能是public和默认,这点和类的修饰

符是一样的。

小练习

public class InterfaceExercise01 {
   public static void main(String[] args) {
       B b = new B();//ok
       System.out.println(b.a);  //b是对象实例,访问一个public的属性a,没问题,23
       System.out.println(A.a);  //接口.static类型的属性,没问题,23
       System.out.println(B.a);  //B实现了A接口,因此可以使用接口中的属性,没问题,23
   }
}

interface A {
   int a = 23; //接口中的属性等价于 public static final int a = 23;
}

class B implements A {//A接口没有抽象方法,因此此处语法正确
}

接口与继承

简单来说,接口是对Java单继承机制的一种补充

看下面的例子

LittleMonkey通过继承父类Monkey得到了climbing()方法

再通过接口,实现了swimming()和flying()。无形之中拓展了LittleMonkey的功能

public class ExtendsVsInterface {
    public static void main(String[] args) {
        LittleMonkey wuKong = new LittleMonkey("悟空");
        wuKong.climbing();
        wuKong.swimming();
        wuKong.flying();
    }
}

//猴子
class Monkey {
    private String name;

    public Monkey(String name) {
        this.name = name;
    }
    public void climbing() {
        System.out.println(name + " 会爬树...");
    }

    public String getName() {
        return name;
    }
}

//接口
interface Fishable {
    void swimming();
}
interface Birdable {
    void flying();
}

//继承
//小结:  当子类继承了父类,就自动的拥有父类的功能
//      如果子类需要扩展功能,可以通过实现接口的方式扩展.
//      可以理解 实现接口 是 对java 单继承机制的一种补充.
class LittleMonkey extends Monkey implements Fishable,Birdable {

    public LittleMonkey(String name) {
        super(name);
    }

    @Override
    public void swimming() {
        System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
    }

    @Override
    public void flying() {
        System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
    }
}

  • 接口和继承解决的问题不同
    继承的价值主要在于:解决代码的复用性和可维护性。
    接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。
  • 接口比继承更加灵活
    接口比继承更加灵活,继承是满足 is - a 的关系【如猫是动物】,

​ 而接口只需满足 like - a 的关系【如猴子像人】接口在一定程度上实现代码解耦

接口多态特性

多态数组

定义一个接口以及对应的实现接口的类

interface Usb{
    void work();
}
class Phone_ implements Usb {
    public void call() {
        System.out.println("手机可以打电话...");
    }

    @Override
    public void work() {
        System.out.println("手机工作中...");
    }
}
class Camera_ implements Usb {

    @Override
    public void work() {
        System.out.println("相机工作中...");
    }
}

在main方法中

public class InterfacePolyArr {
    public static void main(String[] args) {

        //多态数组 -> 接口类型数组
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
        /*
        给Usb数组中,存放 Phone  和  相机对象,Phone类还有一个特有的方法call(),
        请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外,
        还需要调用Phone 特有方法 call
         */
        for(int i = 0; i < usbs.length; i++) {
            usbs[i].work();//动态绑定机制
            //和前面一样,我们仍然需要进行类型的向下转型,即类型判断
            //因为接口类型数组中有不同的类型【Phone和Camera】
            if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
                ((Phone_) usbs[i]).call();
            }
        }
    }
}
多态参数
public class InterfacePolyParameter {
    public static void main(String[] args) {

        //接口的多态体现
        //接口类型的变量 if01 可以指向 实现了IF接口类的对象实例
        IF if01 = new Monster();//IF if01 = new IF();是错误的
        if01 = new Car();

        //继承体现的多态
        //父类类型的变量 a 可以指向 继承AAA的子类的对象实例
        AAA a = new BBB();
        a = new CCC();
    }
}

interface IF {}
class Monster implements IF{}
class Car implements  IF{}

class AAA {

}
class BBB extends AAA {}
class CCC extends AAA {}

接口多态传递

展示多态传递现象

/**
 * 演示多态的传递
 */
public class InterfacePolyPass {
    public static void main(String[] args) {
        //接口类型的变量可以指向,实现了该接口的类的对象实例
        IG ig = new Teacher();
        //如果IG 继承了 IH 接口,而Teacher 类实现了 IG接口
        //那么,实际上就相当于 Teacher 类也实现了 IH接口.
        //这就是所谓的 接口多态传递现象.
        IH ih = new Teacher();
    }
}

interface IH {
    void hi();
}
interface IG extends IH{ }
class Teacher implements IG {
    @Override
    public void hi() {
    }
}

接口练习题

代码有没有错误,有错误就改,改好后,看输出?

interface A{
	int x = 0; }//等价public static final int x=0

class B{
	int x = 1;}

class C extends B implements A {
    public void pX(){
    System.out.printin(x);//没有指明是父类的x还是接口的x,编译器会报错
    public static void main(String[] args) {
    new C().pX();
	}
}

修改后

public class InterfaceExercise02 {
    public static void main(String[] args) {

    }
}

interface A {
    int x = 0;
}  //想到 等价 public static final int x = 0;

class B {
    int x = 1;
} //普通属性

class C extends B implements A {
    public void pX() {
        //System.out.println(x); //错误,原因不明确x
        //可以明确的指定x
        //访问接口的 x 就使用 A.x
        //访问父类的 x 就使用 super.x
        System.out.println(A.x + " " + super.x);
    }

    public static void main(String[] args) {
        new C().pX();
    }
}

至此,类的五大成员已经学习了4个

即:

  • 属性
  • 方法
  • 构造器
  • 代码块

还差一个难点:**内部类

posted @ 2022-11-12 13:17  dayceng  阅读(69)  评论(0编辑  收藏  举报