Java继承与多态的正确打开方式
作者:Lucky_mzc
一.概述
面向对象程序设计的三大原则是封装性,继承性和多态性。继承性是子类自动共享父类的数据和方法的机制,它是由类的派生功能体现的。继承具有传递性,使得一个类可以继承另一个类的属性和方法,这样通过抽象出共同的属性和方法组件新的类,便于代码的重用。而多态是指不同类型的对象接收相同的消息时产生不同的行为,这里的消息主要是对类成员函数的调用,而不同的行为是指类成员函数的不同实现。当对象接收到发送给它的消息时,根据该对象所属的类,动态选用在该类中定义的实现算法。
二.继承
2.1 继承的概述
在现实中存在很多如图所示的关系:
出租车,卡车和公交车都是汽车的一种,分别拥有相似的特征。例如,引擎的数量,外观颜色等。它们拥有相似的行为,如刹车和加速的功能。但是每种不同的交通工具又有自己的特征,如公交车拥有和其他交通工具不同的特性和行为——最大载客数量和到指定站点要报站的特点,而卡车的主要功能是送货物,也就是载货和卸货,因此拥有最大载重量的特性。
在面向对象的程序设计中该怎样描述现实世界的这种状况呢?这就用到继承的概念。
所谓继承,就是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。已有的类一般称为父类(基类或超类),这个过程也称为类的派生。由基类产生的新类称为派生类或子类,派生类同样可以作为基类再派生新的子类,这样就形成了类间的层次结构。
基类和派生类类的关系如下:
- 基类是派生类的抽象(基类抽象了派生类的公共特征)。
- 派生类是对基类的扩展。
- 派生类和基类的关相当于“是一个(is a)”的关系,即派生类是基类的一个对象,而不是“有(has)”的关系,即类的对象包含一个或多个其他类的对象作为该类的属性。
2.2 继承机制
定义教师类,其中一类教师为Net教师,属性为姓名,所属部门,方法为授课,自我介绍。
public class NetTeacher { private String name; private String school; public NetTeacher(String myName,String mySchool){ name = myName; school = mySchool; } public void giveLession(){ System.out.println("启动 VS2021 "); System.out.println("知识点讲解"); System.out.println("总结提问"); } public void introduction(){ System.out.println("大家好!我是" + schoool + "的" + name + " 。"); } }
定义教师类,其中一类教师为Java教师,属性为姓名,所属部门,方法为授课,自我介绍。
public class JavaTeacher { private String name; private String school; public JavaTeacher(String myName,String mySchool){ name = myName; school = mySchool; } public void giveLession(){ System.out.println("启动 IDEA2021 "); System.out.println("知识点讲解"); System.out.println("总结提问"); } public void introduction(){ System.out.println("大家好!我是" + schoool + "的" + name + " 。"); } }
在程序处理中,发现两个类的定义非常相似,有很多相同点,如教师的属性姓名,所属部门类似,类的方法也基本相同。
针对这种情况,将Java教师类和Net教师类的共性抽取出来,形成父类Teacher类,使得Net教师和Java教师成为Teacher类的子类,则子类继承父类的基本属性和方法,就简化了子类的定义。上述代码可以修改如下:
public class Teacher { private String name; private String school; public Teacher(String myName,String mySchool){ name = myName; school = mySchool; } public void giveLession(){ System.out.println("知识点讲解"); System.out.println("总结提问"); } public void introduction(){ System.out.println("大家好!我是" + schoool + "的" + name + " 。"); } } 子类JavaTeacher: public class JavaTeacher extends Teacher { public JavaTeacher(String myName,String mySchool){ super(myName,mySchool); } public void giveLession(){ System.out.println("启动 IDEA2021 "); super.giveLession; } 子类NetTeacher: public class NetTeacher extends Teacher { public NetTeacher(String myName,String mySchool){ super(myName,mySchool); } public void giveLession(){ System.out.println("启动 VS2021 "); super.giveLession; }
子类自动继承父类的属性和方法,子类中不再存在重复代码,从而实现代码的重用。
通过关键字 extends,分别创建父类 Teacher 的子类 JavaTeacher 和 NetTecher 。子类继承父类所有的成员变量和成员方法,但不能继承父类的构造方法。在子类的构造方法中,可使用语句super (参数列表)调用父类的构造方法,如子类构造方法中的语句 super(myName,mySchool)。
extends 说明要构建一个新类,该类从已存在的类派生而来。派生的定义过程,实际是经历了以下几个过程:
- 子类继承父类中被声明为 public 和 protected 的成员变量和成员方法,但是不能继承被声明为 private 的成员变量和成员方法。
- 重写父类成员,包括数据成员和成员函数。如果子类声明了一个与父类成员函数相同的成员函数,子类中的新成员则屏蔽了父类同名成员,类似函数中的局部变量屏蔽了全局变量,称为同名覆盖(Overriding)。
- 定义新成员。新成员是派生类自己的新特性。派生类新成员的加入使得派生类在功能上有所发展。
- 必须在派类中重写构造方法,因为构造方法不能继承。
2.3 类中属性,方法的继承与覆盖
1.属性的继承与覆盖
子类可以继承父类的所有非私有属性。
子类也可以覆盖继承的成员变量,对于子类可以从父类继承的成员变量,只要子类中定义的成员变量和父类中的成员变量同名,子类就覆盖了继承的成员变量。
当子类执行它自己定义的方法时,所操作的就是它自己定义的数据成员,,从而覆盖父类继承来的数据成员。
2.方法的继承与覆盖
父类中非私有( private )方法可以被子类所继承。
在子类继承父类的成员方法时,应注意一下两项:
- 子类不能访问父类的 private 成员方法,但子类可以访问父类的 piblic ,protected 成员方法。
- 访问 protected 时,子类和同一包内的方法都能访问父类的 protected 成员方法,但其他方法不能访问。
方法的覆盖是指子类中定义一个方法,并且这个方法的名字,返回类型,参数列表与父类继承的方法完全相同。
2.4 super 关键字
子类不能继承父类的构造方法。
如果基类中没有默认构造方法或希望调用带参数的基类构造方法,要使用关键字 super 来显示调用基类构造方法。使用关键字 super 调用基类构造方法的语句,必须是子类构造方法的第一个可执行语句。调用基类构造方法时,传递的参数不能是关键字 this 或当前对象的非静态成员。
super 关键字主要应用于继承关系实现子类对父类方法的调用,包括对父类构造方法和一般方法的调用。具体使用方法如下:
(1)子类的构造方法如果要引用 super ,必须把 super 放在构造方法的第一个可执行语句。例如:
public CommonEmployee (String name,double bonus){ super (name); //通过 super () 的调用,给父类的数据成员赋初值 this.bonus = bonus; // this 指当前对象 System.out.println("子类构造方法的调用"); }
(2)在 Java 中,有时还会遇到子类中的成员变量或方法与父类中的成员变量或方法同名。同名子类中的成员变量或方法名优先级高,所以子类中的同名成员变量或方法覆盖了父类的成员变量或方法,但是我们如果想要使用父类中的这个成员变量或方法,就需要用到 super .
(3)可以用 super 直接传递参数。见下面代码:
public class Person { Person(){ prt("A Person."); } Person(String name){ prt("A person name is:" + name); } public static void prt(String s){ System.out.println(s); } } public class Chinese extends Person { Chinese(){ super(); //调用父类无形参构造方法 prt("A chinese"); //调用父类的方法prt } Chinese(String name){ super(name); //调用父类具有相同形参的构造方法 prt("his name is:" + name); } Chinese(String name,int age){ this(name); //调用当前具有相同形参的构造方法 prt("his age is:" + age); } public static void main(String[] args) { Chinese cn = new Chinese(); cn = new Chinese("Kevin"); cn = new Chinese("Jhone",21); } }
程序分析如下:
- main()中首先构建Chinese的第一个对象cn, 语句cn = new Chinese()调用子类无参的构造方法Chinese(),在构造方法中super()语句调用父类的无参构造方法Person(),在父类无参的构造方法中调用父类的方法prt(),程序输出结果A Person,接下来返回子类的构造方法的调用处,继续执行下面的语句pr("A chinese."),则调用父类的方法prt输出结果A chinese.结束第一条语句。
- 程序继续执行第二条语句cn = new Chinese("kevin");此时调用子类具有一 个参数参的构造方法Chinese(name),在构造方法中super(name)语句调用父类的有参构造方法prt("A person name is:"+ name),程序输出结果A person name is:kevin,接下来返回子类的构造方法的调用处,继续执行下面的语句prt("his name is:"+ name),则调用父类的方法prt输出结果his name is:kevin,结束第二条语句。
- 接下来程序继续执行第三条语句cn = new Chinese("Jhone,"22);程序执行时先调用子类具有两个参数参的构造方法Chinese(name, age), 在构造方法中this ( name )语句则调用的是当前具有相同参数的构造方法,即调用子类具有一个参数参的构造方法Chinese(name),接下来调用父类的有参构造方法Person(name),为父类的name进行初始化,再接下来在父类有参的构造方法中调用父类的方法prt("A person name is:"+ name),程序输出结果A person name is: Jhone,接下来返回子类的构造方法的调用处,继续执行下面的语句prt("his name is:"+ name),则调用父类的方法prt输出结果his name is: Jhone,调用结束后程序返回子类构造方法Chinese(name,age)执行语句prt("his age is:"+ age); 输出结果his age is:22,结束第二条语句。
三. 多态
多态是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!