面向对象之封装、继承、多态

面对对象-封装
  • 封装概述
    是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
  • 封装好处
    a. 隐藏实现细节,提供公共的访问方式
    b. 提高了代码的复用性
    c. 提高安全性。
  • 封装原则:
    a. 将不需要对外提供的内容都隐藏起来。
    b. 把属性隐藏,提供公共方法对其访问。
private关键字的概述和特点:
  • private关键字特点:
    a. 是一个权限修饰符

    权限修饰符:

    • public 公共的 可以修饰成员变量,成员方法,类,被他修饰的可以在任意地方进行访问。
    • public > protected > 缺省的(默认的) > private
    • public 和 private 最为常用

    b. 可以修饰成员变量和成员方法
    c. 被其修饰的成员只能在本类中被访问(被private修饰过的属性或方法利用对象.属性(对象.方法())的方式是无法访问的,因为不在该类内!)

  • private 应用的标准案例:
    (1). 把成员变量用private修饰
    (2). 提供对应的getXxx()和setXxx()方法
    在IDEA中快速生成与私有成员变量对应的 getXxx()和setXxx()方法:

    • 在IDEA中点击右键选择 Generate
    • 再选择Getter和Setter
    • 再选择需要针对哪些私有变量生成方法(全选可以按键:crtl+A)
    • 点击OK,即可生成!

    (3). 示例如下:

    public class Person {
        //private 私有的,是一个权限修饰符,可以修饰成员变量和成员方法,修饰后,该成员只能在本类中访问
        private String name;
        private int age;
        //get set 方法的规范
        //提供一个公共的赋值的方法
        public void setAge(int nianLing) {
            if (nianLing >= 0 && nianLing <= 100) {
                age = nianLing;
            } else {
                System.out.println("这个年龄的数据不合理");
            }
        }
        public void setName(String mingzi) {
            name = mingzi;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        private void show() {
            System.out.println(name);
            System.out.println(age);
        }
    }
    
    • 使用该类:
    public class MyTest {
        public static void main(String[] args) {
            //我们通过 对象名.属性名=值 这种赋值方式,不能验证数据的合理性
            //我们现在想要对赋值的数据进行合理性的验证,需要屏蔽掉 对象名.属性名=值 这种赋值方式
            //怎么来屏蔽掉呢?可以使用一个关键字private 私有的,是一个权限修饰符
            Person p = new Person();
           // p.name="张三";
            //p.age=230;
            //设置值
            p.setName("张三");
            p.setAge(20);
            //获取属性值的方法
            int age = p.getAge();
            String name = p.getName();
            System.out.println(name);
            System.out.println(age);
    
            //int num=p.age;
           // p.show();
    
        }
    }
    
this关键字的概述和应用:
  • 为什么要有this?
    当我们的局部变量和成员变量相同的时候,如果我们不使用this关键字,那么会导致一个问题:就是局部变量隐藏了成员变量的问题(因为就近原则的缘故,会首先使用“局部变量!”)
    例如:
    class Demo {
        public static void main(String[] args) {
            Phone phone1 = new Phone();
            System.out.println(phone1.getName());
            phone1.setName("iphone");
            System.out.println(phone1.getName());
        }
    }
    
    class Phone {
        //私有成员变量
        private String name;
        private double price;
    
        //提供公共的set get 方法
        public void setName(String name) {
            //就近原则
            name = name;
            System.out.println(name);
        }
    
        public String getName(){
            // 就近原则(该name是类中定义的name)
            return name;
        }
    }
    -----------------------------------------
    输出:
    null
    iphone
    null
    
    在这种情况下:由于就近原则的缘故,name指向的都是setName方法中的局部变量,而类中String name的值却一直都没有变化!
  • 改用this指针来指明方法中哪些是类成员变量!
    class Demo {
        public static void main(String[] args) {
            Phone phone1 = new Phone();
            System.out.println(phone1.getName());
            System.out.println("phone1对象的地址值" + phone1);
            phone1.setName("iphone");
            System.out.println(phone1.getName());
    	    }
    }
    
    class Phone {
        //私有成员变量
        private String name;
        private double price;
    
        //提供公共的set get 方法
        public void setName(String name) {
            //就近原则
            //this 表示本类的一个引用。你可以理解为本类的一个对象,哪个对象调用这个方法,那么方法中的this就代表这个对象
    private String name;
            this.name = name;
            System.out.println(name);
            System.out.println("this代表调用者的地址值2" + this);
        }
    
        public String getName(){
        	System.out.println("this代表调用者的地址值1" + this);
            // 就近原则(该name是类中定义的name)
            return this.name;
        }
    }
    ----------------------
    输出:
    this代表调用者的地址值1edu.nwu.demo.Phone@1b6d3586
    null
    phone1对象的地址值edu.nwu.demo.Phone@1b6d3586
    iphone
    this代表调用者的地址值2edu.nwu.demo.Phone@1b6d3586
    this代表调用者的地址值1edu.nwu.demo.Phone@1b6d3586
    iphone
    
  • this关键字特点
    a. 是当前类的对象引用
    b. 简单的记,它就代表当前类的一个对象。谁调用这个方法,那么该方法的内部的this就代表谁
  • this的应用场景:
    解决局部变量隐藏成员变量(因就近原则而引起的变量指代混淆!)
面向对象-继承
继承的一些概念:
  • 继承概述:
           多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

  • 继承格式:
    通过extends关键字可以实现类与类的继承

    class 子类名 extends 父类名 {} 
    

    单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。

  • 继承的好处和弊端:

    • 好处:
      a. 提高了代码的复用性
      b. 提高了代码的维护性
      c. 让类与类之间产生了关系,是多态的前提
    • 弊端:
      类的耦合性增强了
  • 开发的原则:
    高内聚,低耦合
    耦合:类与类的关系
    内聚:就是自己完成某件事情的能力

Java中类的继承特点
  • JAVA只支持单继承,不支持多继承
    • 即:不支持一个子对象同时继承多个父对象。
  • JAVA支持多层继承(继承体系)
    • 即:一个子对象的父对象也是它“爷爷”对象的子对象。(该孙子类也具备了“爷爷”类的特性!)
  • 继承的注意事项:
    (1). 子类只能继承父类所有非私有的成员(包括成员方法和成员变量)。
    (2). 子类不能继承父类的构造方法,但是可以通过super关键字去访问父类构造方法。
    (3). 不要为了部分功能而去继承。(不符合程序设计:高内聚,低耦合的设计理念。)
  • 什么时候使用继承?
    (1). 继承其实体现的是一种关系:“is a” (子类是父类的一种!)
    (2). 采用假设法:
    如果有两个类A,B。只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。
  • 继承中成员变量的关系:
    A. 子类中的成员变量和父类中的成员变量名称不一样。
    B. 子类中的成员变量和父类中的成员变量名称一样。
    C. 在子类中访问一个变量的查找顺序:(“就近原则”)
    • 在子类的方法的局部范围找,有就使用。
    • 在子类的成员范围找,有就使用。
    • 在父类的成员范围找,有就使用。
    • 如果还找不到,就报错。
this和super的区别和应用:
  • super 的用途:
    子类局部范围访问父类成员变量。
  • 说说this和super的区别:

    this 代表的是本类对象的引用
    super代表的是父类存储空间的标识(可以理解成父类的引用,可以操作父类的成员)

  • this和super的使用:
    • 调用成员变量:
      this.成员变量   //调用本类的成员变量
      
      super.成员变量  //调用父类的成员变量
      
    • 调用构造方法
      this(...)		//调用本类的构造方法
      
      super(...)	    //调用父类的构造方法
      
    • 调用成员方法:
      this.成员方法 	//调用本类的成员方法
      
      super.成员方法 	//调用父类的成员方法
      
    • 注意:能调用这些方法的都是对象,是通过该类的对象来调用的,this,super都会有相应的对象来调用,在调用子类构造函数的同时,也会对父类对象进行初始化操作,所有才有一个能调用super的对象,详情请参考继承对象的内存图。
继承中的构造方法
  • 继承中的构造方法的关系:
    子类中所有的构造方法默认都会访问父类中空参数的构造方法
  • 这样做的原因:
    因为子类会继承父类中的数据,可能还会使用父类的数据,所以,子类初始化之前,一定要先完成父类数据的初始化。
  • 注意:
    1. 其实,每一个子类构造方法的第一条语句默认都是:super()
    2. Object类,元类,是所有类的父类。(否则父类的父类会是谁呢?就是这个object)
  • 继承中构造方法的注意事项:
    • 父类没有无参构造方法,子类怎么办?(之所以没有,可能是因为被方法重载了)
      a. 在父类中添加一个无参的构造方法(常用!)
      b. 子类通过super去显示调用父类其他的带参的构造方法(常用!)
      c. 子类通过this去调用本类的其他构造方法(本类其他构造也必须首先访问了父类的有参构造)
      示例:(报错,无法初始化父类对象!)
      public class DEMO4 {
          public static void main(String[] args) {
              Student2 student2 = new Student2("小波",10);
              student2.show();
          }
      }
      
      
      class Person{
          int x1 = 0;
      
          public Person(int x1){
              System.out.println("父类的构造函数已经调用!");
              this.x1 = x1;
          }
      
          public void show(){
              System.out.println("父类的对象在呼唤!");
          }
      
      }
      
      
      class Student2 extends Person{
          String name;
          int age;
      
          public Student2(String name, int age){    // 这样的话,由于子类无法初始化父类对象
          					                     // (默认调用空参构造函数),所以该行报错!
              this.name = name;
              this.age = age;
          }
      
          public void test(){
              super.show();
          }
      }
      
      示例:(正确方法!)
      public class DEMO4 {
          public static void main(String[] args) {
              Student2 student2 = new Student2("小波",10);
              student2.show();
          }
      }
      
      class Person{
          int x1 = 0;
      
          public Person(int x1){
              System.out.println("父类的构造函数已经调用!");
              this.x1 = x1;
          }
      
          public void show(){
              System.out.println("父类的对象在呼唤!");
          }
      
      }
      
      
      class Student2 extends Person{
          String name;
          int age;
      
          public Student2(String name, int age){
      		super(10);  // 加上这条语句就正常了!
              this.name = name;
              this.age = age;
              // super(20);     ---------> 如果把super放到这里来,也是报错!(所以必须放到构造函数的第一行去!)
          }
      
          public void test(){
              super.show();
          }
      }
      
    • super(…)或者this(….)必须出现在构造方法的第一条语句上
      详情请参考上述代码中的注释部分!
  • 经典例题:
    public class DEMO5 {
        public static void main(String[] args) {
            Zi z = new Zi(); //请执行结果。
        }
    }
    
    class Fu {
        static {
            System.out.println("静态代码块Fu"); //3  1
        }
    
        {
            System.out.println("构造代码块Fu"); //4 3
        }
    
        public Fu() {
            System.out.println("构造方法Fu"); //5 4
        }
    }
    
    class Zi extends Fu {
        static {
            System.out.println("静态代码块Zi"); //1  2
        }
    
        {
            System.out.println("构造代码块Zi"); //2 5
        }
    
        public Zi() {
            super();
            System.out.println("构造方法Zi"); //6    6
        }
    }
    ------------------------
    输出:
    静态代码块Fu
    静态代码块Zi
    构造代码块Fu
    构造方法Fu
    构造代码块Zi
    构造方法Zi
    
继承中成员方法关系:
  • 当子类的方法名和父类的方法名不一样的时候:
    用谁调谁!
  • 当子类的方法名和父类的方法名一样的时候:(跟成员变量的用法相似:就近原则!)
    则,如果通过子类调用方法:
    a: 先查找子类中有没有该方法,如果有就使用
    b:在看父类中有没有该方法,有就使用
    c: 如果没有就报错
方法重写:
  • 概念:
    子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也被称为方法覆盖,方法复写。
    简单来说:子类对父类功能的实现不满意,想要根据自己的差异性,来实现。那子类就可以进行方法重写,区覆盖掉父类的方法,在实际调用中,以子类重写的为准!
  • 方法重写和方法重载的区别?方法重载可以改变返回值类型吗?
    • 方法重写针对的是继承关系下的子类和父类的成员函数
    • 方法重载针对的是同一类中不同参数个数或形式的成员方法
    • 方法重载不能改变返回值类型,因为返回值类型不是方法是否重载的标志!(只是返回值类型不同的同名函数会报错!)
  • 方法重写的应用:
    当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。这样,即沿袭了父类的功能,又定义了子类特有的内容。
  • 方法重写的语法:
    子类写一个和父类的方法一模一样(返回值,形参,方法名)的方法就行
  • 方法重写和方法扩展:
    在方法重写的逻辑里面,再加入super.方法,即:相当于在子类中,对父类的方法上进行了方法扩展!
  • 在IDEA中快速实现子类对父类的方法重写,在继承的子类中按住Ctrl+o,即可快速生成重写的方法(会自动添加一个注解符号:@Override:检测方法,是不是重写父类的!),示例如下:
    public class Test {
        public static void main(String[] args) {
            Zi zi = new Zi();
            zi.show();
            System.out.println("这是主方法!");
        }
    }
    
    class Fu{
        public void show (){
            System.out.println("这是父类的方法!");
        }
    }
    
    class Zi extends Fu{
        @Override //注解符
        public void show() {
            super.show();
            System.out.println("这是子类的方法!");
        }
    }
    ---------------------------------------------
    输出:
    这是父类的方法!
    这是子类的方法!
    这是主方法!
    
  • 关于注解符号的详细内容,会在以后的课程内容中进行讲解!
  • 重写方法的注意事项:
    1. 父类私有的方法子类不能重写,因为私有的方法子类都不能继承,更不能重写
    2. 子类在重写父类方法时,子类方法前面的权限修饰符,不能比父类的低,要比父类的高,或者一样也行
    3. 权限修饰符:public > protected > 缺省的(就是在权限修饰符的位置啥也不写) > private
    4. 构造方法不能重写
    5. 静态方法不参与重写(可以用super方法来理解,super必须调用一个父类的对象,静态方法属于类,根本就没有对象-----------> 这样的理解好像也不对,因为new 子类的时候也会生成父类,但是确实不是重写,就像是重定义一样!利用Override注解符进行修饰的时候报错!)
继承中的final修饰符:(---->不等于权限修饰符)
  • 为什么会有final:
    由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法.这对这种情况java就给我们提供了一个关键字: final
  • final概述:
    final关键字是最终的意思,可以修饰类,变量,成员方法。
  • final修饰的特点:final可以理解为:最终版,则不会被改变,它就是最完美的了!
修饰对象特点
修饰类被修饰类不能被继承
修饰方法被修饰的方法不能被重写,子类只能继承下来使用
修饰变量该变量变成常量,该变量(常量)的值不能被改变
final 修饰引用数据类型指的是这个引用类型指向的地址值不能被改变!
  • 引出问题:JAVA中的常量到底指的是什么?
    • 常量值:常量值又称为字面常量,它是通过数据直接表示的,因此有很多种数据类型,像整型和字符串型等。

    • 常量:常量不同于常量值,它可以在程序中用符号来代替常量值使用,因此在使用前必须先定义。常量与变量(在《Java变量的声明和初始化》一节中讲解)类似也需要初始化,即在声明常量的同时要赋予一个初始值。常量一旦初始化就不可以被修改。它的声明格式为:

      Java 语言使用 final 关键字来定义一个常量,其语法如下所示:

      final dataType variableName = value
      
    • 详情请参考: Java常量的定义和分类:

    • 问题:在IDEA中,如果在同一个项目下,需要写一些重名的类,总会报错,难道在一个package下的类名都不能重复吗?

  • 继承中一个很经典的例题:(必看!)
    public class DEMO6 {
        public static void main(String[] args) {
            Cat cat = new Cat(10,"加菲猫");
            cat.run();
            cat.eat();
            Dog dog = new Dog(12,"二哈");
            dog.run();
            dog.eat();
        }
    }
    
    
    class Animal{
        int age;
        String name;
    
        public void eat(){
            System.out.println(name+"正在吃饭");
        }
    
        public void run(){
            System.out.println(name+"正在跑步!");
        }
    }
    
    
    class Cat extends Animal{
    
        Cat(int age, String name){
            this.age=age;
            this.name = name;
            System.out.println(this.name+"的初始化已经完成!");
            System.out.println("父类的名称为"+super.name);
        }
    
    
        public void eat(){
            System.out.println(this.name + "正在吃小鱼干!");
        }
    }
    
    
    class Dog extends Animal{
    
        Dog(int age,String name){
            this.age = age;
            this.name = name;
            System.out.println(this.name +"的初始化已经完成!");
            System.out.println("父类的名称为" + super.name);
        }
    
        public void eat(){
            System.out.println(this.name + "正在吃骨头!");
        }
    }
    ----------------------
    输出:
    加菲猫的初始化已经完成!
    父类的名称为加菲猫
    加菲猫正在跑步!
    加菲猫正在吃小鱼干
    二哈的初始化已经完成!
    父类的名称为二哈
    二哈正在跑步!
    二哈正在吃骨头!
    
    重点解析:
    1. 父类Animal中的成员变量被继承了以后,this.name 和super.name指向的都是同一个name,但这个name是属于父类对象的成员变量。(age同理可得!)
    2. 父类的方法一旦被继承下来调用的时候,此时父类的成员方法中引入的非局部变量前面都是隐形的this,代指这个父类的对象(这个父类的对象是在初始化子类的时候,自动初始化的父类对象),但这里,猫跟狗的父类虽然都是Animal,但是其父类对象各有不同(因为其属性值不同),所以有两个父类的对象。参考继承过程中的内存图可以清晰地明白这个过程知:在继承过程中,创建的父类对象所在的内存是在堆中子类对象内存中的一部分。
面向对象-多态
多态的基础知识:
  • 概念:
          指的是一种事物,在不用时刻,所表现出的不同状态。
    举例:
    Cat c=new Cat();   //普通写法
    猫可以是猫的类型。猫 m = new();
    ----------------------------------------------
    Animal a=new Cat();   //多态写法
    同时猫也是动物的一种,也可以把猫称为动物。动物 d = new();
    
  • 多态的前提:(三者必须同时存在,且缺一不可!)
    a. 要有继承关系
    b. 要有方法重写。 (其实没有也是可以的,但是如果没有这个就没有意义。 )
    c. 要用父类引用指向子类对象。
  • 多态的示例:
    public class MyTest {
        public static void main(String[] args) {
            //普通写法:
            // Cat cat = new Cat();
            // cat.eat();
    
            //多态的方式:父类引用,指向子类对象
            Animal an = new Dog();
            an.eat();
    
            an = new Cat();
            an.eat();
        }
    }
    
    class Animal {
        public void eat() {
            System.out.println("吃饭");
        }
    }
    
    class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("吃小鱼干");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void eat() {
            System.out.println("狗吃骨头");
        }
    }
    -------------------
    输出:
    狗吃骨头
    吃小鱼干
    
多态中的成员访问特点:
  • 多态的形式访问成员变量:编译看左边,运行看左边
    Fu fu = new Zi();
    system.out.println(fu.num) //编译看左边,运行看左边
    //打印的是父类定义的变量值
    //编译看左边的意思是:编译器检查报错
    //运行看右边的意思是:运行代码的时候,到底运行的是谁的代码  
    
  • 构造方法:
    创建子类对象的时候,会访问父类的构造方法,对父类的数据进行初始化。
  • 多态的形式访问成员方法的特点:编译看左边,运行看右边(子类重写了父类的方法)
    public class Test {
        public static void main(String[] args) {
    //        Zi zi = new Zi();
    //        zi.show();
    //        System.out.println("这是主方法!");
            // 父类引用指向子类对象:
            // 左(父类)  右(子类)
            Fu fu = new Zi();
            ((Zi) fu).show2(); //这样就不会报错!(这样感觉就成了普通的继承,并不是多态!)这叫做向下转换!
            //fu.show2(); //编译报错!(编译看左边,左边是父类,但父类没有相应的show2方法)
            Fu fu2 = new Zi2();
            fu2.show(); //运行的是右边的结果,即:子类的结果
        }
    }
    
    class Fu{
        public int age =100;
        public void show (){
            System.out.println(age);
            System.out.println("这是父类的方法!");
        }
    }
    
    class Zi extends Fu{
    
        //public int age;
        @Override //注解符
        public void show() {
            super.show();
            System.out.println(age);
            System.out.println("这是子类的方法!");
        }
    
        public void show2(){
            System.out.println("这是Zi的show2方法");
        }
    }
    
    class Zi2 extends Fu{
        @Override
        public void show() {
            //super.show();
            System.out.println("这是Zi2的show方法!");
    
        }
    }
    ----------------------------------------
    输出:
    这是Zi的show2方法
    这是Zi2的show方法!
    
  • 静态方法:
    编译看左边,运行看左边。(静态和类相关,算不上重写,所以,访问还是左边的)、
    示例:
    public class MyTest {
        public static void main(String[] args) {
    
            // Zi zi = new Zi();
            //  System.out.println(zi.num);
            //  zi.fuShow();
    
            //多态的形式访问成员变量的特点
            //多态的形式访问成员变量:编译看左边,运行看左边
            Fu fu = new Zi();
            System.out.println(fu.num);
    
            //多态的形式访问成员方法的特点:编译看左边,运行看右边 (子类重写了父类的方法)
    
            fu.fuShow();
    
            //静态方法:不参与重写
            fu.fuShow2();
            Fu.fuShow2();
            Zi.fuShow2();
            ((Zi)(fu)).fuShow2();
        }
    }
    
    class Fu {
        int num = 100;
    
        public void fuShow() {
            System.out.println("fu show");
        }
    
        public static void fuShow2() {
            System.out.println("fu 静态 show");
        }
    }
    
    class Zi extends Fu {
        int num = 200;
    
        @Override
        public void fuShow() {
            System.out.println("zi show");
        }
    
        public static void fuShow2() {
            System.out.println("zi 静态 show");
        }
    }
    ----------------------
    输出:
    100
    zi show
    fu 静态 show
    fu 静态 show
    zi 静态 show
    zi 静态 show
    
多态的优点和缺点:
  • 多态的好处:
    a. 提高了代码的维护性(继承保证)
    b. 提高了代码的扩展性(由多态保证) (详情可以参考下面的示例!!!即:在工具类的类方法中的形参的类型用父类来声明,传递实际参数的时候,用子类对象来代表实际参数,即:造成"父类引用指向子类对象的效果——多态"!)
    示例:
    • 父类:
      public class Animal {
          public void eat() {
              System.out.println("吃饭");
          }
      }
      
    • 子类Cat:
      public class Cat extends Animal{
          @Override
          public void eat() {
              System.out.println("猫吃鱼");
          }
      
          public void catchMouse(){
              System.out.println("猫抓老鼠");
          }
      }
      
    • 子类Dog:
      public class Dog extends Animal{
          @Override
          public void eat() {
              System.out.println("狗吃骨头");
          }
      }
      
    • 子类Mouse:
      public class Mouse extends Animal{
          @Override
          public void eat() {
              System.out.println("老鼠偷油吃");
          }
      }
      
    • 子类Tiger:
      public class Tiger extends Animal{
      	@Override
          public void eat() {
              System.out.println("脑斧爱吃兔纸");
          }
      }
      
    • 子类大象:
      public class 大象 extends Animal{
          @Override
          public void eat() {
              System.out.println("大象吃苹果");
          }
      }
      
    • 工具类MyUtils:
      public class MyUtils {
      	//私有构造,外界就无法创建,该类对象
          private MyUtils() {
      
          }
      
      	// 工具类声明了一个形式参数是以父对象类型来定义的!(如果传入的实参是该父对象的一个对象,则:此处构成了多态的写法!)
          public static void test(Animal an) {
              //   Animal an=cat  父类引用指向子类对象
              //   Animal an=dog 父类引用指向子类对象
              //多态的方式:在调用成员方法的时候,编译看左边,运行看右边
              //面向父类或接口 编程
              an.eat();
          }
          //------------------------------------
      	// 工具类以方法重载的方式来提供对主函数的调用:(这样写太过于繁琐,如果子类特别多,则不合理!)
          public static void test(Cat cat) {
              cat.eat();
          }
      
          public static void test(Dog dog) {
              dog.eat();
          }
      
          public static void test(Tiger tiger) {
              tiger.eat();
          }
      
          public static void test(Mouse mouse) {
              mouse.eat();
          }
      }
      
      结论:这里的好处是由多态带来的,工具类函数的形参定义为:父类的对象,此处若传入一个子类的对象,则:即可构成多态的写法!,利用多态编译看左边,运行看右边的原则。就可以实现一种写法,多种调用的结果,减少了工具类的函数编写的复杂度!
    • 主函数MyTest:
      public class MyTest {
          public static void main(String[] args) {
              //使用多态的好处:
              //1.提高了代码的复用性,是靠继承保证的
              //2.提高了代码的扩展性。
             Cat cat = new Cat();
             MyUtils.test(cat);  //使用工具类的方法来调用即可,不需要创建对象一个个来调用!(工具类的意义所在!)
      
             /*Dog dog = new Dog();
      
             MyUtils.test(dog);
      
             /* Tiger tiger = new Tiger();
              MyUtils.test(tiger);
      
              Mouse mouse = new Mouse();
              MyUtils.test(mouse);
              大象 dx = new 大象();
              MyUtils.test(dx);*/
      
          }
      }
      -------------------------------------
      输出:
      猫吃鱼
      
  • 测试类:
    只需要提供一个入口即可,不需要在测试类中写太多的类方法,即:测试类中最好只有一个main函数即可。
  • 多态的弊端:
    • 多态的形式,去访问子类特有的方法,是调用不到的!(必须利用向下类型转换来访问子类特有的方法!)
      就比如:不能在上面工具类的代码中用an这个对象去调用catMouse()这个方法!
      Father father = new Zi(); //向上转型!(其实多态就是向上转型!)
      Zi zi = (Zi) father; //向下转型!
      
      注意:多态本身就是“向上转型!”
    • 向下转型可能会遇到类型转换异常的情况:java.lang.ClassCastException(见老师上课演示)
      示例:(改变上面的工具类文件中的一个写法)
      public class MyUtils {
      	//私有构造,外界就无法创建,该类对象
          private MyUtils() {
      
          }
      
      	// 工具类声明了一个形式参数是以父对象类型来定义的!(如果传入的实参是该父对象的一个对象,则:此处构成了多态的写法!)
          public static void test(Animal an) {
              //   Animal an=cat  父类引用指向子类对象
              //   Animal an=dog 父类引用指向子类对象
              //多态的方式:在调用成员方法的时候,编译看左边,运行看右边
              //面向父类或接口 编程
              //an.eat();
              
          }
          //------------------------------------
      	// 工具类以方法重载的方式来提供对主函数的调用:(这样写太过于繁琐,如果子类特别多,则不合理!)
          public static void test(Cat cat) {
              cat.eat();
              Cat cat= (Cat) an;  //向下转换!
      		cat.catchMouse();   //向下转换后才能调用子类特有的方法!(但是这样只对猫类的对象来说是成立的,对于其他对象来说,统统都要报类型转换异常的错误!)
          }
      
          public static void test(Dog dog) {
              dog.eat();
          }
      
          public static void test(Tiger tiger) {
              tiger.eat();
          }
      
          public static void test(Mouse mouse) {
              mouse.eat();
          }
      }
      
      原因就是:其实写成了子类和子类之间的相互转换,这是不能转换的!只能把向上转型过的子类对象再向下回转(型)为原子类对象,不能把向上转型过的子类对象向下回转(型)为另一个子类对象(同属于一个父类的另一个子类)
      即:猫可以变为动物,也可以从动物变回猫,但是猫不能转换为狗!

      示例2:
      package westos.org.newtest;
      
      public class Test {
          public static void main(String[] args) {
              Cat cat = new Cat();
              Animal an = cat; //多态:向上转换!
              Cat cat1 = (Cat) an; // 向下转换!
              //Tiger tiger1 = (Tiger)an;(编译器不报错,但是运行的时候会报出类型转换异常的错误!)
              //上下两者是等价的
              //Tiger tiget1 = (Tiger)cat;(编译器直接报错,说明两个不同的子类是不能相互转换的!)
      
              Tiger tiger = new Tiger();
               //新建立一个父对象,然后测试它能否被直接转换为子类对象(说明父类对象不能被强制转换为子类对象,子类指针不能指向父类对象(除非是向上转换过以后的!),但是父类对象可以指向子类对象(多态))
              Animal an1 = new Animal();
              Cat cat2 = (Cat)an1;//报错(类型转换异常)java.lang.ClassCastException  westos.org.newtest.Animal cannot be cast to westos.org.newtest.Cat
              cat2.eat();
      		    }
      		}
      		
      		class Animal {
      		    String name;
      		    public void eat(){
      		        System.out.println(name+"这是Animal的eat方法!");
      		    }
      		}
      		
      		class Cat extends Animal{
      		    String name = "小猫咪";
      		    @Override
      		    public void eat() {
      		        System.out.println(this.name +"爱吃小鱼干!");
      		    }
      		}
      		
      		class Tiger extends Animal {
      		    String name = "小老虎";
      		    @Override
      		    public void eat() {
      		        System.out.println(name+"爱吃小猫咪!");
      		    }
      		}
      
多态(向上,向下类型转换)的一个非常经典的示例:“孔子装爹”
多态的内存图解
  • 图示:
    在这里插入图片描述
  • 动态绑定(在上课讲授多态情况下的内存图总提到的!)
    向上转换的时候,指针实际指向的区域是堆中子类对象中的一块super区域(初始化子类过程中初始化的父类对象),所以应用变量的时候它会直接取父对象的变量,但是在应用其方法的时候,会进行动态绑定,看子类中是否有定义的同名方法,如果有的话,则执行子类方法,否则,则执行父类方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值