Java继承和多态

目录

1 继承

1.1 什么是继承

1.2 为什么需要继承

1.3 继承的语法

1.4 父类成员访问

1.4.1 子类中访问父类的成员变量的不同情况

1. 子类和父类不存在同名成员变量

2. 子类和父类成员变量同名

1.4.2 子类中访问父类的成员方法有那些

1. 成员方法名字不同

2. 成员方法名字相同

1.5 super关键字

1.6 子类构造方法

1.7 super和this的区别

1.8 有/无继承关系的初始化的打印区别

1.9 protected 关键字

1.10 final 关键字---最终的意思

2.多态

2.1概念

2.2 多态实现条件

2.3 重写

重写和重载的区别

重写例子

重载例子

效果图

2.4 向上转移和向下转型

2.4.1 向上转型

2.4.2 向下转型

2.5 多态的优缺点

优点

1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else

2. 可扩展能力更强

缺点

代码的运行效率降低

2.6 避免在构造方法中调用重写的方法

结论

3.总结


1 继承

1.1 什么是继承

继承是面向对象编程中的一个基本概念,它允许我们根据一个已存在的类来定义一个新的类,这个新的类称为子类(或派生类),而它基于的类称为父类(或基类、超类)。子类继承了父类的属性和方法,并且可以添加新的属性或重写(Override)继承的方法,或者添加新的方法。

1.2 为什么需要继承

  1. 代码复用
    继承允许子类继承父类的属性和方法,从而避免了在子类中重复编写相同的代码。这不仅可以减少代码量,还可以提高代码的可维护性和可读性。当父类中的代码需要修改时,所有继承自该父类的子类都会自动继承这些修改,而无需在每个子类中单独修改。

  2. 建立类之间的层次关系
    通过继承,可以清晰地表达类之间的“是一种”的关系。这种层次关系有助于我们理解和组织复杂的系统。例如,在动物分类学中,猫是动物的一种,因此可以设计一个Animal类作为基类,然后设计一个Cat类继承自Animal类。这种层次关系使得类的结构更加清晰,也更容易理解和维护。

  3. public class Main {
        public static void main(String[] args) {
            Dog dog1 = new Dog("小狗1");
            System.out.println("============================");
            Dog dog2 = new Dog("小狗2");
        }
    }
    
        class Dog {
            public String name;
            public Dog(String name) {
                super();
                System.out.println("Dog构造方法执行");
            }
            {
                System.out.println("Dog实例代码块执行");
            }
            static {
                System.out.println("Dog静态代码块执行");
            }
    
            public void Animal(String name) {
                this.name = name;
                System.out.println("Animal(string)");
            }
            {
                System.out.println("Animal实例代码块执行");
            }
            static {
                System.out.println("Animal静态代码块执行");
            }
        }
    
    
  4. 多态性的基础
    继承是实现多态性的基础。多态性允许我们以统一的接口处理不同类型的对象。

  5. 扩展功能
    继承不仅允许子类继承父类的功能,还允许子类在继承的基础上添加新的功能或修改已有的功能。

  6. 遵循开闭原则
    开闭原则(Open-Closed Principle)是面向对象设计中的一个重要原则,它要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

1.3 继承的语法

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类 {
 // ...  
}

代码示例

package demo3;

//dog和cat在单独且同一个class里面存在会形成了多个类
//影响输出效果,所有要建立一个包
//在包里面定义才不会影响这个问题

//狗这个类的书写
class Dog{
    //基本信息,年龄之类的
    public String name;
    public int age;
    public String color;

    //构造函数==接收函数的值
    public Dog(String name,int age,String color){
        this.name=name;
        this.age=age;
        this.color=color;
    }

    //别的函数会造成什么效果/做出什么动作
    public void eat(){
        System.out.println(name+" 正在吃骨头 ");
    }

    public void wangWang(){
        System.out.println(name+" 汪汪叫 ");
    }

}

class Cat{
    public String name;
    public int age;
    public String color;

    public Cat(String name,int age,String color){
        this.name=name;
        this.age=age;
        this.color=color;
    }

    public void eat(){
        System.out.println(name+" 吃猫条 ");
    }

    public void miMi(){
        System.out.println(name+" 喵喵叫 ");
    }

}

public class fgh {
    public static void main(String[] args) {
        //调用了这些函数了之后,要给空间开辟不然装不下生成的东西
        Dog dog=new Dog("大黄",15,"黄色");
        //类引用
        dog.eat();
        dog.wangWang();

        Cat cat=new Cat("小白",18,"白色");
        cat.eat();
        cat.miMi();

    }
}

注意:

1. 子类会将父类中的成员变量或者成员方法继承到子类中了

2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则没有必要继承了

1.4 父类成员访问

在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?

1.4.1 子类中访问父类的成员变量的不同情况

1. 子类和父类不存在同名成员变量
public class Test7 {
        int a;
        int b;
    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.method();
        System.out.println("a = " + derived.a);
        System.out.println("b = " + derived.b);
        // 注意:c是Derived类的私有成员,除非有getter方法,否则不能直接从外部访问
    }
}
    class Derived extends Test7{
        int c;
        public void method(){
            a = 10;    // 访问从父类中继承下来的a
            b = 20;    // 访问从父类中继承下来的b
            c = 30;    // 访问子类自己的c
        }
    }

2. 子类和父类成员变量同名
//父类
public class Base {
    int a;
    int b;
    int c;

    public static void main(String[] args) {

        //声明空间
        Jkl jkl = new Jkl();

        //调用
        jkl.method();

        //打印
        System.out.println("a = " + jkl.a);
        System.out.println("b = " + jkl.b);
    }

 }
 

 public class jkl extends Base{
    int a;              // 与父类中成员a同名,且类型相同
    char b;             // 与父类中成员b同名,但类型不同
 
    public void method(){
        a = 100;        // 访问父类继承的a,还是子类自己新增的a?
        b = 101;        // 访问父类继承的b,还是子类自己新增的b?
        c = 102;        // 子类没有c,访问的肯定是从父类继承下来的c
        // d = 103;     // 编译失败,因为父类和子类都没有定义成员变量d
    }
 }

解析:

当子类和父类有同名的成员变量时,这些变量被认为是两个完全不同的变量,即使它们的类型可能相同或不同。在子类中,子类自己声明的成员变量会遮蔽(隐藏)从父类继承来的同名成员变量。

所以在method中a,b是访问子类定义的值,并且屏蔽掉父类中a,b的值。

总结:

子类方法中 或者 通过子类对象访问成员时:

·如果访问的成员变量子类,优先访问自己的成员变量。

·如果访问的成员变量子类,则访问父类继承下来的,如果父类也没有定义,则编译报错。

·如果访问的成员变量与父类中成员变量同名,则优先访问自己的。

1.4.2 子类中访问父类的成员方法有那些

1. 成员方法名字不同

看是否加上了extends

public class Base {
     public void methodA(){
         System.out.println("Base中的methodA()");
     }
 }
 
//关键加上extends
public class Derived extends Base{
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    
    public void methodC(){
        methodB();         // 访问子类自己的methodB()
        methodA();         // 访问父类继承的methodA()
        // methodD();      // 编译失败,在整个继承体系中没有发现方法methodD()
    }
 }

总结:

成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有再到父类中找,如果父类中也没有则报错

2. 成员方法名字相同

看是否传递了值

public class Base {
     public void methodA(){
         System.out.println("Base中的methodA()");
     }
 
     public void methodB(){
         System.out.println("Base中的methodB()");
     }
 }
 
public class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的method(int)方法");
    }
 
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
 
    public void methodC(){
        methodA();      // 没有传参,访问父类中的methodA()
        methodA(20);    // 传递int参数,访问子类中的methodA(int)
        methodB();      // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
 }

【说明】

·通过子类对象访问父类与子类中不同名方法时,优子类中找,找到则访问,后父类中找,找到则访,找不到则编译报错

·通过派生类对象(子类)访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(不重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;

·如果子类和父类都有且同名永远是子类。

1.5 super关键字

子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?该关键字主要作用:在子类方法中访问父类的成员。

public class Base {
    int a;
    int b;
 public void methodA(){
     System.out.println("Base中的methodA()");
 }
 
 public void methodB(){
     System.out.println("Base中的methodB()");
 }
 }
public class Derived extends Base{
    int a;    // 与父类中成员变量同名且类型相同
    char b;   // 与父类中成员变量同名但类型不同
    // 与父类中methodA()构成重载
    public void methodA(int a) {
        System.out.println("Derived中的method()方法");
    }
 
    // 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
 
    public void methodC(){
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 100;   // 等价于: this.a = 100;
        b = 101;   // 等价于: this.b = 101;
        // 注意:this是当前对象的引用
 
        // 访问父类的成员变量时,需要借助super关键字
        // super是获取到子类对象中从基类继承下来的部分
        super.a = 200;
        super.b = 201;
 
 
        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA();      // 没有传参,访问父类中的methodA()
        methodA(20);    // 传递int参数,访问子类中的methodA(int)
 
        // 如果在子类中要访问重写的基类方法,则需要借助super关键字
        methodB();      // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
        super.methodB(); // 访问基类的methodB()
    }
 }

【注意事项】 1. 只能在非静态方法中使用 2. 在子类方法中,访问父类的成员变量和方法。

1.6 子类构造方法

父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

public class Base {
    public Base(){
        System.out.println("Base()");
 }
}
public class Derived extends Base{
 public Derived(){
 // super();   // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
 // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
 }
 }
 public class Test {
 public static void main(String[] args) {
 Derived d = new Derived();
 }
 }
结果打印:
Base()
 Derived()

子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整

注意:

1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构 造方法

2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。

3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。

4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现

1.7 super和this的区别

成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

【相同点】

1. 都是Java中的关键字

2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段

3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

【不同点】

1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用

2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性

3. 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现

4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有

1.8 有/无继承关系的初始化的打印区别

实例代码块和静态代码块

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("构造方法执行");
    }
    {
        System.out.println("实例代码块执行");
    }
    static {
        System.out.println("静态代码块执行");
    }
 }
 
public class TestDemo {
    public static void main(String[] args) {
        Person person1 = new Person("bit",10);
        System.out.println("============================");
        Person person2 = new Person("gaobo",20);
    }
 }

执行结果:

静态代码块执行
实例代码块执行
构造方法执行
============================
实例代码块执行
构造方法执行

1. 静态代码块执行,并且只执行一次,在类加载阶段执行

2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

【继承关系上的执行顺序】

class Person {
 public String name;
 public int age;
 public Person(String name, int age) {
 this.name = name;
 this.age = age;
 System.out.println("Person:构造方法执行");
 }
 {
 System.out.println("Person:实例代码块执行");
 }
 static {
 System.out.println("Person:静态代码块执行");
 }
 }
 class Student extends Person{
 public Student(String name,int age) {
 super(name,age);
 System.out.println("Student:构造方法执行");
 }
 {
 }
 System.out.println("Student:实例代码块执行");
 static {
 System.out.println("Student:静态代码块执行");
 }
 }
 public class TestDemo4 {
 public static void main(String[] args) {
 Student student1 = new Student("张三",19);
 System.out.println("===========================");
Student student2 = new Student("gaobo",20);
}
 public static void main1(String[] args) {
 Person person1 = new Person("bit",10);
 System.out.println("============================");
 Person person2 = new Person("gaobo",20);
 }
 }

执行结果:

Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
 Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行

结论:

1、父类静态代码块优先于子类静态代码块执行,且是最早执行

2、父类实例代码块和父类构造方法紧接着执行

3、子类的实例代码块和子类构造方法紧接着再执行

4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

执行次序:静态>动态>构造方法

       次数: 1         多       多

1.9 protected 关键字

主要限定:类或者类中成员能否在类外或者其他包中被访问。

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
 private int a;
 protected int b;
 public int c;
 int d;
 }
 // extend01包中
// 同一个包中的子类
public class D extends B{
 public void method(){
 // super.a = 10;      
// 编译报错,父类private成员在相同包子类中不可见
super.b = 20;         
super.c = 30;         
super.d = 40;         
}
 }
 // 父类中protected成员在相同包子类中可以直接访问
// 父类中public成员在相同包子类中可以直接访问
// 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
// extend02包中
// 不同包中的子类
public class C extends B {
 public void method(){
 // super.a = 10;     // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20;        
比特就业课
// 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30;        
//super.d = 40;      
}
 }
 // 父类中public修饰的成员在不同包子类中可以直接访问
// 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
// extend02包中
// 不同包中的类
public class TestC {
 public static void main(String[] args) {
 C c = new C();
 c.method();
 // System.out.println(c.a);   // 编译报错,父类中private成员在不同包其他类中不可见
// System.out.println(c.b);   // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c);      
// 父类中public成员在不同包其他类中可以直接访问
// System.out.println(c.d);   // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
 }

注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了

1.10 final 关键字---最终的意思

final关键可以用来修饰变量、成员方法以及类。

1. 修饰变量或字段,表示常量(即不能修改)

final int a = 10;
a = 20;  // 编译出错

2. 修饰类:表示此类不能被继承

f
 inal public class Animal {
 ...
 }
 public class Bird extends Animal {
 ...
 }
 // 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承.

3. 修饰方法:表示该方法不能被重写

2.多态

2.1概念

去完成某个行为,当不同的对象去完成时会产生出不同的状态

eg。行为:吃苹果

        不同对象:一大口吃,切着吃

        不同状态:容易被噎住,容易咀嚼

2.2 多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

1. 必须在继承体系下

2. 子类必须要对父类中方法进行重写

3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

public class Animal {
 String name;
 int age;
 public Animal(String name, int age){
 this.name = name;
 this.age = age;
 }
 public void eat(){
 System.out.println(name + "吃饭");
 }
 }
 public class Cat extends Animal{
 public Cat(String name, int age){
 super(name, age);
 }
 @Override
 public void eat(){
 System.out.println(name+"吃鱼~~~");
 }
 }
 public class Dog extends Animal {
 public Dog(String name, int age){
 super(name, age);
 }
 @Override
 public void eat(){
 System.out.println(name+"吃骨头~~~");
 }
 }

---------------------------------分割---------------------------------

 public class TestAnimal {
 // 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
// 注意:此处的形参类型必须时父类类型才可以
比特就业课
public static void eat(Animal a){
a.eat();
 }
 public static void main(String[] args) {
 Cat cat = new Cat("元宝",2);
 Dog dog = new Dog("小七", 1);
 eat(cat);
 eat(dog);
 }
 }
运行结果:
元宝吃鱼~~~
元宝正在睡觉
小七吃骨头~~~
小七正在睡觉

2.3 重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

【方法重写的规则】

·子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型方法名 (参数列表) 要完全一致

·被重写的方法返回值类型可以不同,但是必须是具有父子关系的

·访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为 protected

·父类被static、private修饰的方法、构造方法都不能被重写。

·重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.

重写和重载的区别

即:方法重载是一个类的多态性表现。 方法重写是子类与父类的一种多态性表现

重写例子

//重写
//父类
public class Base {
     public void methodA(){
         System.out.println("jkl");
     }
 }
 
//子类---extends
//父子类方法名同为methodA
//参数名同为methodA()中的空参数
public class Derived extends Base{
    
    public void methodA(){
       System.out.println("abc");
    }
 }

重载例子

//重载
//父类
public class Base {
     public void methodA(){
         System.out.println("jkl");
     }
 }
 
//子类---extends
//父子类方法名同为methodA
//参数名父类为methodA()中的空参数,
//子类为int num
public class Derived extends Base{
    
    public void methodA(int num){
            for(int i=0;i<num;i++)
       System.out.println("abc");
    }
 }

效果图

2.4 向上转移和向下转型

2.4.1 向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

eg.猫和狗都是动物,就可以用动物来代替它们。

语法格式:父类类型 对象名 = new 子类类型()

//代码示例
Animal animal = new Cat("张三",2);

animal是父类类型,但可以引用一个子类对象,因为是从小范围向大范围的转换。

【使用场景】

1. 直接赋值

2. 方法传参

3. 方法返回

三种应用场景的代码示例

 public class TestAnimal {
    // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eatFood(Animal a){
        a.eat();
    }
 
    // 3. 作返回值:返回任意子类对象
    public static Animal buyAnimal(String var){
        if("狗".equals(var) ){
            return new Dog("狗狗",1);
        }else if("猫" .equals(var)){
            return new Cat("猫猫", 1);
        }else{
            return null;
        }
    }
 
    public static void main(String[] args) {
        Animal cat = new Cat("元宝",2);   // 1. 直接赋值:子类对象赋值给父类对象
        Dog dog = new Dog("小七", 1);     //名称直接用声明父类的类型
 
        eatFood(cat);
        eatFood(dog);
 
        Animal animal = buyAnimal("狗");
        animal.eat();
 
        animal = buyAnimal("猫");
        animal.eat();
    }
 }

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

2.4.2 向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象即可,即向下转换。

 public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
 
        // 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
 
        // 编译失败,编译时编译器将animal当成Animal对象处理
        // 而Animal类中没有bark方法,因此编译失败
        // animal.bark();
 
        // 向上转型
        // 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
        // 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.mew();
 
        // animal本来指向的就是狗,因此将animal还原为狗也是安全的   
        dog = (Dog)animal;
        dog.bark();
    }
 }

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了instanceof(实例运算符),如果该表达式为true,则可以安全转换

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
 
        // 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
 
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        }
 
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
 }

2.5 多态的优缺点

 class Shape {
    //属性....
    public void draw() {
        System.out.println("画图形!");
    }
 }
 class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
 }
 class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("●");
    }
 }
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
 }

优点

1. 能够降低代码的 "圈复杂度", 避免使用大量的 if - else

什么叫 "圈复杂度" ? 圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如 果有很多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度". 如果一个方法的圈复杂度太高, 就需要考虑重构. 

例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:

 public static void drawShapes() {
    Rect rect = new Rect();
    Cycle cycle = new Cycle();
    Flower flower = new Flower();
    String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
    
    for (String shape : shapes) {
        if (shape.equals("cycle")) {
            cycle.draw();
        } else if (shape.equals("rect")) {
            rect.draw();
        } else if (shape.equals("flower")) {
            flower.draw();
        }
    }
 }

如果使用使用多态, 则不必写这么多的 if - else 分支语句, 代码更简单

public static void drawShapes() {
    // 我们创建了一个 Shape 对象的数组. 
    Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), 
                      new Rect(), new Flower()};
    for (Shape shape : shapes) {
        shape.draw();
    }
 }

2. 可扩展能力更强

如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("△");
    }
 }

对于类的调用者来说(drawShapes方法), 只要创建一个新类的实例就可以了, 改动成本很低. 而对于不用多态的情况, 就要把 drawShapes 中的 if - else 进行一定的修改, 改动成本更高.

缺点

代码的运行效率降低

1. 属性没有多态性当父类和子类都有同名属性的时候通过父类引用,只能引用父类自己的成员属性

2. 构造方法没有多态性

2.6 避免在构造方法中调用重写的方法

一段有坑的代码. 我们创建两个类, B 是父类, D 是子类. D 中重写 func 方法. 并且在 B 的构造方法中调用 func

 class B {
    public B() {
        // do nothing
        func();
    }
 
    public void func() {
        System.out.println("B.func()");
    }
 }
 
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
 }
 
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
 }
 
// 执行结果
D.func() 0

·构造 D 对象的同时, 会调用 B 的构造方法.

·B 的构造方法中调用了 func 方法, 此时会触发动态绑定(D是子类,B是父类,子类先运行), 会调用到 D 中的 func

·此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.

·所以在构造函数内,尽量避免使用实例方法除了finalprivate方法。

结论

尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

3.总结

Java 中继承允许子类继承父类属性和方法,可通过 extends 实现。多态需在继承体系下满足条件实现,有向上转型和向下转型,多态虽有优点但也存在运行效率降低等缺点,构造方法中应避免调用重写方法。


希望这篇文章能够帮助读者解决遇到的一点问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值