一. 为什么要有继承:
比如,现在有一个需求: 开发教务管理系统。
- 其中学生类中的实体属性有:姓名,年龄,性别。学生具有的行为有:吃饭,睡觉,听课
- 教师类中的实体属性有:姓名,年龄,性别。教师具有的行为有:吃饭,睡觉,授课
如果,像以前,则需要定义两个类:student类和teacher。并且分别为这两个类写上对应的成员变量和成员方法。相同的代码需要分别写上两遍,代码重用率低。而如今,通过继承,可以抽取相同的代码定义成为Person类,在通过学生和老师分别继承Person类,得到对应的属性和方法即可。
二. 继承的格式:
Son extends Father。(其中Son和father分别表示不同的类。)
实现上述代码:
1. 创建Person类,并设定相关的成员变量和成员方法
public class Person {
//设置Person具有的属性
private String name;
private int age;
private char sex;
//设置Person具有的行为
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
public void work(){
System.out.println("工作");
}
public Person() {
}
public Person(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
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;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
2. Teacher类和Student类分别继承Person类
public class Student extends Person {
@Override
public void eat() {
System.out.println("学生吃饭");
}
@Override
public void sleep() {
System.out.println("学生睡觉");
}
@Override
public void work() {
System.out.println("学生听课");
}
}
public class Teacher extends Person {
@Override
public void eat() {
System.out.println("老师吃饭");
}
@Override
public void sleep() {
System.out.println("学生睡觉");
}
@Override
public void work() {
System.out.println("老师上课");
}
}
3. 测试类
public class Main {
public static void main(String[] args) {
Student student = new Student();
student.eat(); //学生吃饭
student.sleep(); //学生睡觉
student.work(); //学生听课
System.out.println();
Teacher teacher = new Teacher();
teacher.eat(); //老师吃饭
teacher.sleep(); //老师睡觉
teacher.work(); //老师上课
}
}
从上述代码可以清晰的发现,通过继承虽然创建的类要多一个,但是对于一些类具有共同的属性,可以提取出来,提高了代码的重用率。
四. 内存图解:
1. 虚拟机加载Main类,提取类型信息到方法区。
2. 通过保存在方法区的字节码,虚拟机开始执行main方法,main方法入栈。
3. 执行main方法的第一条命令,new Student(),在堆内存开辟了空间,又因为Student继承Person类,所以,虚拟机首先加载Person类到方法区,并在堆中为父类成员变量在子类空间中初始化。然后加载Student类到方法区,为Student类的成员变量分配空间并初始化默认值。将Student类的实例对象地址(0X666)赋值给引用变量student
4. 调用eat()方法,student引用在堆内存中会先检查本身区域是否有eat()方法,(如果有则调用,没有就去寻找父类的内存区域是否存在,俗称‘就近原则’)。eat()方法在自身区域存在,则入栈。执行里面的方法体,执行完毕后,出栈,再去寻找sleep()方法。sleep()找到,入栈,执行方法体,执行完毕后,出栈....
5. Teacher类也是如此。
6. 最后执行完毕,main()方法出栈,主线程消亡,虚拟机实例消亡,程序结束。
五. 继承的特点:
1. 继承中成员变量和成员方法的同名和不同名:
同名的成员变量和成员方法遵循 “就近原则”,即选择的优先级为:子类局部变量>子类成员变量/方法>父类成员变量/方法
不同名的则是调用谁则用谁。
public class Animal {
private String name = "父类名称" ;
public int age1 = 12;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void method(){
System.out.println("父类方法!")
}
}
public class Dog extends Animal{
private String name = "子类名称";
public int age2 = 13;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void method(){
System.out.println("子类方法!")
}
public void message(){
String name = "局部变量";
System.out.println(super.name); //输出父类名称
System.out.println(this.name); //输出子类名称
System.out.println(name); //输出局部变量
}
}
public class TestDog01 {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.getName()); //输出子类名称
System.out.println(dog.age1); //输出12
System.out.println(dog.age2); //输出13
dog.method; //输出子类方法
dog.message(); //调用子类方法
}
}
2.对于构造器:
子类的全部构造器第一排且必须是第一排默认为super调用,如果不写,则是隐式的super调用父类构造器进行初始化,然后再调用
自己的构造器进行初始化。
3. 类不能多继承,而接口可以实现多继承
原因: 比如出现了多继承,父类:A类和B类都具有mehtod()成员方法,当c继承了A和B类,调用method()方法时,会不知道出现调用那个类的method()方法。而,接口的成员变量均为 final static类型,且成员方法都是抽象类,都没有具体的方法体。而具体的方法只能有实现类去实现方法体在调用的时候始终只会调用实现类的方法(不存在歧义),因此不存在 多继承的第二个缺点;而又因为接口只有静态的常量,但是由于静态变量是在编译期决定调用关系的,即使存在一定的冲突也会在编译时提示出错;而引用静态变量一般直接使用类名或接口名,从而避免产生歧义,因此也不存在多继承的第一个缺点。
4. 类可以多重继承:即C extends B; B extends A;
5. 一个类可以有很多子类