ava super的定义
本章节目标:
掌握super都可以用在哪里?理解super在内存方面的存储位置。掌握怎么通过子类的构造方法调用父类的构造方法。super什么时候可以省略,什么时候不能省略
知识框架:
super和this可以对比着学习:
this
● this是一个引用,保存内存地址指向自己。
● this出现在实例方法中,谁调用这个实例方法,this就代表谁,this代表当前正在执行这个动作的对象。
● this不能出现在静态方法中。
● this大部分情况下可以省略,在方法中区分实例变量和局部变量的时候不能省略。
●“this(实际参数列表)”出现在构造方法第一行,通过当前的构造方法去调用本类当中其它的构造方法。
super
严格来说,super其实并不是一个引用,它只是一个关键字,super代表了当前对象中从父类继承过来的那部分特征。this指向一个独立的对象,super并不是指向某个“独立”的对象,假设张大明是父亲,张小明是儿子,有这样一句话:大家都说张小明的眼睛、鼻子和父亲的很像。那么也就是说儿子继承了父亲的眼睛和鼻子特征,那么眼睛和鼻子肯定最终还是长在儿子的身上。假设this指向张小明,那么super就代表张小明身上的眼睛和鼻子。换句话说super其实是this的一部分。如下图所示:张大明和张小明其实是两个独立的对象,两个对象内存方面没有联系,super只是代表张小明对象身上的眼睛和鼻子,因为这个是从父类中继承过来的,在内存方面使用了super关键字进行了标记,对于下图来说“this.眼睛”和“super.眼睛”都是访问的同一块内存空间。
图14-1:super内存图
● super和this都可以使用在实例方法当中。
● super不能使用在静态方法当中,因为super代表了当前对象上的父类型特征,静态方法中没有this,肯定也是不能使用super的。
● super也有这种用法:“super(实际参数列表);”,这种用法是通过当前的构造方法调用父类的构造方法。
接下来,我们来测试一下:
-
public class SuperTest01 extends Object{
-
//实例方法
-
public void doSome(){
-
System.out.println(this);
-
System.out.println(super);
-
}
-
}
编译报错了:
图14-2:this可以单独输出,super不能
通过以上的测试,可以看出this是可以单独使用的引用,但super无法输出,编译器提示super要使用必须是“super.xxx”,显然super并不指向独立的对象,并不是保存某个对象的内存地址。
再来看另外的一个测试:
-
public class SuperTest02 extends Object{
-
//静态方法
-
public static void doSome(){
-
System.out.println(this);
-
System.out.println(super.toString());
-
}
-
}
编译报错了:
图14-3:this和super都不能使用在静态方法中
通过以上的测试,可以看出this和super都是无法使用在静态方法当中的。
Java中super关键字的使用(在构造方法中)
super使用在构造方法中,语法格式为:super(实际参数列表),这行代码和“this(实际参数列表)”都是只允许出现在构造方法第一行(这一点记住就行了),所以这两行代码是无法共存的。“super(实际参数列表)”这种语法表示子类构造方法执行过程中调用父类的构造方法。我们来看一段代码:
-
public class People {
-
String idCard;
-
String name;
-
boolean sex;
-
public People(){
-
}
-
public People(String idCard,String name,boolean sex){
-
this.idCard = idCard;
-
this.name = name;
-
this.sex = sex;
-
}
-
}
-
public class Student extends People{
-
//学号是子类特有的
-
int sno;
-
public Student(){
-
}
-
public Student(String idCard,String name,boolean sex,int sno){
-
this.idCard = idCard;
-
this.name = name;
-
this.sex = sex;
-
this.sno = sno;
-
}
-
}
-
public class StudentTest {
-
public static void main(String[] args) {
-
Student s = new Student("12345x","jack",true,100);
-
System.out.println("身份证号" + s.idCard);
-
System.out.println("姓名" + s.name);
-
System.out.println("性别" + s.sex);
-
System.out.println("学号" + s.sno);
-
}
-
}
运行结果如下图所示:
图14-4:运行结果
我们把上面的代码片段拿过来放在一起看看:
父类的构造方法:
-
public People(String idCard,String name,boolean sex){
-
this.idCard = idCard;
-
this.name = name;
-
this.sex = sex;
-
}
子类的构造方法:
-
public Student(String idCard,String name,boolean sex,int sno){
-
this.idCard = idCard;
-
this.name = name;
-
this.sex = sex;
-
this.sno = sno;
你有没有察觉到子类的构造方法前三行代码和父类构造方法中的代码是一样的?接下来把子类的构造方法修改一下,然后再运行测试程序:
-
public Student(String idCard,String name,boolean sex,int sno){
-
super(idCard,name,sex);
-
this.sno = sno;
-
}
运行结果如下图所示:
图14-5:运行结果
通过以上代码的学习,“super(实际参数列表);”语法表示调用父类的构造方法,代码复用性增强了,另外一方面也是模拟现实世界当中的“要想有儿子,必须先有父亲”的道理。不过这里的“super(实际参数列表)”在调用父类构造方法的时候,从本质上来说并不是创建一个“独立的父类对象”,而是为了完成当前对象的父类型特征的初始化操作。(或者说通过子类的构造方法调用父类的构造方法,是为了让张小明身上长出具有他父亲特点的鼻子和眼睛,鼻子和眼睛初始化完毕之后,具有父亲的特点,但最终还是长在张小明的身上)。
接下来,再来看一段代码:
-
public class A {
-
public A(){
-
System.out.println("A类的无参数构造方法执行");
-
}
-
}
-
public class B extends A {
-
public B(){
-
System.out.println("B类的无参数构造方法执行");
-
}
-
}
-
public class C extends B {
-
public C(){
-
System.out.println("C类的无参数构造方法执行");
-
}
-
}
-
public class Test {
-
public static void main(String[] args) {
-
new C();
-
}
-
}
运行结果如下图所示:
图14-6:super()的测试
通过以上运行结果可以得出以下的等效代码:
-
public class A {
-
public A(){
-
//这里调用的是Object类中的无参数构造方法
-
//因为A类的父类是Object
-
super();
-
System.out.println("A类的无参数构造方法执行");
-
}
-
}
-
public class B extends A {
-
public B(){
-
super();
-
System.out.println("B类的无参数构造方法执行");
-
}
-
}
-
public class C extends B {
-
public C(){
-
super();
-
System.out.println("C类的无参数构造方法执行");
-
}
-
}
运行结果如下图所示:
图14-7:super()的测试
通过以上代码的测试我们得出,当一个构造方法第一行没有显示的调用“super(实际参数列表)”的话,系统默认调用父类的无参数构造方法“super()”。当然前提是“this(实际参数列表)”也没有显示的去调用(因为super()和this()都只能出现在构造方法第一行,所以不能并存)。我们可以通过以下程序再次测试一下:
-
public class A {
-
//有参数构造方法定义之后
-
//系统则不再提供无参数构造方法
-
public A(String s){
-
}
-
}
-
public class B extends A {
-
public B(){
-
}
-
}
编译报错了:
图14-8:编译报错信息
以上程序为什么会编译报错呢?原因是B类的构造方法第一行默认会调用“super()”,而super()会调用父类A的无参数构造方法,但由于父类A中提供了有参数构造方法,导致无参数构造方法不存在,从而编译报错了。所以在实际开发中还是建议程序员将无参数构造方法显示的定义出来,这样就可以避免对象的创建失败了。
另外,通过以上内容的学习,还可以得出这样的结论:在java语言当中无论是创建哪个java对象,老祖宗Object类中的无参数构造方法是必然执行的。
接下来我们再来看一下:一个java对象在创建过程中比较完整的内存图是如何变化的,请先看以下代码:
-
public class People {
-
String name;
-
boolean sex;
-
public People(String name,boolean sex){
-
this.name = name;
-
this.sex = sex;
-
}
-
}
-
public class Worker extends People {
-
//子类特有的工资属性
-
double salary;
-
public Worker(String name,boolean sex,double salary){
-
super(name,sex);
-
this.salary = salary;
-
}
-
}
-
public class WorkerTest {
-
public static void main(String[] args) {
-
Worker w = new Worker("jack",true,10000.0);
-
System.out.println("姓名:" + w.name);
-
System.out.println("性别:" + w.sex);
-
System.out.println("工资:" + w.salary);
-
}
-
}
运行结果如下图所示:
图14-9:运行结果
以上程序创建Worker对象时构造方法的执行顺序是:
● 先执行Object类的无参数构造方法;
● 再执行People类的构造方法;
● 最后执行Worker类的构造方法;
注意:虽然执行了三个构造方法,但是对象实际上只创建了一个Worker。
以上程序的内存结构图是这样变化的:
图14-10:程序执行内存图
通过以上内容的学习,super()的作用主要是:第一,调用父类的构造方法,使用这个构造方法来给当前子类对象初始化父类型特征;第二,代码复用。
Java中super关键字的使用(在实例方法中)
super和this都可以使用在实例方法中,并且都不能使用在静态方法当中,“this”大部分情况下都是可以省略的,只有在方法中区分局部变量和实例变量的时候不能省略。那“super”什么时候可以省略,什么时候不能省略呢?
-
//书
-
public class Book {
-
//书名
-
String name;
-
//构造方法
-
public Book(){
-
super();
-
}
-
public Book(String name){
-
super();
-
this.name = name;
-
}
-
}
-
//纸质书
-
public class PaperBook extends Book {
-
//构造方法
-
public PaperBook(){
-
super();
-
}
-
public PaperBook(String name){
-
super();
-
this.name = name;
-
}
-
//打印书名
-
public void printName(){
-
System.out.println("this.name->书名 : " + this.name);
-
System.out.println("super.name->书名 : " + super.name);
-
}
-
}
-
public class BookTest {
-
public static void main(String[] args) {
-
PaperBook book1 = new PaperBook("零基础学Java卷I");
-
book1.printName();
-
}
-
}
运行结果如下图所示:
图14-11:super和this
我们发现printName()方法中的super.name和this.name最终输出结果是一样的,这是为什么呢?请看以上程序执行的内存图:
图14-12:父类的构造方法执行结束之后的内存图
图14-13:子类的构造方法执行结束之后的内存图
通过以上内存结构图发现this.name和super.name实际上是同一块内存空间,所以它们的输出结果是完全一样的。接下来,我们再把以上的PaperBook类修改一下:
-
//纸质书
-
public class PaperBook extends Book {
-
String name; //在子类中也定义了一个name属性
-
//构造方法
-
public PaperBook(){
-
super();
-
}
-
public PaperBook(String name){
-
super();
-
this.name = name;//这里的this.name代表子类的name
-
}
-
//打印书名
-
public void printName(){
-
System.out.println("this.name->书名 : " + this.name);
-
System.out.println("super.name->书名 : " + super.name);
-
}
-
}
运行结果如下图所示:
图14-14:super和this的区别
为什么super.name是null呢,我们一起来看看以上程序的内存图:
图14-15:父类Book的构造方法执行之后的内存图
图14-16:子类PaperBook的构造方法执行结束之后的内存图
通过以上内存图可以清楚的看到,父类Book的构造方法在执行的时候给super.name赋值null,子类PaperBook的构造方法在执行的时候给this.name赋值“零基础学Java卷I”,由于在子类PaperBook中定义了重名的变量name导致在当前对象中有两个name,一个是从父类中继承过来的,一个是自己的,如果此时想访问父类中继承过来的name则必须使用super.name,当直接访问name或者this.name都表示访问当前对象自己的name。
通过以上的学习,大家知道super在什么情况下不能省略了吗?当父类中有该实例变量,子类中又重新定义了同名的实例变量,如果想在子类中访问父类的实例变量,super不能省略。实例方法是这样吗?我们可以来测试一下,请看代码:
-
public class Vip {
-
//Vip默认继承Object
-
//重写从Object类中继承过来的toString()方法
-
public String toString(){
-
return "我是金牌会员";
-
}
-
public void test(){
-
System.out.println(super.toString());
-
System.out.println(this.toString());
-
System.out.println(toString());
-
}
-
}
-
public class VipTest {
-
public static void main(String[] args) {
-
Vip vip = new Vip();
-
vip.test();
-
}
-
}
想要了解更多请扫描下方二维码