详解继承和多态

继承和多态

继承

继承,对共性进行抽取,从而实现代码的复用

继承的语法

在Java中要表示类之间的继承的关系,要使用extends关键字

public class 子类 extends 父类{
}
继承的快速入门

创建一个animal类,在创建一个cat类,dog类,让cat和dog去继承animal类

class Animal{
    String name;
    int age;
    public void eta(){
        System.out.println(name+"在吃饭");
    }
    public void sleep(){
        System.out.println(name+"在睡觉");
    }
}
class Cat extends Animal{
    public void mimi(){
        System.out.println(name+"正在咪咪叫");
    }
}
class Dog extends Animal{
    public void wangwang(){
        System.out.println(name+"正在汪汪叫");
    }
}
public class MAIN {
    public static void main(String[] args) {
        Dog dog = new Dog();//创建一个子类对象
        //dog中没有定义任何成员变量,name和age是从父类继承下来的
        dog.name = "小花";//通过子类的引用去访问
        dog.age = 10;
        //dog中也没用eat方法和sleep方法,eat和sleep都是从父类继承下来的
        dog.eta();
        dog.sleep();

    }
}
继承访问父类的成员
访问父类的成员属性

在继承体系中,子类将父类的方法和字段直接继承下来,如何在子类中访问父类的成员变量

1:子类和父类中成员变量没有重名的情况

//访问父类的成员变量 子类和父类的成员变量不重名时
class Base{
    int a;
    int b;

}
class Bus extends Base{
    int c;
    public void func(){
        a = 10;//访问父类继承下来的a
        b = 20;//访问父类继承下来的b
        c = 30;//访问自己的c
    }
}

2:子类和父类成员变量中有同名的情况

//访问父类的成员变量 子类和父类的成员变量重名时

class AA{
    int a;
    int b;
}
class BB extends AA{
    int a;
    int b;
    int c;
    public void method() {
        a = 10; //访问子类的属性 
        b = 20;//子类的属性
        c  = 30;
    }
}

注意:

  1. 在方法中或者子类对象中访问对象成员时,如果访问的成员变量子类中有,则访问子类中的属性,如果子类中没有找到,则访问父类继承下来的,如果父类也没有定义,则编译报错。如果子类中有和父类同名的属性,如果没有有super关键字,则优先访问自己的,如果有super,则访问父类的属性
  2. 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
访问父类的成员方法

1:成员方法名字不同

class base{
    public void methodA(){
        System.out.println("父类A");
    }
}
class s extends base{
    public void methodB(){
        System.out.println("子类B");
    }
    public void methodC(){
        methodA();//访问父类的methodA
        methodB();//访问子类的methodB
    }
}
public class MAIN02 {
    public static void main(String[] args) {
        s s = new s();
        s.methodC();
    }
}

输出:

父类A
子类B

注意:

成员方法没有同名时,在子类中或者通过子类对象去访问的话,则先到子类中寻找,如果找到,则访问,

如果没有找到,则到父类中找,如果父类中没有找到,则编译报错

2:成员方法名字相同

//访问成员方法中有同名的方法
class a{
    public void methodA(){
        System.out.println("父类A");
    }
    public void methodB(int n){
        System.out.println("父类B");
    }
}
class b extends a{
    public void methodA(int n){
        System.out.println("子类A");
    }
    public void methodB(){
        System.out.println("子类B");
    }
    public void methodC(){
        methodA(10);//子类A  有传参 先从子类找有参数的methodA方法
        methodB(10);//父类B  有传参  先从子类找有参数的methodB方法,
        // 子类没有找到有参数的methodB方法,到父类中找
        methodB();//子类B  无参,先到子类中找无参的,如有则访问
    }
}
public class MAIN03 {
    public static void main(String[] args) {
        b b = new b();
        b.methodC();
    }
}

注意:

  1. 通过子类对象访问子类或者父类中有不同名的方法时,优先在子类中寻找,找到则访问,子类中没有则在父类中寻找,如果有,则访问,如果没有则编译错误
  2. 如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;
super关键字

如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?

这里可以使用super关键字

super关键字的作用,在子类中访问父类的成员变量和成员方法

class Person{
    int a;
    int b;
    public void methodA(){
        System.out.println("父类A");
    }
    public void methodB(){
        System.out.println("父类B");
    }
}
class Student extends Person{
    int a;
    int b;
    public void methodA(){
        System.out.println("子类A");
    }
    public void methodB(){
        System.out.println("子类B");
    }
    public void methodC(){
        //访问子类的成员属性  名字相同
        a = 10;//访问的子类  对于同名的属性和方法,则现在子类中寻找,找到则访问
        b = 20;//子类  相当于 this.a = 10;this.b = 20;

        //访问父类的成员属性 因为父类和子类的成员属性是同名,则需要使用super
        super.a = 20;
        super.b = 30;

        //访问子类的成员方法   名字相同
        methodA();

        //如果是父类的methodA(),就需要super
        super.methodA();//访问的是父类的methodA();
    }
}
public class SUPER {
    public static void main(String[] args) {

    }
}

注意

super只能在非静态方法中使用

在子类的方法中,访问父类的成员属性和方法

子类的构造方法

继承可以理解为父子,那么父子父子,肯定是先有父,在有子,在使用构造方法是,需要先调用父类的构造方法完成父类的初始化,在完成子类的初始化。

class Person{

    public Person() {
        System.out.println("父类的无参构造1");
    }
}
class Student extends Person{
    public Student() {
        super();//编译器会自动添加一个super();默认会调用父类的无参构造
        //必须放在子类构造器的第一行,只能出现一次
        System.out.println("子类的无参构造2");
    }
}
public class MAIN01 {
    public static void main(String[] args) {
        Student student = new Student();
    }
}

输出:

父类的无参构造1
子类的无参构造2

注意:

子类对象中的成员是由两部分组成,一部分是自己的,一部分是继承父类的,所以在构造对象的时侯,先调用父类的构造方法初始化父类的成员,在调用子类的构造方法初始化子类的成员,将自己的成员构造完整,

class p1{
    String name;
    int age;

    public p1(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class p2 extends p1{
   double salary;

    public p2(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }
}
public class MAIN02 {
    public static void main(String[] args) {
        p2 p = new p2("hello",18,2000);
    }
}

父类的成员由父类初始化,子类的成员由子类初始化。

super和this
  1. super和this都可以在成员方法和构造方法中使用
  2. 在成员方法中使用this表示访问当前对象的引用,可以访问当前对象的属性和方法。
  3. 在构造方法中使用this表示访问当前类的其他构造方法。
  4. super在成员方法中使用表示访问父类的其他成员方法。
  5. 在构造方法中使用表示访问父类的其他构造方法。

相同点:

  1. 只能在类和非静态方法中使用,用来访问非静态的成员方法,因为静态成员方法不属于任何对象。所有使用this和super 会报错
  2. 在构造方法中使用,只能放在第一行,并且不能同时存在。

不同点:

  1. this表示当前对象的引用,super相当于子类从父类继承下来的部分成员的引用,但是绝不是父类的引用。
  2. 在非静态的成员方法中,this表示访问本类的方法和属性,super表示访问父类继承下来的方法和属性
  3. 在构造方法中,this(…)表示本类的构造方法,super(…)表示调用父类的构造方法,但是要注意,两种情况不能同时出现
  4. 构造方法中一定会有super的调用,即使没有写,但编译器也会增加
代码的执行顺序

在没有继承关系的代码里面,执行顺序是

静态代码块 实例代码块 构造方法

注意:静态代码块只有在类加载的时候执行一次,只执行一次。

继承关系上的执行顺序

class A{
    public A() {
        System.out.println("父类无参构造");
    }
    static{
        System.out.println("父类的静态代码块");
    }
    {
        System.out.println("父类的实例代码块/构造代码块");
    }
}
class B extends A{
    public B() {
        System.out.println("子类无参构造");
    }

    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类实例代码块/构造代码块");
    }
}
public class MAIN01 {
    public static void main(String[] args) {
        B b = new B();
        System.out.println("===========");
        B b1 = new B();
    }
}

输出:

父类的静态代码块
子类静态代码块
父类的实例代码块/构造代码块
父类无参构造
子类实例代码块/构造代码块

子类无参构造

===================

父类的实例代码块/构造代码块
父类无参构造
子类实例代码块/构造代码块
子类无参构造

静态代码块>实例代码块/构造代码块>构造方法

  1. 可以看出来静态的代码块只会执行一次,而且父类的静态代码块优先于子类的静态代码块
  2. 父类的实例代码块和父类的构造方法是紧接着执行的
  3. 子类的实例代码块和子类的构造方法紧接着再执行

注意:

在第二次实例化对象时,静态代码块将不会执行

下面代码将会输出什么?

class X{
    Y y=new Y();//1
    int a =10;
    int b = 20;
    public X(){//2
        System.out.print("X");
    }
}
class Y{
    public Y(){//3
        System.out.print("Y");
    }
}
public class Z extends X{
    Y y=new Y();//4
    public Z(){//5
        System.out.print("Z");
    }
    public static void main(String[] args) {
        new Z();
    }
}

需要注意的是在执行构造方法之前要先初始化本类的成员属性。

应该输出 YXYZ

代码的执行顺序:

  • 父类的静态
  • 子类的静态
  • 父类的实例
  • 父类的构造方法
  • 子类的实例
  • 子类的构造方法
final关键字

final可以用来修饰变量,成员方法和类。

修饰变量或字段:表示成为常量 不能修改

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

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

final public class A{
    
}
public class a extends A{
    
}
//编译报错

多态

多态是什么:当不同的对象去完成不同的事,会产生不同的状态

多态的前提:继承

从程序的角度理解多态

发生在不同对象上,就会产生不同的结果

多态实现的条件
  1. 必须在继承关系下
  2. 子类必须对父类的方法进行重写
  3. 通过父类的引用去调用重写的方法
多态的体现

当我们的代码运行时,当传递不同的对象,就会调用对应类中的方法

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+"吃饭");
    }
}
class Cat extends Animal{

    public Cat(String name, int age) {
        super(name, age);
    }

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

    @Override
    public void eat() {
        System.out.println(name+"吃骨头");
    }
}
public class test01 {
    
    public static void eat(Animal animal){
        animal.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("小花",28);
        Dog dog = new Dog("大黄", 18);
        eat(cat);
        eat(dog);

    }
}
  1. 编译器在编译代码时并不知道要调用dog或者cat的eat方法

  2. public static void eat(Animal animal){
            animal.eat();
        }//等程序运行起来之后,形参animal引用的对象确定后,才知道调用的是那个的方法
    
  3. 但是形参的类型必须是父类的类型才可以 父类的引用可以指向子类

运行结果

小花吃鱼
大黄吃骨头

当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 animal 引用指向的是哪个类型(哪个子类)的实例. 此时 animal这个引用调用 eat方法可能会有多种不同的表现(和 animal 引用的实例 相关), 这种行为就称为 多态.

重写

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

重写:

  1. 方法名称相同
  2. 参数列表相同
  3. 返回值相同

注意:

  1. 如果是private修饰的方法,则不能被重写
  2. static修饰的方法是不能被重写的
  3. 子类的访问修饰限定权限要大于等于父类的权限
  4. private<默认<protected<public
  5. 被final修饰的方法不能被重写,这个方法被称作密封方法

重写应用场景

当有一个类,不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用

正确做法是:新建一个类,对原有的这个方法重写就好了,这样就达到了我 们当今的需求了。

静态绑定和动态绑定

静态绑定:程序在编译时,编译器根据用户的参数,就已经知道了要调用用哪些方法,典型的方法重载

动态绑定:即程序在编译时,不能确定要掉那个具体的方法,只有等程序运行时,才能确定调用那个具体的方法

动态绑定 是多态的基础 运行时帮我们调用了重写的方法

向上转型和向下转型
向上转型

当我们发生向上转型之后,通过父类的引用,只能访问父类自己的成员,和子类中重写了父类的方法,不能访问子类特有的成员

父类的引用指向了子类的对象

语法格式 父类类型 引用名 = new 子类类型()

Animal animal = new Cat();
 Animal animal = new Cat("小花",10);//向上转型
        animal.eat();

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

向上转型的优点:让代码更加的灵活

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+"吃饭");
    }
}
class Cat extends Animal{

    public Cat(String name, int age) {
        super(name, age);
    }
    public void show(){
        System.out.println("这是子类特有的方法");
    }

    @Override
    public void eat() {
        System.out.println(name+"吃鱼");
    }
}
public class test01 {
    public static void main(String[] args) {
        Animal animal = new Cat("小花",10);
        animal.eat();
        animal.show();//报错,向上转型不能调用子类特有的方法

    }
}
向下转型

那如果一定要调用子类中特有的方法呢?

此时可以使用向下转型

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

但是向下转型是不安全的,万一转换失败,程序就会报错。

向下转型的前提:

先进行向上转型 向上转型之后,再进行向下转型

  1. 要强转的引用必须指向当前类型的目标对象
  2. ​ 此时animal指向Cat()
  3. ​ 向下转型就是将animal指向的猫还原为猫
  4. 只能强制转换父类的引用,不能强制转换父类的对象
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+"吃饭");
    }
}
class Cat extends Animal{

    public Cat(String name, int age) {
        super(name, age);
    }
    public void show(){
        System.out.println("这是子类特有的方法");
    }

    @Override
    public void eat() {
        System.out.println(name+"吃鱼");
    }
}
public class test01 {
    public static void main(String[] args) {
        Animal animal = new Cat("小花",10);//向上转型
        animal.eat();
        animal.show();//报错,向上转型不能调用子类特有的方法
        Cat cat = (Cat)animal;//向下转型第一种写法
        cat.show();
        //((Cat)animal).show();//向下转型第二种写法


    }
}

Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。

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 + "吃饭");
    }
}

class Cat extends Animal {

    public Cat(String name, int age) {
        super(name, age);
    }

    public void show() {
        System.out.println("这是子类特有的方法");
    }

    @Override
    public void eat() {
        System.out.println(name + "吃鱼");
    }
}
public class test01 {
    public static void main(String[] args) {
        Animal animal = new Cat("小花", 10);
        animal.eat();
        
        
        //animal.show();//报错,向上转型不能调用子类特有的方法
        //要强转的引用必须指向当前类型的目标对象
        //此时animal指向Cat()
        //向下转型就是将animal指向的猫还原为猫

        if(animal instanceof Cat){ //判断animal引用是否指向了Cat对象
            Cat cat = (Cat)animal;//向下转型第一种写法
            cat.show();
        }

        //((Cat)animal).show();//向下转型第二种写法
        
    }
}
  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值