一、介绍
在继承中,我们子类继承了父类,并没有继承他的构造方法,所以我们要了解的是子类与父类的构造方法的处理。
二、知识点介绍
1、继承关系内存解释
2、父类对象优于子类对象产生
3、super关键字
4、this关键字
5、super与this比较
6、对多态向上向下转型的内存解释
三、上课视频对应说明文档
1、继承关系内存解释
实际上在面向对象第一天的讲解当中,我们对内存方面做了一些隐瞒。因为除了Object类,所有的类都是有父类的。但是我们在考虑内存图时忽略了这点,现在,我们来简单描述加入了子父类关系后的对象内存图。
以Person类为例:
//定义父类
public class Person {
private String name;
private int age;
public Person(){}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
//get/set方法
}
//定义子类
public class Chinese extends Person{
private Stirng address;
public Chinese(){}
public Chinese(String name,int age,String address) {
super(name,age);
this.address = address;
}
//对address的get/set
}
//定义测试类,使用子类创建对象
public class Test{
Chinese c = new Chinese(“AngelaBaby”,18,”北京海淀区上地7街晋福公寓”);
}
对象内存图
代码示例:
/*
* 自定义类型Person 类
*
* name age
*
* 吃 睡
*/
public abstract class Person {
private String name;
int age;
//定义无参构造方法
public Person(){
//方法逻辑
System.out.println("我是Person的无参构造");
}
//定义带参构造一般都是为了给成员变量赋值
public Person(String name ,int age){
System.out.println("我是Person带参构造给成员变量赋值");
this.name = name;
this.age = age;
}
private void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/*
* 自定义Studnet继承Person类
*/
public class Student extends Person{
private String number;
/*
* 无参构造
*/
public Student() {
System.out.println("Studnet的无参构造");
}
//带参构造
public Student(String number,String name,int age) {
System.out.println("Studnet的带参构造");
this.number = number;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
/*
* 测试有继承关系的构造方法
*/
public class Test {
public static void main(String[] args) {
//Student s = new Student();
Student s2 = new Student("2016");
}
}
2、父类对象优于子类对象产生
在每次创建子类对象时,我们均会先创建父类对象,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类对象,便可以包含其父类对象的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。
反之,如果没有先创建父类对象就使用了子类对象,则子类无法使用父类的成员。抽象类包含构造方法的原因就在于其仅仅是为了给成员变量赋值,供子类使用。
这里我们需要注意的是,内存当中实际是存在抽象类的对象空间的,我们无法直接创建抽象类对象,但是子类可以,在子类的内存空间中包括了这个抽象父类对象。
3、super关键字
3.1、super关键字概念
super代表本类对象中包含的父类对象空间的引用。
当有了继承关系后,创建一个子类对象时,会先在子类中创建其父类对象,则子类对象包含了父类的所有方法与属性,而其非私有的方法一般都可以访问 (在完成访问权限的学习后,会有进一步认识) 。
3.2、super访问普通成员
在子类的任意位置,均可以使用super.属性名或者super.方法名()的方式访问父类空间的非私有成员。
代码示例:
/*
* 自定义类型Person 类
*
* name age
*
* 吃 睡
*/
public abstract class Person {
private String name;
int age;
//定义无参构造方法
public Person(){
//方法逻辑
System.out.println("我是Person的无参构造");
}
//定义带参构造一般都是为了给成员变量赋值
public Person(String name ,int age){
System.out.println("我是Person带参构造给成员变量赋值");
this.name = name;
this.age = age;
}
private void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/*
* 自定义Studnet继承Person类
*
* super访问父类构造方法
* 在子类的所有构造方法的第一行 都默认调用了父类的无参构造 super()
* 我们通过super(参数)调用父类的带参构造 给父类的成员变量赋值
* super访问普通成员
* 在子类的任意位置,均可以使用super.属性名或者super.方法名()的方式访问父类空间的非私有成员。
*
*/
public class Student extends Person{
private String number;
/*
* 无参构造
*/
public Student() {
super();
System.out.println("Studnet的无参构造");
}
//带参构造
public Student(String number,String name,int age) {
super(name,age);
System.out.println("Studnet的带参构造");
this.number = number;
}
public void method(){
//super.属性名可以访问父类的非私有成员变量
//System.out.println(super.name);
System.out.println(super.age);
//super.方法名()可以访问父类的非私有成员方法
// super.eat();
super.sleep();
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
/*
* 测试有继承关系的构造方法
*/
public class Test {
public static void main(String[] args) {
// Student s = new Student();
Student s2 = new Student("2016","柳岩",38);
System.out.println(s2.getNumber());
System.out.println(s2.getName());
System.out.println(s2.getAge());
}
}
3.3、super调用父类构造方法
在子类的每个构造方法中,第一行具有默认调用父类空参构造代码,即super().所以在每次创建子类对象时,会先创建父类的构造。
使用super(参数)可以访问父类任意参数的构造,当手动调用父类任意的构造方法后,Java将不再提供默认调用父类空参的构造方法。
/*
* 自定义类型Person 类
*
* name age
*
* 吃 睡
*/
public abstract class Person {
private String name;
Private int age;
//定义无参构造方法
public Person(){
//方法逻辑
System.out.println("我是Person的无参构造");
}
//定义带参构造一般都是为了给成员变量赋值
public Person(String name ,int age){
System.out.println("我是Person带参构造给成员变量赋值");
this.name = name;
this.age = age;
}
Public void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
/*
* 自定义Studnet继承Person类
*
* super访问父类构造方法
* 在子类的所有构造方法的第一行 都默认调用了父类的无参构造 super()
* 我们通过super(参数)调用父类的带参构造 给父类的成员变量赋值
* super访问普通成员
* 在子类的任意位置,均可以使用super.属性名或者super.方法名()的方式访问父类空间的非私有成员。
*
*/
public class Student extends Person{
private String number;
/*
* 无参构造
*/
public Student() {
super();
System.out.println("Studnet的无参构造");
}
//带参构造
public Student(String number,String name,int age) {
super(name,age);
System.out.println("Studnet的带参构造");
this.number = number;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
/*
* 测试有继承关系的构造方法
*/
public class Test {
public static void main(String[] args) {
// Student s = new Student();
Student s2 = new Student("2016","柳岩",38);
System.out.println(s2.getNumber());
System.out.println(s2.getName());
System.out.println(s2.getAge());
}
}
4、this关键字
4.1、this关键字概念回顾
this代表本类一个对象的引用,当创建了一个子类对象时,子类自己的空间可以使用this访问到。
4.2、this调用普通成员
在子类的任意位置,均可以使用this.属性名或者this.方法名()的方式访问子类自身空间的成员。
4.3、this调用本类其他构造
使用this(参数)可以访问子类任意其他参数的构造方法,当手动调用子类任意的构造方法后,Java将不再提供默认调用父类空参的构造方法。
this调用构造方法与super调用构造方法不能同时出现。
无论以哪种方式完成构造方法的定义,均会先创建父类对象,再创建子类对象。
package cn.javahelp3;
/*
* 自定义类型Person 类
*
* name age
*
* 吃 睡
*/
public abstract class Person {
private String name;
int age;
//定义无参构造方法
public Person(){
//方法逻辑
System.out.println("我是Person的无参构造");
}
//定义带参构造一般都是为了给成员变量赋值
public Person(String name ,int age){
System.out.println("我是Person带参构造给成员变量赋值");
this.name = name;
this.age = age;
}
private void eat(){
System.out.println("吃");
}
public void sleep(){
System.out.println("睡");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package cn.javahelp3;
/*
* 自定义教师
*
* this
*
* this调用本类其他构造
* this(参数)可以调用本来当中其他构造方法
*
* this访问本来的普通成员
* 在子类的任意位置,均可以使用this.属性名或者this.方法名()的方式访问子类自身空间的成员。
*
*/
public class Teacher extends Person{
//id
private String id;
public Teacher() {
//this(参数)可以调用本来当中其他构造方法
this("90213");
}
public Teacher(String name, int age,String id) {
super(name, age);
this.id = id;
}
public Teacher(String id){
super();
this.id = id;
}
public void method(){
//在子类的任意位置,均可以使用this.属性名或者this.方法名()的方式访问子类自身空间的成员。
System.err.println(this.id);
this.teach();
}
public void teach(){
System.out.println("教学生的方法");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
5、this与super的使用对比及注意事项
(1)访问子类区域的成员使用this,访问父类区域的成员使用super。
this:
(2)访问本类对象成员变量:this.变量名
(3)调用本类普通方法:this.方法名(参数)
(4)本类构造方法调用本类其他构造:本类构造方法第一行this(参数)
super:
(5)访问本类对象当中的父类对象成员变量:super.变量名
(6)调用本类对象当中的父类普通方法:super.方法名()
(7)本类构造方法调用父类构造:本类构造方法第一行super(参数)
变量访问的就近原则:
(8)当多个位置出现相同名称的变量时,访问时会根据就近原则依次访问其先后顺序为:
局部位置>本类成员位置>父类成员位置 >父类的父类成员位置 …
package cn.javahelp4;
/*
* 变量的就近访问原则
*/
public class Fu {
String name = "父类名字";
}
package cn.javahelp4;
public class Zi extends Fu {
String name = "子类名字";
public void method(){
String name = "局部名字";
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
package cn.javahelp4;
/*
* 当多个位置出现相同名称的变量时,访问时会根据就近原则依次访问。其先后顺序为:
局部位置 > 本类成员位置 > 父类成员位置 > 父类的父类成员位置 …
*/
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}
注意:
this与super在调用构造方法时,均必须在第一行,只能调用其中的一个。
父类多个构造,子类调用父类某个参数的构造时,必须保证父类有这个构造,否则报错。
package cn.javahelp5;
/*
* 自定义Person类
*
* name age
*/
public class Person {
private String name;
private int age;
public Person(){
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person(int age){
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package cn.javahelp5;
/*
* 学生类
*
*this与super在调用构造方法时,均必须在第一行,只能调用其中的一个。
* 父类多个构造,子类调用父类某个参数的构造时,必须保证父类有这个构造,否则报错。
*/
public class Student extends Person{
public Student() {
super();
}
public Student(String name, int age) {
super(name, age);
}
public Student(int age){
super(age);
}
}
6、对多态向上向下转型的内存解释
向上转型:
如图所示,当出现多态时,引用为Person类型,对象为Chinese对象,此时,由于Chinese中包含了父类所有成员,所以可以访问父类非私有的一切。对外表现的就”像个父类对象一样”。仅仅在调用方法时,会调用子类重写后的方法。
向下转型:
当出现多态后,父类Person引用指向子类对象,当强转为子类引用时,由于堆内存当中存储的仍为子类对象,包含子类的一切成员。所以可以转型成功。
但是,如果没有出现多态,仅仅创建父类对象(如果父类不是抽象类的话),则为 父类Person的引用指向Person的对象,没有子类的对象。此时如果强转为子类对象,则不包含子类的一些属性与功能,所以强转失败。
思考:
当子父类中有相同名称的成员变量时,强转前与强转后访问的是相同的属性值么?
代码示例:
/*
* 变量的就近访问原则
*/
public class Fu {
String name = "父类名字";
}
package cn.javahelp4;
public class Zi extends Fu {
String name = "子类名字";
public void method(){
String name = "局部名字";
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
public class Test1 {
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.name);
Zi zi = (Zi)fu;
System.out.println(zi.name);
}
}