java的多态

文章目录

  • 多态的概念
  • 继承
    • 语法
    • 子类中访问父类的成员变量
    • 子类中访问父类的成员方法
    • 如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
    • 子类构造方法
  • final 关键字
  • 重写
  • 向上转移和向下转型
    • 向上转型
    • 向下转型
  • 多态

多态的概念

就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
同样是吃饭,可以定义狗吃肉,猫吃鱼

实现条件

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

继承

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

语法

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

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

注意:

  1. 父类中的成员变量或者成员方法继承到子类中了
  2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了
  3. Java中不支持多继承。(即一个子类有多个父类)

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

不存在同名成员变量

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

成员变量同名
在子类方法中 或者 通过子类对象访问成员时:

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

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。

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

成员方法名字不同

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

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

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(),基类的无法访问到
	}
}
  • 通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法视传递的参数选择合适的方法访问,如果没有则报错

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

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中的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. 在子类方法中,访问父类的成员变量和方法。

子类构造方法

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

class Base {
    public Base(){
        System.out.println("Base()");
    }
}
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();
    }
}

在这里插入图片描述
在子类构造方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分 。父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

注意:

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

super与this

【相同点】

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

【不同点】

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

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

  • 静态代码块先执行,并且只执行一次,在类加载阶段执行
  • 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
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 Test {
    public static void main(String[] args) {
        Student student1 = new Student("张三",19);
        System.out.println("===========================");
        Student student2 = new Student("李四",20);
    }
}

在这里插入图片描述

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

final 关键字

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

  1. 修饰变量或字段,表示常量(即不能修改)
final int a = 10;
a = 20; // 编译出错
  1. 修饰类:表示此类不能被继承
  2. 修饰方法:表示该方法不能被重写

重写

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

【方法重写的规则】

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

【重写和重载的区别】

区别点重写(override)重载(overload)
参数列表一定不能修改必须修改
返回类型一定不能修改【除非可以构成父子类关系】可以修改
访问限定符一定不能做更严格的限制(可以降低限制)可以修改
  • 静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
  • 动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

向上转移和向下转型

向上转型

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
语法格式:父类类型 对象名 = new 子类类型()

【使用场景】

  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 Test {
    // 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();
    }
}

在这里插入图片描述

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

向下转型

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

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

多态

【使用多态的好处】

  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 对象的数组.
	Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()};
	for (Shape shape : shapes) {
		shape.draw();
	}
}
  1. 可扩展能力更强

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

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

  1. 属性没有多态性

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

  1. 构造方法没有多态性
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();
	}
}

在这里插入图片描述

  • 构造 D 对象的同时, 会调用 B 的构造方法.
  • B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
  • 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1.
  • 所以在构造函数内,尽量避免使用实例方法,除了final和private方法。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WorkPlus即时通讯,让沟通零障碍!企业协作更高效

如今,随着信息技术的快速发展,企业对于高效沟通和即时协作的需求也日益增长。在这个数字化时代,WorkPlus作为一款领先的企业级移动办公平台,以其强大的即时通讯功能和卓越的用户体验,成功为企业打造了高效沟通的新时代…

Linux-帮助命令的使用和练习(type、man、help、info详解)

目录 5.3.1 type-判断是否为内部命令 5.3.2 man-查看详细文档 5.3.3 help-查看shell内部命令的帮助信息 5.3.4 --help-查看系统外部命令帮助信息 5.3.5 info-查看info格式的帮助指令 5.3.6 /usr/share/doc-存储软件包的文档信息 平时我们看到的命令大多数都可以查看帮助文…

基于PLC的污水处理控制系统的设计(论文+源码)

1.系统设计 污水由进水系统通过粗格栅和清污机进行初步排除大块杂质物体以及漂浮物等,到达除砂池中。在除砂池系统中细格栅进一步净化污水中的细小颗粒物体,将污水中的细小沙粒滤除后进入氧化沟反应池。在该氧化沟系统中进行生化处理,分解污…

class006 二分搜索【算法】

class006 二分搜索【算法】 算法讲解006【入门】二分搜索 code1 有序数组中是否存在一个数字 // 有序数组中是否存在一个数字 package class006;import java.util.Arrays;// 有序数组中是否存在一个数字 public class Code01_FindNumber {// 为了验证public static void mai…

Java8流式编程详解

简介 java8提供的流式编程使得我们对于集合的处理不再是临时集合加上各种还能for循环,取而代之的是更加简洁高效的流水线操作,所以笔者就以这篇文章总结一下流式编程中常见的操作。 前置铺垫 后文示例操作中,我们都会基于这个菜肴类的集合…

使用C语言操作kafka ---- librdkafka

1 安装librdkafka git clone https://github.com/edenhill/librdkafka.git cd librdkafka git checkout v1.7.0 ./configure make sudo make install sudo ldconfig 在librdkafka的examples目录下会有示例程序。比如consumer的启动需要下列参数 ./consumer <broker> &…

前言-计算机概述

1 计算机作用&#xff1f; 计算机已经成为人们日常生活中不可缺少的产物&#xff0c;具体作用如下 1&#xff09;信息处理 电脑可以处理、存储和检索大量的信息&#xff0c;例如文档、音频、视频等等&#xff0c;这使得信息传播和共享变得更加容易和高效。 2&#xff09;通讯 …

人口减少引发全面社会变革:从幼儿园到经济结构都在发生深刻变化

湖南省教育厅发布文件&#xff0c;首次正式提出“有序组织幼儿园设并转撤”&#xff0c;在我国整体人口下降的大背景下&#xff0c;引发了社会对于幼儿园存废问题的深刻思考。随着出生率的下降&#xff0c;人们虽然对于幼儿园规模的减少有所预期&#xff0c;但供需形势的逆转却…

前端知识(十五)——es6 相关面试总结

1、es6 是什么 新一代的js 语言标准&#xff0c;对其核心做了升级优化&#xff0c;更加适合大型应用开发。 2、箭头函数优缺点 优点&#xff1a; 1.代码优化 2.this 指向不会变动&#xff0c;永远指向其父元素 缺点&#xff1a; 1.没有arguments 参数 2.不能通过 appl…

御剑工具学习

御剑 1.1 工具的下载路径1.2 工具的安装流程1.3 工具的详细使用 1.1 工具的下载路径 百度网盘 链接&#xff1a;https://pan.baidu.com/s/1Bn7GtWb7AStcjzVahFOjSQ 提取码&#xff1a;zkaq 1.2 工具的安装流程 御剑不用安装&#xff0c;直接下载下来解压&#xff0c;双击“御…

visual studio code 好用的插件

vscode-icons Better comments 该插件对不同类型的注释会附加了不同的颜色&#xff0c;更加方便区分&#xff0c;帮助我们在代码中创建更人性化的注释。 Error Lens Error Lens插件是一款可以检测你编写的代码的语法错误&#xff0c;并且会显示出对语法错误的诊断信息…

background的多种用法,包括渐变+背景图

top left斜杠后的数值是图片的大小&#xff0c;可以用cover或contain 渐变色与图片搭配 多背景图时&#xff0c;层叠关系 背景滤镜

windows安装protoc、protoc-gen-go、protoc-gen-go-grpc

文章目录 一、 protoc二、protoc-gen-go三、protoc-gen-go-grpc 一、 protoc 1&#xff0c;下载&#xff1a;https://github.com/google/protobuf/releases 下载对应的protoc&#xff0c;注意选择windows 2&#xff0c;下好之后解压就行&#xff0c;然后把bin目录加入到环境…

排序算法之六:快速排序(递归)

快速排序的基本思想 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法 其基本思想为&#xff1a; 任取待排序元素序列中的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右序列中所…

LeetCode 77.组合

题目&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 方法&#xff1a;灵神-组合型回溯 剪枝 class Solution {private int k;private final List<Integer> path new ArrayList<>();…

面向 AI 开发者的新型编程语言Mojo

文章目录 面向 AI 开发者的新型编程语言Mojo一、什么是mojoLLVMMLIR为什么选择Mojo&#x1f525; 二、Mojo安装系统要求安装步骤Mojo Visual Studio Code (VS Code) 扩展 安装 三、官方hello world交互式运行构建和运行Mojo源文件构建可执行的二进制 四、Mojo语言基础Mojo 语言…

大话数据结构-查找-多路查找树

注&#xff1a;本文同步发布于稀土掘金。 7 多路查找树 多路查找树&#xff08;multi-way search tree&#xff09;&#xff0c;其每个结点的孩子可以多于两个&#xff0c;且每一个结点处可以存储多个元素。由于它是查找树&#xff0c;所有元素之间存在某种特定的排序关系。 …

Java面向对象实践小结(含面试题)

继承 作用 提高了代码的复用性。让类与类之间产生了关系。有了这个关系&#xff0c;才有了多态的特性。 代码示范 父类代码 public class Parent {public void say() {System.out.println("父类的say方法");} }子类代码&#xff0c;继承父类&#xff0c;也就拥有…

java表达式、java中jexl3的使用,java中jexl3如何自定义函数方法,jexl3自定义函数怎么传集合数组列表

引入jexl3 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-jexl3</artifactId><version>3.2.1</version> </dependency> 基本用法 //引入对应包 import org.apache.commons.jexl3.*;public class …

操作系统学习笔记---内存管理

目录 概念 功能 内存空间的分配和回收 地址转换 逻辑地址&#xff08;相对地址&#xff09; 物理地址&#xff08;绝对地址&#xff09; 内存空间的扩充 内存共享 存储保护 方式 源程序变为可执行程序步骤 链接方式 装入方式 覆盖 交换 连续分配管理方式 单一连…