JAVA初阶——继承和多态

news2025/1/17 13:47:03

目录

一、继承

1、定义:

2、用法:

3、使用从父类继承的成员

(1)、子类使用从父类继承的成员变量

(2)、子类使用从父类继承的成员方法

4、super

(1)、定义:

5、子类构造方法

(1)、定义

6、执行顺序

(1)、没有继承关系时

(2)、有继承关系时

7、继承方式

(1)、单继承

(2)、多继承

(3)、不同类继承同一个类

8、final

(1)、final修饰变量或字段

(2)、修饰方法

(3)、修饰类

9、继承与组合

(1)、定义

二、多态

1、定义

2、使用

3、重写

(1)、定义

(2)、规则

(3)、绑定 

4、向上转型和向下转型

(1)、向上转型

(2)、向下转型

5、多态优缺点

(1)、优点

(2)、缺点

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


一、继承

1、定义:

在Java语言中,两个不同类可能会有部分重叠的属性和方法,因此为了减少代码的重复率,Java提出了继承的概念以提高代码的复用率。

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

2、用法:

Java语言中,类的继承关系,需要使用关键字extends

public class 子类 extends 父类 {

}

例: 

class person{
    public String name;
    public int age;
    public void eat(){
        System.out.println("吃饭!!!");
    }
    public void sleep(){
        System.out.println("休息!!!");
    }
}

class student extends person{
    public void learning{
        System.out.println("上课!!!")
    }
}

class teacher extends person{
    public void teaching{
        System.out.println("教学!!!")
    }
}

public class test {
    public static void main(String[] args) {
        student s1 = new student();
        // student类中并没有定义任何成员变量,name和age属性是从父类person继承下来的
        System.out.println(s1.name);
        System.out.println(s1.age);
        // s1访问的eat()和sleep()方法也是从person中继承下来的
        s1.eat();
        s1.sleep();
    }
}

注:

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

3、使用从父类继承的成员

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

(1)、子类使用从父类继承的成员变量

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

public class A {
    int a;
    int b;
}
public class B extends A{
    int c;
    public void func(){
        a = 10; // 访问从父类中继承下来的a
        b = 20; // 访问从父类中继承下来的b
        c = 30; // 父类没有c,访问子类的c
    }
}

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

public class A {
    int a;
    int b;
} 
public class B extends A{
    int a; // 与父类中成员a同名,且类型相同
    public void method(){
        a = 1; // 访问子类的a!!!
        b = 2; // 子类没有b,访问的是从父类继承下来的b
        //c = 3; // 编译失败,因为父类和子类都没有成员变量c
    }
}

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

  • 如果访问的成员变量子类中有,优先访问自己的成员变量;若子类中无,则访问父类继承下来的;如果父类也没有定义,则编译报错。
  • 成员变量访问遵循就近原则,如果子类和父类中存在同名成员变量,则优先访问子类的。

(2)、子类使用从父类继承的成员方法

1. 子类和父类不存在同名成员方法

public class A {
    public void func1(){
        System.out.println("A中的func1方法");
    }
}
public class B extends A{
    public void func2(){
        System.out.println("B中的func2方法");
    }
    public void func3(){
        func2(); // 访问子类的方法
        func1(); // 访问父类的方法
        // func4(); // 父类和子类都没有,编译失败
    }
}

2. 子类和父类存在同名的成员方法

public class A {
    public void func1(){
        System.out.println("A中的func1()");
    }
    public void func2(){
        System.out.println("A中的func2()");
    }
}
public class B extends A{
    public void func1(int a) {
        System.out.println("B中的func1(int)");
    }
    public void func2(){
        System.out.println("B中的func2()");
    }
    public void func3(){
        func1(); // 没有传参,访问父类中的func1()
        func1(20); // 传递int参数,访问子类中的func1(int)
        //方法重载,注意不是重写!!!
        func2(); // 访问的是子类中的func2()
    }
}

注: 

  • 成员方法没有同名时,优先在子类中寻找,若子类中没有则再到父类中找,如果父类中也没有则报错。 
  • 成员方法存在同名时,则使用子类中的方法。
  • 若父类和子类同名方法的参数不同,根据调用方法传递的参数选择方法,注意这种情况是重载而非重写。

4、super

(1)、定义:

在上述的代码中,子类和父类中存在同名的成员变量和方法,调用同名的变量和方法时只能使用子类中的成员,但是想要明确调用父类中的成员就需要使用super关键字。

public class A {
    int a;
    int b;
}
public class B extends A{
    int a; // 与父类中成员变量同名且类型相同
    char b; // 与父类中成员变量同名但类型不同
    public void func1(){
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        this.a = 100;
        this.b = 101;
        // 注意使用this是引用当前类的对象,而想要访问父类的成员变量时,需要使用super关键字
        super.a = 200;
        super.b = 201;
    }
}

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

5、子类构造方法

(1)、定义

构造子类对象时,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

public class A {
    public A(){
        System.out.println("A()");
    }
}
public class B extends A{
    public B(){
        super();
        // 用户没有写构造方法时,编译器会自动添加无参构造方法,而且super()必须是子类构造方法中第一                        
           条语句。
        System.out.println("B()");
    }
}
public class Test {
    public static void main(String[] args) {
        B b = new B();
    }
} 

结果:
//    A()
//    B()

注:

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

6、执行顺序

(1)、没有继承关系时

class A {
    public int m;
    public int n;
    public A(String m, int n) {
        this.m = m;
        this.n = n;
        System.out.println("构造方法执行");
    } 

    {
        System.out.println("实例代码块执行");
    } 

    static {
        System.out.println("静态代码块执行");
    }
}

public class test {
    public static void main(String[] args) {
        A a1 = new A(1,2);
        System.out.println("------------------------");
        A a2 = new A(3,4);
    }
}

执行结果:
静态代码块
实例代码块
构造方法
------------------------
实例代码块
构造方法

注: 

  • 静态代码块先执行,并且只执行一次,在类加载阶段执行
  • 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

(2)、有继承关系时

class A {
    public int m;
    public int n;
    public A(int m, int n) {
        this.m = m;
        this.n = n;
        System.out.println("A的构造方法执行");
    } 
    
    {
        System.out.println("A的实例代码块执行");
    }
 
    static {
        System.out.println("A的静态代码块执行");
    }
}

class B extends A{
    public B(int m,int n) {
        super(m,n);
        System.out.println("B的构造方法执行");
    }
 
    {
        System.out.println("B的实例代码块执行");
    } 
    
    static {
        System.out.println("B的静态代码块执行");
    }
}

public class test {
    public static void main(String[] args) {
        B b1 = new B(1,2);
        System.out.println("--------------------");
        B b2 = new B(3,4);
    }
}

执行结果:
A的静态代码块执行
B的静态代码块执行
A的实例代码块执行
A的构造方法执行
B的实例代码块执行
B的构造方法执行
---------------------
A的实例代码块执行
A的构造方法执行
B的实例代码块执行
B的构造方法执行

注:

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

7、继承方式

(1)、单继承

  

(2)、多继承

 (3)、不同类继承同一个类

注:Java不支持多继承,并且一般不出现超过三层的继承关系。继承层次太多, 就需要考虑对代码进行重构了。如果想从语法上进行限制继承, 可以使用 final 关键字。

8、final

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

(1)、final修饰变量或字段

此处表示常量即不能修改

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

(2)、修饰方法

此方法不能被重写

(3)、修饰类

此类不能被继承

final public class Animal {

}
public class Bird extends Animal {

} 
// 编译出错Error:(3, 27) java: 无法从最终com.bit.Animal进行继

9、继承与组合

(1)、定义

        组合和继承类似,既是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。继承表示对象之间是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、定义

多态为不同的对象去完成同一个行为时,会产生出不同的状态。总的来说就是同一件事情,发生在不同对象身上,就会产生不同的结果。

2、使用

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

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写
  • 通过父类的引用调用重写的方法

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

例:

public class A {
    int m;
    public Animal(int m){
        this.m = m;
    }
    public void func(){
        System.out.println("呵呵呵!!!");
    }
}
public class B extends A{
    public B(int m){
        super(m);
    } 
    public void func(){
        System.out.println("哈哈哈!!");
    }
}
public class C extends A {
    public C(int m){
        super(m);
    }
    public void func(){
        System.out.println("嘿嘿嘿!");
    }
}
public class test {
    public static void func1(A a){
        a.func);
    }
    public static void main(String[] args) {
        B b = new B(1);
        C c = new C(2);
        func1(b);
        func1(c);
    }
} 

运行结果:
哈哈哈!!
嘿嘿嘿!

3、重写

(1)、定义

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

(2)、规则

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.

(3)、绑定 

静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

4、向上转型和向下转型

(1)、向上转型

定义:实际就是创建一个子类对象,将其当成父类对象来使用,从小范围向大范围的转换。

父类类型 对象名 = new 子类类型()

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

(2)、向下转型

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

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

5、多态优缺点

(1)、优点

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

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

例:

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[] 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 进行一定的修改,改动成本更高。

(2)、缺点

  • 代码的运行效率降低。
  • 属性没有多态性,当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
  • 构造方法没有多态性

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

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

例:

class B {
    public B() {
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    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 中的 func。此时 D 对象自身还没有构造,此时 num 处在未初始化的状态,值为 0。如果具备多态性,num的值应该是1。所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/18762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ID3算法

目录 ID3算法 例子 ID算法总结 ID3算法 ID3算法是在每个结点处选取能获得最高信息增益的分支属性进行分裂 在每个决策结点处划分分支、选取分支属性的目的是将整个决策树的样本纯度提升 衡量样本集合纯度的指标则是熵; 举例来说,如果有一个大小为10的…

被裁后,狂刷607页JUC源码分析笔记,立马拿蚂蚁offer

前言 可能大家最近,在公众号,或者各大自媒体平台,都能够刷到,因为疫情美国经济面临结构性衰退,美联储疯狂印钞导致世界性经济波动,导致国际环境不是很好,也间接影响到了中国,中国也…

跟艾文学编程《Python基础》(2)Python 容器

作者: 艾文,计算机硕士学位,企业内训讲师和金牌面试官,公司资深算法专家,现就职BAT一线大厂。 邮箱: 1121025745qq.com 博客:https://wenjie.blog.csdn.net/ 内容:跟艾文学编程《Pyt…

JAVA微服务场景下分布式日志收集排查问题实战

问题产生的根由?不同服务的日志存在哪里?我们怎么去排查线上问题? 问题场景:我们部署的java服务可能有几十个,不同的项目里面他是看不到别的服务的日志,只有服务的返回msg消息,相比传统的单体服…

计算机体系结构:1.1.系统加速比计算例题

文章目录题目内容题目分析题目求解题目内容 假设在某程序的执行过程中,浮点操作时间占整个执行时间的10%,现希望对浮点 操作加速 。 (1)设对浮点操作的加速比为Sf,请推导出程序总的加速比S和Sf之间的关系表达式&#…

IoU的计算实现详解(基于Python)

文章目录1. 交并比(IoU)2.原理3.代码实现1. 交并比(IoU) 具体来说,它是两边界框相交部分面积与相并部分面积之比,如下所示: 也就是两个框的交集和两个框的并集之比。 2.原理 这里详细解释一…

如何安装与配置Node.js

Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型, 让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等…

使用轻量应用服务器搭配宝塔面板搭建可道云kodbox私有云网盘的方法教程

你是否有过网盘下载速度只有十几KB,时不时出现网盘的文件被删除的问题,不如自己搭建一个云网盘吧,只需要一云服务器,即可搭建一个跟某度云一样的云盘。可以自由下载,不限制网速,随时都可上传下载。这篇文章…

nginx+redis+jvm三级缓存设计与落地实现

由于涉及到个人隐私,使用的是阿里云平台,所以下面的IP地址和密码我都做了修改。不是真实的地址。此模拟秒杀商品列表信息缓存案例实现的技术解决方案。 远程环境1版本操作系统ubuntu22.04openresty1.21.4.1jdkOracle JDK17IP192.168.1.1远程环境2版本redis7.0.5Springboot2.5…

149. SAP UI5 Table 控件数据进行 Excel 导出时如何进行格式控制

文章目录 字符串类型的显示控制数值类型(Number)的值显示控制日期和时间显示的格式控制布尔值的显示控制BigNumber 和百分比数值的显示总结本教程的前一步骤,我们成功的将 sap.m.Table 控件里显示的数据导出到了本地 Excel 文件中。 下图是使用 sap.m.Table 显示的表格页面:…

菜小白聊聊开源和开源协议

最近想入linux的深坑,于是开启了马哥sre课程的探险之旅。在了解到Linux是一款自由和开放源码的类UNIX操作系统的历史时,深深被开源精神所折服。也强烈感受到了开源精神的伟大。也正是因为有了开放源码的精神,才有了国产百花齐放的android系统…

【JUC源码专题】AQS 源码分析(JDK8)

文章目录同步队列同步队列结点 Node同步队列状态 state获取互斥锁acquire 方法tryAcquire 方法获取互斥锁addWaiter 方法enq() 入队acquireQueued()setHead 方法设置头节点shouldParkAfterFailedAcquire()parkAndCheckInterrupt()cancelAcquire 发生异常,取消线程获…

第七章第一节:顺序查找和折半查找

文章目录教程1. 查找的基本概念1.1 对查找表的常见操作1.2 查找算法的评价指标2. 顺序查找2.1 顺序查找的算法思想2.2. 顺序查找的实现2.3 查找效率分析2.4 顺序查找的优化(对有序表)2.5 用查找判定树分析ASL2.6 顺序查找的优化(被查概率不相…

在centos中注册gitlab runner

注册runner 有几种不同的方式,这里介绍的是在centos7中使用 rpm包来安装 按照gitlab runner的官网链接里面的介绍: gitlab runner 链接 下载 官网描述: ## Replace ${arch} with any of the supported architectures, e.g. amd64, arm, ar…

python 空间滤波

均值滤波器 空域变换包含灰度变换和空间滤波 灰度变换是通过点对点的映射进行图像增强,是一种点运算 空间滤波是基于邻域的一种运算,即图像像素的灰度值不仅和当前点有关,还和这个点周围邻域像素点的灰度值有关。所以空间滤波其实是一种加…

如何管理oralce口令文件和参数文件

口令文件 口令文件审核 Step 1: 使用root账号将 oracle dba的权限移除 [rootoracle-db-19c ~]# su - oracle [oracleoracle-db-19c ~]$ [oracleoracle-db-19c ~]$ id oracle uid1501(oracle) gid1501(oinstall) groups1501(oinstall),1502(dba),1503(oper),1504(backupdba)…

浅析linux 内核 高精度定时器(hrtimer)实现机制(一)

1 hrtimer 概述 在Linux内核中已经存在了一个管理定时器的通用框架。不过它也有很多不足,最大的问题是其精度不是很高。哪怕底层的定时事件设备精度再高,定时器层的分辨率只能达到Tick级别,按照内核配置选项的不同,在100Hz到1000…

灵界的科学丨一、灵界在哪里?

摘自李嗣涔教授《灵界的科学》 在国内物理学界近十位学者的见证下, 发现我们所处四度时空的物质世界之外, 似乎还有一个世界的存在, 当年我把这个世界称作信息场, 也就是俗称的灵界。 二十世纪末宇宙大尺度谜团的重大发现──…

设计模式学习记录

设计模式 UML图: ------> 依赖 ——>关联 -------▲ 实现 —–—▲ 继承 🔺———> 聚合 ▲———> 组合(关联性更强) 一、策略模式(行为型) 策略模式:是一种定义一系列算法的方法…

Java --- Spring6项目创建及注意事项

目录 一、Spring框架解决的问题 二、Spring介绍 三、Spring八大模块 四、Spring特点 五、第一个Spring6入门程序 六、spring的细节 6.1、配置文件的bean的id不能重复 6.2、spring底层是通过反射调用无参构造方法创建对象 6.3、spring会把创建好的对象存储在Map集合中 6.4…