JavaSE继承和多态

简介: JavaSE继承和多态

JavaSE基础-继承和多态


一、继承

1、继承概念

  1. 继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类
  2. 继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程
  3. 继承主要解决的问题是:共性的抽取,实现代码复用

0560e0a9ce22deb11a075d752fa97c04.png

extends关键字实现继承:

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

注:子类会将父类中的成员变量或者成员方法继承到子类中,子类可以添加自己的成员属性及方法


2、子类访问父类

  • 在子类方法中 或者 通过子类对象访问成员时:
  1. 如果访问的成员变量子类中有,优先访问自己的成员变量
  2. 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错
  3. 如果访问的成员变量与父类中成员变量同名,则优先访问自己的,如果想指定访问父类的则可以使用使用super关键字


  • 子类访问成员方法时:
  1. 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错
  2. 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错
  3. 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表相同(隐藏),使用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()
    }
}

注:在静态方法中不能使用this关键字和super关键字,因为静态方法不与对象进行绑定(不传入对象地址,无法操作指定对象)


2、子类构造

在继承中子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法

  • 注意:
  1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法
  2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败
  3. 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句
  4. super(…)只能在子类构造方法中出现一次,并且不能和this同时出现


3、super和this

  • 相同点:
  1. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  2. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
  • 不同点:
  1. this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
  2. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有


4、继承中的初始化顺序

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 TestDemo {
    public static void main(String[] args) {
        Student student1 = new Student("张三",19);
        System.out.println("===========================");
        Student student2 = new Student("111",20);
     }
}


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


  • 说明:
  1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
  2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
  3. 父类静态初始化先于子类,执行静态初始化再做构造,且父类构造先于子类构造


5、protected 关键字

92126ebf4b35034a7843805e8367f921.png

  • 说明:
  1. 父类中private成员变量虽然继承到子类中,但子类不能直接访问
  2. 在继承中,如果想让父类的成员属性或者方法可以让子类直接访问,而其他类不能访问,可以对成员属性或者方法使用protected修饰
  3. 总之,类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者


6、继承方式

  • Java中只支持以下几种继承方式:

9c7425d6f42060b1369f03a53a11bfcd.png

  • 注意:
  1. Java中不支持多继承
  2. 一般我们不希望出现超过三层的继承关系,如果继承层次太多,就需要考虑对代码进行重构
  3. 如果想从语法上进行限制继承, 就可以使用 final 关键字


7、final关键字

  1. final修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错


  1. final修饰类:表示此类不能被继承
final public class Animal {
...
}
public class Bird extends Animal {
...
} 
// 编译出错
//Error:(3, 27) java: 无法从最终com.bit.Animal进行继承


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

2477f2d5433ec5ab134e40af069889cd.png

8、继承和组合

和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果

组合并没有涉及到特殊的语法, 仅仅是将一个类的实例作为另外一个类的成员属性

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物

组合表示对象之间是has-a的关系,比如:汽车由轮胎、发动机、方向盘、车载系统等组合而成

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合


案例:

// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
} 
// 车载系统类
class VehicleSystem{
// ...
}
class Car{
private Tire tire; // 可以复用轮胎中的属性和方法
private Engine engine; // 可以复用发动机中的属性和方法
private VehicleSystem vs; // 可以复用车载系统中的属性和方法
// ...
} 
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}


二、多态


1、多态概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态

  • 在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、重写

  • 重写概念:
  1. 重写(override):也称为覆盖,子类重写父类函数,对父类函数进行覆盖
  2. 重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写,名称相同,返回值和形参不变
  3. 重写的好处在于子类可以根据需要,定义特定于自己的行为,即子类能够根据需要实现父类的方法


  • 方法重写的规则:
  1. 一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  2. 重写特例:被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  3. 访问权限不能比父类中被重写的方法的访问权限更低
  4. 父类被static、private修饰的方法、构造方法都不能被重写
  5. 重写的方法, 可以使用 @Override 注解来显式指定,可以校验是否重写了父类函数


  • 重写和重载的区别:
区别点 重写(override) 重载(override)
参数列表 一定不能修改 必须修改
返回类型 一定不能修改,除非可以构成父子类关系 可以修改
访问限定符 一定不能做更严格的限制(可以降低限制) 可以修改


  • 静态绑定:

也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法,典型代表函数重载


  • 动态绑定:

也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法


3、向上转移和向下转型

  • 向上转型:

实际就是创建一个子类对象,将其当成父类对象来使用

Animal animal = new Cat();


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

  • 使用场景:
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();
    }
}


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

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

  • 向下转型:

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

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。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();
        }
    }
}


4、多态的优缺点

  1. 能够降低代码的复杂度

将方法进行提取,对于不同状态进行重写,降低调用的复杂性

  1. 可扩展能力更强

如果要新增一种方法, 使用多态的方式代码改动成本也比较低,不影响原来的代码

  1. 代码的运行效率降低

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

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

cat = (Cat)animal;
        cat.mew();
    } if(
        animal instanceof Dog){
        dog = (Dog)animal;
        dog.bark();
    }
}


}

### 4、多态的优缺点
1. 能够降低代码的复杂度
将方法进行提取,对于不同状态进行重写,降低调用的复杂性
2. 可扩展能力更强
如果要新增一种方法, 使用多态的方式代码改动成本也比较低,不影响原来的代码
3. 代码的运行效率降低
属性没有多态性,当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
构造方法没有多态性,在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题  


相关文章
|
Java 编译器
【JAVASE】继承 中
【JAVASE】继承
|
7月前
javaSE&多态
javaSE&多态
37 1
|
安全 Java
【JAVASE】多态 下
【JAVASE】多态
|
7月前
|
安全 Java
JavaSE&匿名对象 , 继承 , 抽象类
JavaSE&匿名对象 , 继承 , 抽象类
21 0
|
Java 程序员 编译器
【JavaSE】一起学继承
【JavaSE】一起学继承
|
7月前
|
Java 编译器
JavaSE学习之--继承和多态(一)
JavaSE学习之--继承和多态
66 0
|
7月前
|
Java
JavaSE学习之--继承和多态(二)
JavaSE学习之--继承和多态(二)
73 0
|
7月前
|
Java 编译器
JavaSE学习之--继承和多态(三)
JavaSE学习之--继承和多态(三)
62 0
|
Java 程序员 编译器
【JavaSE】面向对象之继承
【JavaSE】面向对象之继承
|
Java 编译器
【JAVASE】多态 上
【JAVASE】多态