写一篇博客记录一下学习JAVA 继承 中遇到的问题
目录
继承
继承的目标:实现代码的复用,减少ctrl c + ctrl v的次数。
继承的关键字:extends。
即
public Teacher
{
int age;
String name;
public void eat()
{
System.out.println("中午吃的面条。");
}
}
public class Student extends Teacher
{
//Student 从Teacher继承了age,name,eat()
//接下来在main方法中输出他们试试
public static void main(String[] args)
{
Student Tom = new Student();
Tom.age = 18;
Tom.name = "Tom";
System.out.println(Tom.age);
System.out.println(Tom.name);
Tom.eat();
}
}
这里Student就继承了Teacher的属性和方法。
所以Student类就成为子类/派生类
Teacher类就称为父类/基类/超类(superclass)
注意 : 父类的私有 变量/方法 也继承(这个众说纷谈,我查了好久,有人说java官方文档里写的不能继承,有人说java数据结构书中写的能继承...我感觉可以继承...)
为什么子类不能继承父类的构造器?
如果继承,那么父类的构造器在子类有两种存在形式:构造方法、普通方法。
1.如果是构造方法,它与子类的名称不相同,违背了构造器的规定。
2.如果是普通成员方法,它没有返回值也不写void,违背了普通成员方法的规定。
故子类不继承父类的构造器
(子类不能继承父类的构造器,但是能使用父类的构造器,用它来对那些从父类继承过来的变量进行赋值,这在下面会写到。)
多继承与多重继承
C++里的继承可以一个子类继承多个父类,如:class A: public B,public C{ .... }
但java里,一个子类只能有一个父类,但是它可以有爷爷,这里的爷爷普遍被称为间接父类。而一个父类可以有很多个子类,B类可以继承A类,C类也可以继承A类,那么B类和C类就都是A类的儿子
如:
public class A {}
public class B extends A {}
public clasd C extends B {}
C继承了A和B中的成员。
(java.long.Object类是所有类的父类)
继承时父类/子类 构造方法执行顺序
public class Teacher
{
public Teacher()
{
System.out.println("父类构造方法执行!");
}
}
public class Student extends Teacher
{
public Student()
{
System.out.println("子类构造方法执行!");
}
}
public class text
{
public static void main()
{
Student Tom = new Student();
}
}
结果输出:
父类构造方法执行!
子类构造方法执行!
由此可以看出,有父才有子,父类的构造方法先执行,子类的构造方法后执行
(这是为什么呢?为什么只是创造一个子类对象,就执行父类方法了呢?下文就会讲到)
方法重写
有时从父类继承过来的方法跟子类的实际要求不符合,例如:
public class Animal
{
public int age;
public void eat()
{
System.out.println("进食");
}
}
这个动物类的eat方法是进食,如果想实现一个猫类,一个狗类,要求把他们吃的食物具体化,就要用到方法重写(注意,是方法重写,而不是方法重载)。
(重写和重载的区别会在最下面拓展)
public class Animal
{
public String age;
public void eat()
{
System.out.println("进食");
}
}
public class Cat extends Animal
{
//这里已经把Animal的age继承了过来
public void eat()
{
System.out.println("猫吃鱼");
}
}
一般在被重写的方法上面加上@override,这样的话,可以确保下一行一定是覆盖重写,如果你把方法名或者形参列表写错了,编译器会报错。
@override
public void eat()
{
System.out.println("猫吃鱼");
}
把父类的方法重写后,父类的方法就会被隐藏,如果想重新调用父类被重写的方法就要用到super关键字,下面会讲。
方法重写的注意事项:
1.重写子类和父类方法的名称和参数列表一定一定是一样的
2.权限修饰符:
子类重写方法的权限修饰符一定大于等于父类被重写的权限修饰符
3.返回值:
子类重写方法的返回值范围一定小于等于父类被重写方法的返回值
如果父类被重写方法的返回值是void,那么子类也必定是void
如果父类被重写方法的返回值是A类,那么子类重写的返回值必须是A类或A类的子类
4.异常处理:
子类重写方法抛出的异常必须
5.父类的private方法不能被重写
6.如果上面的Animal和Cat类其中一个的eat()方法被static修饰,编译器会报错,如果两个都被static修饰,那么这就不叫覆盖重写,因为static关键字修饰的内容是属于类的,而不是某个对象,它随着类的加载而加载,所以不能被覆盖
this和super
this
this代表本类对象的引用,如:
public class Teacher
{
String name;
public Teacher()
{
;
}
public Teacher(String name)
{
// name = name;
this.name = name;
}
}
上面这行代码中,有参构造传入一个名为name的局部变量,而这个局部变量与Teacher的成员变量名字相同,如果直接使用 name = name ;
额...这纯属傻瓜行为家人们。
所以要加上this,表明:这个name是成员变量,不是局部变量!
当然,this不仅仅可以调用成员变量,也可以调用成员方法,还可以调用构造器。
this.eat()就是调用这个类的eat方法
this()就是Teacher的无参构造方法,this("Tom")就是Teacher的有参构造方法
super
this是对本类内容的引用,(回想一下父类还有个名字叫:超类,superclass。自然而然就知道:super是对父类内容的引用
public class Teacher
{
public int age;
public String name;
public void eat()
{
System.out.println("中午吃的面条。");
}
}
public class Student extends Teacher
{
@Override
public void eat()
{
System.out.println("晚上吃的米饭。");
}
public static void main(String[] args)
{
Student Tom = new Student();
Tom.eat();
}
}
运行后毫无疑问是“晚上吃的米饭。” 对吧,而现在我想让Tom执行老师的eat()方法,就要用super关键字调用
只需要在eat()方法中调用super.eat()
this/super使用事项:
用this/super完成构造函数时需要注意:
this(形参列表)
super(形参列表)
二者使用时都要放在方法的第一行,所以二者只能选其一,因为一个放在上面,必定有一个要放在第二行,违反规定
即:一个构造器的首行只能是this(参数列表)/super(参数列表),不可能有其他的
编译器在子类的构造方法中默认赠送一个无参的super(),
(这也就是为什么初始化一个子类对象却先执行父类的构造方法的原因)
如果你用编译器的快捷键来生成子类的构造方法,就可以更直观的看出这种现象
点击Alt + Insert(在F12右边的快捷键),选择Constructor,选中成员方法,点击确认后会自动生成一个带有super()的子类构造方法
此时如果父类中只有 有参构造方法(此时编译器不会赠送无参构造),就会出错。看图:
报错为:
原因:父类Demo01中有一个有参构造,编译器不再提供默认构造,子类继承时没有写构造方法,编译器补全一个默认构造,这个默认构造方法(隐式)调用父类的无参默认构造,但是发现没有,所以报错。
解决办法:
1.在父类中添加无参构造方法
2.不用编译器赠送的 默认构造&&无参super(),
在子类中显式调用super(参数列表)
调用父类的构造器,构造子类从父类继承过来的成员变量
前面说过,子类不继承父类的构造器,但是可以用父类的构造器来初始化从父类继承的属性
public class Teacher
{
public int age;
public String name;
public Teacher(int age,String name)
{
this.age = age;
this.name = name;
}
public void print()
{
System.out.println("老师的年龄:" + this.age);
System.out.println("老师的姓名:" + this.name);
}
}
public class Student extends Teacher
{
String major; //学的专业
public Student(int age,String name,String major)
{
super(age,name); //调用父类的构造器,构造子类从父类继承过来的成员变量
this.major = major;
}
@Override
public void print()
{
System.out.println("学生的年龄:" + this.age);
System.out.println("学生的姓名:" + this.name);
System.out.println("学生的专业:" + this.major);
}
public static void main(String[] args)
{
Student Tom = new Student(18,"Tom","计算机");
Tom.print();
}
}
执行结果:
方法重载与方法重写的区别:
方法重载的要求:方法名相同,参数列表不同,返回值随意
方法重写的要求:方法名相同,参数列表相同,返回值、权限修饰符、异常抛出...也有要求
方法重载的目的:
实现一个方法的多种用处,例如Arrays.sort()方法,能比较int、float、double等等类型,又比如println方法,既能打印基本数据类型,又能打印String等类型
方法重写的目的:
对于从父类继承的方法,他的功能太单一,不符合我们的期望,如:父亲的工作是医生,儿子的工作是程序员,这是用重写就能实现这种需求