毕向东Java基础教程-继承【下】

抽象类

概述

  • 抽象定义
    抽象就是从多个事物中将共性的、本质的内容抽取出来。
    例如:狼和狗共性都是犬科,犬科就是抽象出来的概念。

  • 抽象类
    Java中可以定义没有方法体的方法,该方法的具体实现由子类完成,该方法称为抽象方法,包含抽象方法的类就是抽象类。

  • 抽象方法的由来
    多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主体的方法称为抽象方法。
    例如:狼和狗都有吼叫的方法,可是吼叫内容是不一样的。所以抽象出来的犬科虽然有吼叫功能,但是并不明确吼叫的细节。

特点

  • 抽象类和抽象方法必须用abstract关键字来修饰。
  • 抽象方法只有方法声明,没有方法体,定义在抽象类中。
    格式: 修饰符 abstract 返回值类型 函数名(参数列表) ;
  • 抽象类不可以被实例化,也就是不可以用new创建对象。
    原因如下:
    1. 抽象类是具体事物抽取出来的,本身是不具体的,没有对应的实例。
      例如:犬科是一个抽象的概念,真正存在的是狼和狗。
    2. 而且抽象类即使创建了对象,调用抽象方法也没有意义。
  • 抽象类通过其子类实例化,而子类需要覆盖掉抽象类中所有的抽象方法后才可以创建对象,否则该子类也是抽象类。

相关问题

  • 抽象类中是否有构造函数?
    有,用于给子类对象进行初始化。

  • 抽象类中可不可以没有抽象方法?
    可以,但很少见,目的是不让该类创建对象。AWT的适配器对象就是这种类。
    通常这个类中的方法有方法体,但没有内容。(没有大括号才是没有方法体,有大括号就表示有方法体)

    abstract class Demo
    {
        void show1()
        {}
        void show2()
        {}
     }
    
  • 抽象关键字abstract不可以和哪些关键字共存?
    private:修饰方法时,子类不知道父类的方法,因此父类的方法不能被子类覆盖;修饰类时更不用说了。static:修饰方法,可直接用抽象类的类名调用,不用对象,但是没有方法体,所以没有意义。
    final:修饰类时,子类不能继承父类;修饰方法时,子类不能覆盖掉父类的方法。

  • 抽象类和一般类的异同点
    相同点:抽象类和一般类都是用来描述事物的,都在内部定义了成员。
    不同点:
    一般类有足够的信息描述事物;抽象类描述事物的信息有可能不足。
    一般类中不能定义抽象方法,只能定义非抽象方法;抽象类中可定义抽象方法,同时也可定义非抽象方法。
    一般类可以被实例化;抽象类不可以被实例化。

  • 抽象类一定是个父类吗?
    是的。因为需要子类覆盖其方法后才可以对子类实例化。

示例代码

abstract class Employee
{
    private String name;
    private String id;
    private double salary;

    Employee(String name, String id, double salary)
    {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    public String getName(){return name;}
    public void setName(String name){this.name = name;}
    public String getId(){return id;}
    public void setId(String id){this.id = id;}
    public double getSalary(){return salary;}
    public void setSalary(double salary){this.salary = salary;}

    public abstract void work();
}

class Programmer extends Employee
{
    Programmer(String name, String id, double salary)
    {
        super(name, id, salary);
    }
    public void work()
    {
        System.out.println("code...");
    }
}

class Manager extends Employee
{
    private double bonus;
    Manager(String name, String id, double salary, double bonus)
    {
        super(name, id, salary);
        this.bonus = bonus;
    }
    public void work()
    {
        System.out.println("manage...");
    }
}

接口

概述

1. 定义
当一个抽象类中的方法都是抽象的时候,可以将该抽象类用另一种形式定义和表示,就是接口interface(表面上是这样,但实质却很不相同)。

abstract class Demo
{
    abstract void show1();
    abstract void show2();
}

2. 格式interface {}

interface Demo
{
    abstract void show1();
    abstract void show2();
}

3. 接口中的成员修饰符是固定的。
成员常量: public static final,成员函数: public abstract

interface Demo
{
    public static final int NUM = 4;
    public abstract void show1();
    public abstract void show2();
}

由于是固定的,所以可以省略,写成下面的形式(但是阅读性差).

interface Demo
{
    int NUM = 4;
    void show1();
    void show2();
}

4. 接口不可以实例化。
只能由实现了接口的子类覆盖接口中所有的抽象方法后,该子类才可以实例化,否则这个子类就是一个抽象类。

class DemoImpl implements Demo
{
    public void show1()//注意必须写public
    {}
    public void show2()
    {}
}
class InterfaceDemo
{
    DemoImpl d = new DemoImpl();
    System.out.println(d.NUM);
    System.out.println(DemoImpl.NUM);
    System.out.println(Demo.NUM);
    // 以上三种都对,但是不能写d.NUM = 3;
}

5. 接口的出现将“多继承”通过另一种形式体现出来,即“多实现”。
在java中不直接支持多继承,因为会出现调用的不确定性,所以java将多继承机制进行改良,变成了多实现。
Example1:一个类可以实现多个接口

interface A
{
    public abstract void show();
}
interface B
{
    public abstract void show();
}
class Test implements A,B
{
    public void show()
    {}
}
class Demo
{
    public static void main(String[] args)
    {
        Test t = new Test();
        t.show();//不会有不确定性,因为在接口中的方法没有方法体
    }
}

注意,以下这种情况不允许多实现。因为若只写1,则不能覆盖到B中的函数,而如果1和2都写,则会冲突。

interface A
{
    public abstract void show();
}
interface B
{
    public abstract int show();
}
class Test implements A,B
{
    /*------1------*/
    public void show()
    {}
    /*------2------*/
    public int show()
    {...}
}

Example2:一个类在继承另一个类的同时,还可以实现多个接口
继承自Q,属于Q的体系,但通过实现接口,扩展功能。接口的出现避免了单继承的局限性。

class Q
{
    public void method()
    {}
}
class Test2 extends Q implements A,B
{}

Example3:接口之间可以多继承
类与类之间是继承关系,类与接口之间是实现关系,接口与接口之间是继承关系。
多继承的问题主要在于方法体。

interface CC
{
    void show();
}
interface MM
{
    void method();
}
interface QQ extends CC,MM
{
    void function();
}
class WW implements WW
{
    //需要覆盖三个方法
    public void show(){}
    public void method(){}
    public void funcion(){}
}

特点

  • 接口是对外暴露的规则。
    凡是对外暴露的内容,全都可以理解为接口,不要把接口仅理解为interface。
  • 接口是程序的功能扩展。
  • 接口的出现降低耦合性。
  • 接口可以用来多实现。
  • 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口。
  • 接口与接口之间可以有继承关系。

例如,笔记本的接口(好好体会)
笔记本的接口(如USB接口)是提前定义的规则【接口】,外围设备(如U盘、鼠标)的生产产商只需按照该规则生产设备【实现类】即可,人们在使用时【使用类】,只需插拔不同的设备(即不同情况使用不同的实现类),而无需去更改笔记本内部的结构,以后若还有其他外围设备生产,也只用实现该接口就可。
笔记本的接口在于三部分:定义规则、实现规则、使用规则。而规则在java中即是interface。
注意:(引用指向的是对象)接口型引用指向的是其子类的对象

接口与抽象类的异同点

相同点:都是不断抽取出来的抽象的概念。

不同点

  • 抽象类体现继承关系,一个类只能单继承;
    接口体现实现关系,一个类可以多实现。
  • 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法;
    接口中只能定义抽象方法,必须由子类实现,接口中的成员都有固定修饰符。
  • 抽象类是继承,是"is a"关系,在定义该体系的基本共性内容;
    接口是实现,是"like a"关系,在定义体系额外功能。

多态

概述

1. 定义:某一类事物的多种存在形态。
例:动物中猫,狗。
猫这个对象对应的类型是猫类型:猫 x = new 猫();
同时猫也是动物中的一种,也可以把猫称为动物:动物 y = new 猫();(动物是猫和狗具体事物中抽取出来的父类型。)
猫这类事物既具备猫的形态,又具备着动物的形态,这就是对象的多态性(一个对象,两种形态)。简单说,就是一个对象对应着不同类型。
网上的另一种解释——不同类的对象对同一消息作出不同的响应就叫做多态。就像上课铃响了,上体育课的学生跑到操场上站好,上语文课的学生在教室里坐好一样。

2. 多态在代码中的体现:父类或者接口的引用指向或者接受自己的子类对象。

abstract class Animal
{
    abstract void eat();
}
class Dog extends Animal
{
    void eat()
    {
        System.out.println("啃骨头");
    }
}
class Cat extends Animal
{
    void eat()
    {
        System.out.println("吃鱼");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Cat c = new Cat();
        Dog d = new Dog();

        method(c);
        method(d);
    }
    public static void method(Animal a) //用父类作为参数,这个函数可以接收其各个子类的对象
    {
        a.eat();
    }
}

3. 多态的好处:提高了代码的扩展性(重用性)和后期可维护性,前期定义的代码可以使用后期的内容。
网上的资料——简单讲多态的作用就是解耦。再详细点讲就是,多态是设计模式的基础,不能说所有的设计模式都使用到了多态,但是23种中的很大一部分,都是基于多态的。

class Pig extends Animal //新加的一个Pig类
{
    void eat()
    {
        System.out.println("吃饲料");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Cat c = new Cat();
        Dog d = new Dog();

        method(c);
        method(d);
        method(new Pig()); //新加代码
    }
    public static void method(Animal a)
    {
        a.eat();
    }
}

4. 多态的弊端:前期定义的内容不能使用(调用)后期子类的特有内容。

abstract class Animal
{
    abstract void eat();
}
class Dog extends Animal
{
    void eat()
    {
        System.out.println("啃骨头");
    }
    void guardHouse()
    {
        System.out.println("看家");
    }
}
class Cat extends Animal
{
    void eat()
    {
        System.out.println("吃鱼");
    }
    void catchMice()
    {
        System.out.println("抓老鼠");
    }
}
class Pig extends Animal
{
    void eat()
    {
        System.out.println("吃饲料");
    }
    void digEarth()
    {
        System.out.println("拱地");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        /* 第一种方式*/
        Animal a = new Cat();
        a.eat();
        a.catchMice(); //编译错误,在类Animal中找不到方法catchMice()

        /* 第二种方式*/
        Cat c = new Cat();
        method(c);
    }
    public static void method(Animal a)
    {
        a.eat();
        a.catchMice(); //编译错误,在类Animal中找不到方法catchMice()
    }
}

5. 多态的前提
1)存在继承或者实现关系
2)子类重写父类方法
3)父类引用指向子类对象

6. 转型

Animal a = new Cat(); //自动类型提升,猫对象提升到了动物类型。
a.eat();

向上转型(自动):猫一旦提升成动物,访问上就有了局限性,不能再访问猫的特有功能了。其作用就是提高扩展性(可以接收不同类型)和限制对特有功能的访问。
如果还想调用具体动物——猫的特有功能,还可将该对象进行向下转型。

Cat c = (Cat)a;
c.eat();
c.catchMice();

向下转型(需要强转):目的是为了使用子类中的特有方法。

Animal a = new Dog();
Cat c = (Cat)a; //运行报错:java.lang.ClassCastException: Dog cannot be cast to Cat
Son s = (Son)new Father(); //注意这种写法也可以

注意,对于转型,自始至终都是子类对象在做着类型的变化,一会变成父类型,一会变成本类型。

7. instanceof关键字
用于判断对象的具体类型,只能用于引用数据类型判断。

public static void method(Animal a)
{
    a.eat();
    if(a instanceof Cat)
    {
        Cat c = (Cat)a;
        c.catchMice();
    }else if(a instanceof Dog)
    {
        Dog d = (Dog)d;
        d.guardHouse();
    }
}

通常在向下转型前使用,增强程序的健壮性(因为如果不是猫的类型,并且程序没有对其判断的话,直接强转,编译时不会报错,但运行会报错)。

8.多态的分类
1)编译时多态,即方法的重载,从JVM的角度来讲,这是一种静态分派(static dispatch)【函数的多态】
2)运行时多态,即方法的重写,从JVM的角度来讲,这是一种动态分派(dynamic dispatch)【对象的多态】

特点

1. 成员变量
编译时和运行时均只看引用变量所属的类。
简单说,编译和运行都参考等号的左边。

class Father
{
    int num = 3;
}
class Son extends Father
{
    int num = 4;
}
class Demo
{
    public static void main(String[] args)
    {
        Father f = new Son();
        System.out.println(f.num); //输出3,覆盖只发生在函数上;若Father中没有num变量,会编译失败
    }
}

2. 成员函数(非静态)
编译时:要查看引用变量所属的类中是否有所调用的成员(有->编译通过;没有->编译失败)。
运行时:要查看对象所属的类中是否有所调用的成员。
简单说:编译看等号左边,运行看等号右边。

class Father
{
    void show()
    {
        System.out.println("father show");
    }
}
class Son extends Father
{
    void show()
    {
        System.out.println("son show");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Father f = new Son();
        f.show();
        //Father和Son中都有show(),调用Son的方法,输出son show;
        //Father中没有show(),Son中有,编译失败;
        //Father中有show(),Son中没有,调用Father的方法,输出father show
    }
}

内存图解:

非静态方法需要依赖对象。
动态绑定:在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。
在Java中重写可以被认为是动态绑定的最佳示例,因为父类和子类具有相同的方法—— 也就是说,它不决定要调用的方法。

3. 静态函数
编译和运行都参考等号的左边。

class Father
{
    static void method(){
        System.out.println("father static method");
    }
}
class Son extends Father
{
    static void method(){
        System.out.println("son static method");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Father f = new Son();
        f.method();//输出father static method
    }
}

静态绑定:staticfinalprivate方法的绑定是静态绑定,都在编译时完成,因为它们无法被覆盖,所以将始终由某个本地类的对象访问,静态绑定提供了更好的性能。

以下三种类型的方法是没有办法表现出多态特性的(因为不能被重写):
1)static方法,因为被static修饰的方法是属于类的,而不是属于实例的。
2)final方法,因为被final修饰的方法无法被子类重写。
3)private方法和protected方法,前者是因为被private修饰的方法对子类不可见,后者是因为尽管被protected修饰的方法可以被子类见到,也可以被子类重写,但是它是无法被外部所引用的,一个不能被外部引用的方法,怎么能谈多态呢。

你可能感兴趣的:(毕向东Java基础教程-继承【下】)