(java)继承和多态 (详解)

目录

1 继承

1.1为什么需要继承

1.2 继承概念

1.3 继承的语法

1.4 父类成员访问 

1.4.1 子类中访问父类的成员变量

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

 1.5 super关键字

1.6 子类构造方法

1.7 super和this

1.7.1 this

1.7.2 super和this 

1.8 再谈初始化

1.9 继承方式

1.10 继承与组合

2 多态

2.1 多态的概念

2.2 多态实现条件

2.3 重写

2.4 向上转移和向下转型

2.4.1 向上转型

2.4.2 向下转型

2.5 多态的优缺点

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


1 继承


1.1为什么需要继承

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。
比如:狗和猫,它们都是一个动物


 那我们在对猫和狗的各种动作进行描述时,利用Java,就会设计出这样的代码

发现了吗,这里面有很多重复的地方 

那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。(共性的抽取,达到代码的复用)

1.2 继承概念

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
例如:上面的代码,狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用

图解:

上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类Dog和Cat可以称为Animal的子类/派生类继承之后子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可
 

 从继承概念中可以看出继承最大的作用就是:实现代码复用


1.3 继承的语法

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

修饰符 class 子类 extends 父类{

}

实操 :(将上面的代码用继承方法重新设计)

上面的代码: 

 

重新设计的: 

然后我们测试一下 :

 

运行: 

 

注意:1.没有赋值的话,默认放null或0

2.子类会将父类中的成员变量或者成员方法继承到子类中了
3. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

 

1.4 父类成员访问 

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

1.4.1 子类中访问父类的成员变量

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

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

总结:由上诉可知 

  • 如果访问的成员变量子类中有优先访问自己的成员变量。
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
  • 如果访问的成员变量与父类中成员变量同名与类型无关,同名都先访问自己),则优先访问自己的

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

1. 成员方法名字不同 


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

2. 成员方法名字相同
 

 总结:

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找找到则访问,否则在父类中找,找到则访问,否则编译报错
  • 通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问,如果没有则报错

 1.5 super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问是无法做到的Java提供了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中的methodA方法");
    }
    //与父类中的methodB构成重写
    public void methodB() {
        System.out.println("Derived中的methodB方法");
    }
    public void methodC(){
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 10;//相当于 this.a = 10
        b = '1';//相当于 this.b = '1'
        
        // 访问父类的成员变量时,需要借助super关键字
        // super是获取到子类对象中从基类继承下来的部分
        super.a = 3;
        super.b = 4;
        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)

        // 如果在子类中要访问重写的基类方法,则需要借助super关键字
        methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
        super.methodB(); // 访问基类的methodB()
    }
}

简单来说就是,想要明确访问父类中的成员,要使用 super 关键字 

 注意:

  • super只能在非静态方法中使用
  • super是在子类方法中,访问父类的成员变量和方法。 

ps:那super和this的区别是什么? 

  • this可以访问父类,也可以访问子类
  • this优先访问子类(有同名时)
  • super只能访问从父类继承下来的成员变量
  • 当前类使用了super,那当前类一定是子类

1.6 子类构造方法

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

  • 当子类继承了父类之后,一定要先帮助父类构造他的构造方法,然后再构造子类自己的构造方法(因为子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分
  • 父类的构造方法只能在子类中调用

代码演示: 

初学小贴士:构造方法不需要通过d.来访问,只要实例化对象就行 😀

                    (当调用完相应的构造方法之后,实例化对象才产生,也就是说你创建对象的时候即使不写构造方法,也会有一个默认的空构造方法)

注意:

  • 子类构造方法默认调用父类的无参构造方法super(),用户没有写时,编译器会自动添加,而且super(..)必须是子类构造方法中第一条语句,并且只能出现一次,并且不能和this()同时出现
  • 如果父类构造方法带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。

代码演示:

父类的构造方法有参数,子类没有,编译失败,如何做看下面吧

 在调用构造方法时,super(..)与this()不能同时出现,因为他们俩都只能在第一行

 


1.7 super和this

1.7.1 this

再说super和this之前我先来介绍一下this 

  1. this引用 用在形参名不小心与成员变量名相同时

那函数体中到底是谁给谁赋值?形参给成员变量?还是成员变量给形参?傻傻分不清楚😵😵😵

看看这样会打印出什么?


 

这不是我们想要的,这时候就要用this了,

我们可以使用"this.属性"或"this.方法"的方式,调用当前正在运行的对象属性或方法。

如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性(类中的属性),而非形参。

 总结:

  • this表示当前对象的引用,不能再引用其他对象(成员方法运行时调用该成员方法的对象)
  • this只能使用在非静态方法(又称实例方法,或成员方法)中谁调用这个实例方法,this就是谁。所以this代表的是:当前对象。
  • this不能在静态方法中使用, 因为this代表当前对象,静态方法中不存在当前对象。  

ps:静态方法静态方法属于类,可以在实例化之前被类直接调用

   2.可以通过this调用其他构造方法来简化代码 (语法this();)

  • this();调用当前类当中的其他构造方法(看匹配到那个,就调用那个)
  • 只能在当前的构造方法内部来使用
  • 只能在第一行 

1.7.2 super和this 

  • < 相同点>

1. 都是Java中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员的方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

  • <不同点>

1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3. 在构造方法中:this(...)用于调用本类中的其他构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有


1.8 再谈初始化

1、父类静态代码块优先子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

  


1.9 继承方式

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

  1.单继承
public class A{
}
public class  B extends A{
}
  2.多层继承
public class A{
}
public class  B extends A{
}

public class C extends B{

}

3.不同类继承同一个类 
public class A{
}
public class  B extends A{
}

public class C extends A{

}

注意:

  • Java中不支持多继承。(public class A{ }   public class B{ } public class C extends A,B )
  •  一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.
  • 如果想从语法上进行限制继承, 就可以使用 final 关键字 


1.10 继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。

  • 继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物
  • 组合表示对象之间是has-a的关系,比如:汽车

ps:  组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,

       一般建议:能用组合尽量用组合


2 多态
 


2.1 多态的概念

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

同一件事情,发生在不同对象身上,就会产生不同的结果。


2.2 多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:
1. 必须在继承体系下
2. 子类必须要对父类中方法进行重写
3. 通过父类的引用调用重写的方法

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


2.3 重写

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

 方法重写的规则:

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的

  • 访问权限不能比父类中被重写的方法的访问权限更低。(private < 默认权限 < protected < public)
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.

重写重载区别 :

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


2.4 向上转移和向下转型


2.4.1 向上转型

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

当向上转型,在运行时,会遗忘子类对象中与父类对象中不同的方法。也会覆盖掉与父类中相同的方法(重写)。(向上转型,无法访问子类特有的成员)
所以向上转型时,可以调用的方法就是,父类中有的,但是子类中没有的方法,和子类中的重写父类的方法

1. 直接赋值 


2. 方法传参


3. 方法返回

向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。

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

在编译的时候确实认为应该调用Animal的eat方法。但是运行的时候发现,子类重写父类的这个
eat方法,所以会直接调用子类 

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


2.4.2 向下转型

向下转型,可以调用子类的所有成员。

我们看下面的代码报错了,因为这样调用的是父类的fly()方法,但是父类中有没有fly()方法,当然报错上面的可以调用子类中的eat()方法,是因为父类中本来就有eat()方法,但是子类中又重写了eat方法,调用时发生了动态绑定,所以调用了子类的方法) 

此时就利用了向下转型,但是不安全 

 你往下看

报了一个错误: Dog cannot be cast to Bird

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


2.5 多态的优缺点

比如我们想打印【"●", "♦", "●", "♦", "❀"】, 那我们利用多态思想就会写出这样的代码

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

利用多态 :(动态绑定,向上转型)

public class Test {
    public static void drawMap(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        Shape[] shapes = {cycle, rect, cycle, rect, flower};
        for(int i = 0; i < shapes.length; i++){
           drawMap(shapes[i]);
        }
    }

}

如果没有多态就会这样:(很多if else 语句)

public class Test2 {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Rect rect = new Rect();
        Flower flower = new Flower();
        String[] shape = {"cycle", "rect", "cycle", "rect", "flower"};
        for(int i = 0; i < shape.length; i++){
            if (shape[i].equals("cycle")) {
                cycle.draw();
            } else if (shape[i].equals("rect")) {
                rect.draw();
            } else if (shape[i].equals("flower")) {
                flower.draw();
            }
        }
    }
}

总结

多态优点:

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

2. 可扩展能力更强
    如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低

 多态缺陷:代码的运行效率降低


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

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

public class B {
    public B() {
        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();
    }
}

 运行结果:

 

是0,可是我们的num明明被初始化赋值成了1 ,因为此时D这个对象还没有构造完成

 
所以在构造函数内,尽量避免使用实例方法,除了final和private方法。
结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

 

╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯完╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯╰(*°▽°*)╯

  • 37
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
Java继承和多Java面向对象编程中的重要概念。继承是指子类可以继承父类的属性和方法。在Java中,通过使用关键字"extends"来实现类的继承。例如,可以定义一个父类和一个子类,子类通过extends关键字继承父类的属性和方法。 多Java中的另一个重要概念,它是指同一个方法可以根据调用对象的不同而表现出不同的行为。方法的重写和重载是多性的不同表现。重写是指子类实现了和父类相同名称、相同参数列表和相同返回类型的方法,但是具体的实现可能不同。重载是指在同一个类中可以定义多个同名方法,但是参数列表不同。 通过继承和多Java中的类可以实现代码的重用、灵活性和扩展性。子类可以继承父类的属性和方法,并且可以根据需要进行方法的重写和重载,从而实现不同的行为。这样可以提高代码的可维护性和可扩展性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [java学习资料-Java继承和多](https://download.csdn.net/download/weixin_43516258/87905486)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [十三、 封装、继承和多](https://blog.csdn.net/CaesarQu/article/details/117373505)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值