美文网首页
毕向东Java基础教程-继承【下】

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

作者: Lois_Huang | 来源:发表于2019-12-17 18:46 被阅读0次

    抽象类

    概述

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

    • 抽象类
      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基础教程-继承【下】

          本文链接:https://www.haomeiwen.com/subject/girenctx.html