day13_面向对象(抽象类丶接口)

什么是抽象类

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。抽象类也属于引用数据类型可以包含普通方法和抽象方法。Java中规定如下语法:

  • 用abstract关键字来修饰一个类,这个类叫做抽象类。
  • 用abstract来修饰一个方法,该方法叫做抽象方法。
  • 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
public abstract void talk();//抽象方法

 抽象类怎么定义?

 day13_面向对象(抽象类丶接口)

代码示例

package demo01;
// 定义一个抽象类
public abstract class Animal {
    
}

特点

  • 抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。
  • 抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。
  • 不能用abstract修饰变量、代码块、构造器;
  • 不能用abstract修饰私有方法、静态方法、final的方法、final的类。

抽象方法

抽象方法表示没有实现的方法,没有方法体的方法。

定义格式: 

day13_面向对象(抽象类丶接口)

例如:

public abstract void doSome();

抽象方法特点:

  • 没有方法体,以分号结尾。
  • 前面修饰符列表中有abstract关键字。
抽象的方法只需在抽象类中,提供声明,不需要实现,起到了一个强制的约束作用,要求子类必须实现。如果一个类包含抽象方法,那么该类必须是抽象类。 

抽象的使用

继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。
package demo01;

public class Test {
    public static void main(String[] args) {
        // 能不能使用多态?
        // 父类型引用指向子类型对象。
        Animal a = new Bird();  // 向上转型。(自动类型转换)

        // 这就是面向抽象编程。
        // 以后你都是调用的a.XXXX
        // a的类型是Animal,Animal是抽象的
        // 面向抽象编程,不要面向具体编程,降低程序的耦合度,提高程序的扩展力。
        // 这种编程思想符合OCP原则。
        /*
            分析以下:
                编译的时候这个move()方法是谁的?父类
                运行的时候这个move()方法又是谁的?子类
        */
        a.move();//鸟儿在飞翔!

        // 多态(当对多态不是很理解的时候,以后写代码能用多态就用多态。慢慢就理解了。)
        Animal x = new Cat();
        x.move();//猫在走猫步!
    }
}


// 动物类(抽象类)
abstract class Animal {
    // 抽象方法
    public abstract void move();

    //成员方法
    public void message() {
        System.out.println("我是一个动物");
    }
}

class Bird extends Animal {
    // 需要将从父类中继承过来的抽象方法进行覆盖/重写,或者也可以叫做“实现”。
    @Override
    public void move() {
        System.out.println("鸟儿在飞翔!");
    }
}

class Cat extends Animal {

    @Override
    public void move() {
        System.out.println("猫在走猫步!");
    }
}

注意事项

抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

  • 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

  • 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

  • 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。

  • 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。 

接口

接口的由来

一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。

接口的概述

接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了抽象方法。接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。接口(interface)是抽象方法和常量值定义的集合。

接口的使用,它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。

接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。

定义格式

day13_面向对象(抽象类丶接口)

代码示例

package demo02;
// 定义一个接口
public interface Runner {
    int ID = 1;

    void start();

    public void run();

    void stop();
}

接口的特点

  • 在 java 中接口采用 interface 声明,所有的元素都是public修饰的
  • 接口中的方法默认都是 public abstract 的,不能更改。 public abstract 可以省略不写。
  • 接口中的变量默认都是 public static final 类型的,不能更改,所以必须显示的初始化。public static final可以省略不写。
  • 接口不能被实例化,接口中没有构造函数的概念
  • 接口之间可以继承,但接口之间不能实现

上面的代码等价于下面的

package demo02;

// 定义一个接口
public interface Runner {
    //常量
    public static final int ID = 1;

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

    public abstract void run();

    public abstract void stop();
}

接口的基本的实现

类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements 关键字。

非抽象子类实现接口: 必须重写接口中所有抽象方法。

我来实现下上面定义的接口
package demo02;

//非抽象RunnerTest实现了Runner接口
public class RunnerTest implements Runner {
    @Override
    public void start() {
        System.out.println("实现了start");
    }

    @Override
    public void run() {
        System.out.println("实现了run");
    }

    @Override
    public void stop() {
        System.out.println("实现了stop");
    }
}

定义测试类

package demo02;

public class Test {
    public static void main(String[] args) {
        Runner runnerTest = new RunnerTest();
        //调用实现类中的方法
        runnerTest.run();
        //访问接口中的常量
        int id = Runner.ID;
        System.out.println(id);
    }
}

总结

  • 如果Java实现类即继承了类又实现了接口的语法格式先写extends,后写implements
package demo04;

/*
    继承和实现都存在的话,代码应该怎么写?
        extends 关键字在前。
        implements 关键字在后。
*/
public class Test {
    public static void main(String[] args) {
        // 创建对象(表面看Animal类没起作用!)
        Flyable f = new Cat(); //多态。
        f.fly();//飞猫起飞,翱翔太空的一只猫,很神奇,我想做一只猫!!
        //向下转型调用继承下来的方法
        if (f instanceof Cat) {
            Cat cat = (Cat) f;
            cat.message();//我是动物

            // 同一个接口
            Flyable f2 = new Pig();
            // 调用同一个fly()方法,最后的执行效果不同。
            f2.fly();//我是一只会飞的猪!!!

            Flyable f3 = new Fish();
            f3.fly();//我是六眼飞鱼(流言蜚语)!!!

        }
    }
}

// 动物类:父类
class Animal {
    public void message() {
        System.out.println("我是动物");
    }
}

// 可飞翔的接口(是一对翅膀)
// 接口通常提取的是行为动作。
interface Flyable {
    void fly();
}

// 动物类子类:猫类
// Flyable是一个接口,是一对翅膀的接口,通过接口插到猫身上,让猫变的可以飞翔。
class Cat extends Animal implements Flyable {
    public void fly() {
        System.out.println("飞猫起飞,翱翔太空的一只猫,很神奇,我想做一只猫!!");
    }
}

// 蛇类,如果你不想让它飞,可以不实现Flyable接口
// 没有实现这个接口表示你没有翅膀,没有给你插翅膀,你肯定不能飞。
class Snake extends Animal {
}

// 想飞就插翅膀这个接口。
class Pig extends Animal implements Flyable {
    public void fly() {
        System.out.println("我是一只会飞的猪!!!");
    }
}

// 鱼(默认实际上是存在继承的,默认继承Object。)
/*
class Fish extends Object implements Flyable{
}
*/
class Fish implements Flyable { //没写extends,也是有的,默认继承Object。
    public void fly() {
        System.out.println("我是六眼飞鱼(流言蜚语)!!!");
    }
}
  • 接口和接口之间支持多继承
interface X{
}
interface Y{
}
interface Z extends X,Y{ //接口和接口支持多继承。
}
  • 一个类可以同时实现多个接口。
  • 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
package deno03;

/*
    接口和接口之间支持多继承,那么一个类可以同时实现多个接口吗?

        对于计算机来说,一个机箱上有多个接口,一个接口是接键盘的,
        一个接口是接鼠标的,一个接口是接电源的,一个接口是接显示器的.....
    

    重点(五颗星*****):一个类可以同时实现多个接口。

    这种机制弥补了java中的哪个缺陷?
        java中类和类只支持单继承。实际上单继承是为了简单而出现的,现实世界中
        存在多继承,java中的接口弥补了单继承带来的缺陷。
    
    接口A和接口B虽然没有继承关系,但是写代码的时候,可以互转。
    编译器没意见。但是运行时可能出现:ClassCastException

    之前有一个结论:
        无论向上转型还是向下转型,两种类型之间必须要有继承关系,
        没有继承关系编译器会报错。(这句话不适用在接口方面。)
        最终实际上和之前还是一样,需要加:instanceof运算符进行判断。
        向下转型养成好习惯。转型之前先if+instanceof进行判断。
*/
public class Test {
    public static void main(String[] args) {
        // 都是父类型引用指向子类型对象
        A a = new D();
        //a.m2(); // 编译报错。A接口中没有m2()方法。
        B b = new D();
        C c = new D();
        // 调用其他接口中的方法,你需要转型(接口转型。)
        // 接口和接口之间在进行强制类型转换的时候,没有继承关系,也可以强转。
        // 但是一定要注意,运行时可能会出现ClassCastException异常。编译没问题,运行有问题。
        //向下转型养成好习惯。转型之前先if+instanceof进行判断。直接转为子类,就可以调用子类中实现的方法
        if (a instanceof D) {
            D b1 = (D) a;
            b1.m2();//m2 ....
        }
    }
}

interface A {
    void m1();
}

interface B {
    void m2();
}

interface C {
    void m3();
}

// 实现多个接口,其实就类似于多继承。
class D implements A, B, C {
    // 实现A接口的m1()
    public void m1() {

    }

    // 实现B接口中的m2()
    public void m2() {
        System.out.println("m2 ....");
    }

    // 实现接口C中的m3()
    public void m3() {

    }
}
  • 接口的主要用途就是被实现类实现。(面向接口编程)
  • 与继承关系类似,接口与实现类之间存在多态性
  • 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。

其他成员特点

  • 接口中,没有构造方法,不能创建对象。
  • 接口中,没有静态代码块。

接口的进一步应用

在 java 中接口其实描述了类需要做的事情,类要遵循接口的定义来做事,使用接口到底有什么本质的好处?可以归纳为两点:
  • 采用接口明确的声明了它所能提供的服务
  • 解决了 Java 单继承的问题
  • 实现了可接插性(重要)

代码示例

我们去餐厅吃饭,菜单就是一个接口,这个饭馆的“菜单”,让“顾客”和“后厨”解耦合了。顾客不用找后厨,后厨不用找顾客。他们之间完全依靠这个抽象的菜单

  • 谁面向接口调用。(顾客面向菜单点菜,调用接口。)
  • 谁负责实现这个接口。(后台的厨师负责把西红柿鸡蛋做好!是接口的实现者。)

定义菜单接口

package demo03;

public interface FoodMenu {

    // 西红柿炒蛋
    void shiZiChaoJiDan();

    // 鱼香肉丝
    void yuXiangRouSi();
}

厨师是接口的实现者。

package demo03;
//中餐厨师
public class ChinaCooker implements FoodMenu {
    @Override
    public void shiZiChaoJiDan() {
        System.out.println("中国口味炒鸡蛋");
    }

    @Override
    public void yuXiangRouSi() {
        System.out.println("中国口味鱼香肉丝");
    }
}
package demo03;
//西餐厨师
public class AmericCooker implements FoodMenu {

    @Override
    public void shiZiChaoJiDan() {
        System.out.println("西餐师傅做的西红柿炒鸡蛋!");
    }

    @Override
    public void yuXiangRouSi() {
        System.out.println("西餐师傅做的鱼香肉丝!");
    }
}

定义顾客类,

package demo03;
//顾客类
public class Customer {
    // 顾客手里有一个菜单
    // Customer has a FoodMenu!(这句话什么意思:顾客有一个菜单)
    // 记住:以后凡是能够使用 has a 来描述的,统一以属性的方式存在。
    // 实例变量,属性
    // 面向抽象编程,面向接口编程。降低程序的耦合度,提高程序的扩展力。
    private FoodMenu foodMenu;
    //构造方法
    public Customer() {
    }

    public Customer(FoodMenu foodMenu) {
        this.foodMenu = foodMenu;
    }
    // setter and getter
    public FoodMenu getFoodMenu() {
        return foodMenu;
    }

    public void setFoodMenu(FoodMenu foodMenu) {
        this.foodMenu = foodMenu;
    }

    // 提供一个点菜的方法
    public void order(){
        // 先拿到菜单才能点菜
        // 调用get方法拿菜单。
        //FoodMenu fm = this.getFoodMenu();
        // 也可以不调用get方法,因为在本类中私有的属性是可以访问
        foodMenu.shiZiChaoJiDan();
        foodMenu.yuXiangRouSi();
    }
}

定义测试类

package demo03;

public class Test {
    public static void main(String[] args) {
        // 创建厨师对象
        //FoodMenu cooker1 = new ChinaCooker();
        FoodMenu cooker1 = new AmericCooker();
        //创建顾客类
        Customer customer = new Customer(cooker1);

        // 顾客点菜
        customer.order();

    }
}

执行结果

day13_面向对象(抽象类丶接口)

接口可以解耦合,解开的是谁和谁的耦合!!!

任何一个接口都有调用者和实现者。接口可以将调用者和实现者解耦合。调用者面向接口调用。实现者面向接口编写实现。

接口和抽象类的区别

  • 在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
  • 接口描述了方法的特征,不给出实现,一方面解决 java 的单继承问题,实现了强大的可接插性
  • 抽象类提供了部分实现,抽象类是不能实例化的,抽象类的存在主要是可以把公共的代码移植到抽象类中
  • 面向接口编程,而不要面向具体编程(面向抽象编程,而不要面向具体编程)
  • 优先选择接口(因为继承抽象类后,此类将无法再继承,所以会丧失此类的灵活性)

day13_面向对象(抽象类丶接口)

常见的类之间的关系

  • is a(继承):但凡满足is a的表示都可以设置为继承。例如:Cat is a Animal,
  • has a(关联):但凡是满足has a的表示都以属性的形式存在。例如:I has a Pen(我有一支笔)
  • like a(实现):凡是能够满足like a关系的表示“实现关系”实现关系通常是:类实现接口。例如:Cooker like a FoodMenu(厨师像一个菜单一样)
上一篇:day13-面向对象


下一篇:AC小笔记