当前位置 IIS7博客首页 > 多态 最大化 缩小

    多态

    作者:夜空星满 栏目:未分类 时间:2020-06-30 10:04:47

    多态

    多态的基础语法

    1. 学习多态基础语法之前,需要普及两个概念:
      • 第一个:向上转型(upcasting):子 ----> 父(好比自动类型转换)

      • 第二个:向下转型(downcasting):父 ----> 子(好比强制类型转换,需要加强制类型转换符)

      • 注意事项:

        • Java中允许向上转型,也允许向下转型。
        • 无论是向上转型还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错!
        • 以后在工作过程中,说向上转型和向下转型,不要说自动类型转换和强制类型转换。因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型。
    2. 多态指的是:
      • 父类型引用指向子类型引用。
      • 包括编译阶段和运行阶段。多种形态。
        • 编译阶段:绑定父类的方法。
        • 运行阶段:动态绑定子类型对象的方法。
    public class Animal {//动物类:父类
        //移动的方法
        public void move(){
            System.out.println("动物在移动!");
        }
    }
    
    public class Cat extends Animal {//猫类:子类
        //对move()方法进行重写
        public void move(){
            System.out.println("cat在走猫步!");
        }
    }
    
    public class Bird extends Animal {//鸟儿类:子类
        //对move()方法进行重写
        public void move(){
            System.out.println("鸟儿在飞翔!");
        }
    }
    
    public class Test01 {
        public static void main(String[] args) {
            Animal a1 = new Animal();
            a1.move();//动物在移动!
    
            Cat c1 = new Cat();
            c1.move();//cat在走猫步
    
            Bird b1 = new Bird();
            b1.move();//鸟儿在飞翔!
    
            //代码可以这样写吗?
            /*
              1.Animal和Cat之间有继承关系吗?有的!
              2.Animal是父类,Cat是子类。
              3.Cat is Animal, 这句话能不能说通?能!
              4.经过测试得知:Java中支持这样的一个语法:
                父类型的引用允许指向子类型的对象。
                Animal a2 = new Cat();
                a2就是父类型的引用。
                new Cat()是一个子类型的对象。
                允许a2这个父类型的引用指向子类型的对象。
            */
            Animal a2 = new Cat();
            Animal a3 = new Bird();
    
            //调用a2的move()方法
            /*
             什么是多态?
                多种形态,多种状态。
             分析:a2.move();
                java程序分为编译阶段和运行阶段。
                先来分析编译阶段:
                    对于编译器来说,编译器只知道a2的类型是Animal,
                    所以编译器在检查语法的时候,会去Animal.class字节码文件中找move()方法。
                    找到了,绑定上move()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定)
                再来分析运行阶段:
                    运行阶段的时候,实际上在堆内存中创建的Java对象是Cat对象,所以move的时候,
                    真正参与move的对象是一只猫,所以运行阶段会动态执行Cat对象的move()方法。
                    这个过程属于运行阶段绑定。(运行阶段绑定属于动态绑定)
             多态表示多种形态:编译的时候一种形态,运行的时候另一种形态。
    
             */
            a2.move();//cat在走猫步!
    
            //调用a3的move()方法
            a3.move();//鸟儿在飞翔!
        }
    }
    
    1. java中只有“类名”或者“引用”才能去“点”。点前面要么是一个类名,要么是一个引用。

    分析下面程序能否编译和运行?

    public class Animal {//动物类:父类
        //移动的方法
        public void move(){
            System.out.println("动物在移动!");
        }
    }
    
    public class Cat extends Animal {//猫类:子类
        //对move()方法进行重写
        public void move(){
            System.out.println("cat在走猫步!");
        }
    
        //猫除了move之外,应该有自己特有的行为,例如抓老鼠
        //这个行为是子类型对象特有的方法。
        public void catchMouse(){
            System.out.println("猫正在抓老鼠!");
        }
    }
    
    public class Test{
        public static void main(String[] args){
            Animal a = new Cat();//底层对象是一个猫
            //分析这个程序能否编译和运行?
            //分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定
            //只有编译通过代码才能运行,没有编译,根本轮不到运行。
            //错误:找不到符号
            //因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法,
            //结果没有找到,所以静态绑定失败,无法运行。(语法不合法)
            a.catchMouse(); 
        }
    }
    

    什么时候必须向下转型

    • 调用子类对象上特有的方法时
    public class Animal {//动物类:父类
        //移动的方法
        public void move(){
            System.out.println("动物在移动!");
        }
    }
    
    public class Cat extends Animal {//猫类:子类
        //对move()方法进行重写
        public void move(){
            System.out.println("cat在走猫步!");
        }
    
        //猫除了move之外,应该有自己特有的行为,例如抓老鼠
        //这个行为是子类型对象特有的方法。
        public void catchMouse(){
            System.out.println("猫正在抓老鼠!");
        }
    }
    
    public class Test{
        public static void main(String[] args){
            Animal a = new Cat();//底层对象是一个猫
        //假如代码写到这里,非要调用catchMouse()方法怎么办?
        //这个时候就必须使用“向下转型”了,(强制类型转换)
        //以下代码为什么没有报错?
        //因为a是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没有错。
          Cat x = (Cat)a;
            x.catchMouse();//猫正在抓老鼠!
        }
    }
    

    instanceof运算符

    public class Animal {//动物类:父类
        //移动的方法
        public void move(){
            System.out.println("动物在移动!");
        }
    }
    
    public class Cat extends Animal {//猫类:子类
        //对move()方法进行重写
        public void move(){
            System.out.println("cat在走猫步!");
        }
    
        //猫除了move之外,应该有自己特有的行为,例如抓老鼠
        //这个行为是子类型对象特有的方法。
        public void catchMouse(){
            System.out.println("猫正在抓老鼠!");
        }
    }
    
    public class Bird extends Animal {//鸟儿类:子类
        //对move()方法进行重写
        public void move(){
            System.out.println("鸟儿在飞翔!");
        }
    }
    
    public class Test{
        public static void main(String[] args){
             //向下转型有风险了吗?
        Animal a = new Bird();//表面上a是一个Animal,运行时实际上是一只鸟儿
         /*
         分析以下程序,编译错误还是运行报错?
         	编译器检测到a这个引用是Animal类型,而Animal和Cat之间存在继承关系,所以可以向下转型,编译没毛病。
         	
         	运行阶段,堆内存实际创建的对象是:Bird对象。在实际运行过程中,拿着Bird对象装换成Cat对象就不行了。因为Bird和Cat没有继承关系。
         	
         运行时出现异常:这个异常和空指针一样非常重要,也非常经典,
         java.lang.ClassCastException:类型转换异常。
         java.lang.NullPointerException:空指针异常。这个也非常重要。
         */
          Cat x = (Cat)a;
            x.catchMouse();//猫正在抓老鼠!
        }
    }
    

    怎么避免ClassCastException异常发生?

    public class Test{
        public static void main(String[] args){
             //向下转型有风险了吗?
        Animal a = new Bird();//表面上a是一个Animal,运行时实际上是一只鸟儿
         /*
         新的内容,运算符:Instanceof(运行阶段动态判断)
         第一:instanceof可以在运行阶段动态判断引用指向的对象的类型
         第二:Instanceof的语法:(引用 instanceof 类型)
         第三:instanceof运算符的运算结果只能是:true/false
         第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
         	  假设(c instanceof Cat)为true表示:
         	  	c引用指向的堆内存中的java对象是一个Cat.
         	  假设(c instanceof Cat)为false表示:
         	  	c引用指向的堆内存中的java对象不是一个Cat.
         	  	
         程序员要养成一个好习惯:
         	任何时候,任何地点,对类型进行向下转换时,一定要使用instanceof运算符进行判断。(java规范中要求的)
         	这样可以很好的避免:ClassCastException
         */
            if(a instanceof Cat){//如果a是一只Cat
         		 Cat x = (Cat)a;//再进行强制类型转换
            	x.catchMouse();//猫正在抓老鼠!
            }
        }
    }
    

    为什么要用instanceof判断?

    public class Test{
        public static void main(String[] args){
            //main方法是程序员A负责编写的
            AnimalTest at = new AnimalTest();
            at.test(new Cat());
            at.test(new Bird());
        }
    }
    
    public class AnimalTest{//test方法是程序员B负责编写的
        public void test(Animal a){//这个test()方法的参数是一个Animal
            //这个方法别人回去调用
            //别人调用的时候可能给你test()方法传过去一个Bird
            //当然也可能传过去一个Cat
            //对于程序员B来说,不知道在调用的时候会传来什么
            if(a instanceof Cat){
                Cat c = (Cat)a;
                a.catchMouse();
            }else if(a instanceof Bird){
                Bird b = (Bird)a;
                b.sing();
            }
        }
    }
    

    多态在开发中的作用

    • 降低程序的耦合度,提高程序的扩展力

      public class Master{
          public void feed(Dog d){}
          public void feed(Cat c){}
      }
      
      

      以上的代码表示:Master和Dog以及Cat的关系很紧密(耦合度高),导致扩展力很差。


      public class Master{
          public void feed(Pet pet){}
      }
      

      以上的代码表示:MasterDog以及Cat的关系就脱离了,Master关注的是Pet类,这样MasterDog以及Cat的耦合度就降低了,提高了软件的扩展性。

    程序演示

    public class Master {//主人类
        //假设主人起初的时候只喜欢养狗狗
        public void feed(Dog d){//喂养宠物狗狗
            d.eat();
        }
        //由于新的需求产生,导致我们不得不去修改这个类的代码
        /*public void feed(Cat c){
            c.eat();
        }*/
    
        //能不能让Master主人这个类以后不再修改了
        //即使主人又喜欢养其他宠物了,Master也不需要修改
        //这个时候就需要使用:多态机制。
        //最好不要写具体的宠物类型,这样会影响程序的扩展性。
        public void feed(Pet pet){
            //编译的时候,编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,编译通过
            //运行的时候,底层实际的对象是什么,就自动调用到该类实际对象对应的eat()方法上。
            //这就是多态的使用
            pet.eat();
        }
    
    }
    
    /*
    注意这里的分析:
        主人起初的时候只喜欢杨宠物狗狗
        随着时间的推移,主人有喜欢上了养猫咪,
        在实际的开发之中这就表示客户产生了新的需求,
        作为软件开发人员来说,必须满足客户的需求
        该如何去满足客户的额需求呢?
            在不使用多态的前提下,目前我们只能在Master类中添加一个新的方法。
    
    思考:软件在扩展新需求的过程中,修改Master这个类有什么问题?
            一定要记住:软件在扩展过程中,修改的越少越好。
           修改的越多,系统当前的稳定性就越差,未知风险就越多。
           其实这里涉及到一个软件开发原则:
            软件开发原则有七大原则(不属于Java,这个开发原则属于整个软件行业)
                其中有一条最基本的原则:OCP(开闭原则)
    
    什么是开发原则?
        对扩展开放(你可以额外添加,没问题),对修改关闭(最好很少的修改现有的程序)。
        在软件的扩展过程中,修改的越少越好
    
    高手开发项目不仅仅为了实现客户需求,还需要考虑软件的扩展性。
    
    什么是扩展性?
        假设电脑的内存条部件坏了,我们可以买一个新的插上,直接使用。
        这个电脑的设计就考虑了“扩展性”。内存条的扩展性就很好。
    
    面向父类型编程,面向更加抽象编程,不建议面向具体编程。
    因为面向具体编程会让软件的扩展力变得很差。
     */
    
    public class Pet {//所有宠物的父类
        //吃的行为(这个方法可以不给具体的实现)
        public void eat(){}
    }
    
    public class Dog extends Pet{//宠物狗狗类
        public void eat(){//吃
            System.out.println("狗狗喜欢吃骨头,吃得很香!");
        }
    }
    
    public class Cat extends Pet{//猫类
        public void eat(){
            System.out.println("猫咪喜欢吃鱼,吃的很香!");
        }
    }
    
    //OCP:对扩展开放,对修改关闭
    public class YingWu extends Pet{//鹦鹉类
        public void eat(){//重写eat方法
            System.out.println("鹦鹉喜欢吃小虫子,吃的很香!");
        }
    }
    
    public class Test {//调试多态在开发中的作用
        public static void main(String[] args) {
            //创建主人对象
            Master zhangsan = new Master();
            //创建宠物对象
            Dog zangAo = new Dog();
            //主人喂食
            zhangsan.feed(zangAo);
    
            //创建宠物对象
            Cat xiaohua = new Cat();
            //主人喂食
            zhangsan.feed(xiaohua);
    
            //创建鹦鹉对象
            YingWu yingWu = new YingWu();
            //主人喂养
            zhangsan.feed(yingWu);
        }
    }
    
    

    面向对象三大特性的关系

    • 封装、继承、多态三者是一环扣一环。
    • 有了封装,有了这种整体的概念之后。
    • 对象于对象之间产生了继承。
    • 有了继承之后,才有了方法的覆盖和多态。

    软件开发原则

    • 七大原则最基本的原则:OCP(对扩展开发,对修改关闭)
    • 目的是:降低程序的耦合度,提高程序的扩展力。
    • 面向抽象编程,不建议面向具体编程。