概述
类的继承是指在一个现有类的基础上构建一个新类。
- 新类称作子类,现有类称作父类。
- 子类自动拥有父类所有可继承的属性和方法。
如何实现类的继承?
声明一个类继承另一个类,需要使用关键字extends。
比如我先创建一个Person类,代码如下:
public class Person {
private char gender;
public Person(){
System.out.println("Person类无参构造方法执行");
}
public char getGender(){
return gender;
}
public void setGender(char gender){
this.gender = gender;
}
public void sleep(){
System.out.println("要睡觉...");
}
}
创建一个Student类,继承Person类,代码如下:
public class Student extends Person{
private String name;
int age;
public Student(){}
public Student(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public void intro(){
System.out.println("我的性别是"+getGender());
}
}
创建Student对象,程序入口代码如下:
public static void main(String[] args) {
Student stu1 = new Student("大西瓜");
stu1.age = 10;
stu1.gender = '男';//报错,private修饰的成员不可继承
stu1.setGender('男');
stu1.intro();
stu1.sleep();
}
运行结果如下:
Person类无参构造方法执行
我的性别是男
要睡觉...
可以看出,
- Student类通过extends关键字继承Person类后,虽然没有没有定义gender属性和sleep()方法,却能访问两个成员。
- private修饰的变量gender无法访问。
这就说明,子类继承父类时,自动拥有父类所有可继承的成员。而被private修饰的变量gender则属于不可继承的成员。
- 创建的是Student对象,为什么Person类的无参构造会被调用?
这就要从内存创建开始说起,创建Student对象,先开辟堆内存空间。不同以往,开辟的不是Student对象的空间,而是先开辟它的父类对象的空间:
再创建子类对象Student,将父类的引用赋值给新增属性super
这就是为什么Person类构造方法被调用的原因了,
等于说是开辟了两块空间,一父一子两个对象,其中子类对象中的super属性指向父类,
我们在访问子类中不存在、父类中存在的成员时,就相当于是通过这个属性找到父类成员并访问的。
继承与权限修饰符的关系
下面通过这个表,为小伙伴们简单讲解继承的范围。
修饰符/访问范围 | 同一个类中 | 同一个包中 | 子类中 | 全局范围 |
---|---|---|---|---|
public | v | v | v | v |
protected | v | v | v | |
default | v | v | ||
private | v |
由上图,只有private修饰的类或类的成员不能被继承。
可能细心的小伙伴会发现,Person类中的age变量没有被修饰,修饰符应该属于default,结果却可以被子类访问
这是因为,
age的修饰符属于(default)默认修饰符,同一个包下可以访问,而Person类和student类就在同一个包下。
继承与类的关系
类的继承也有限制,请注意:
- Java中类仅支持单继承、不支持多继承,即一个类只有一个直接父类。
class A{}
class B{}
class C extends A,B{}//不可同时继承多个类
- 多个类可以继承一个类
class A{}
class B extends A{}
class c extends A{}//B类和C类都可以继承A类
- 多层继承是允许的,即一个类的父类可以再去继承别的父类。
class A{}
class B extends A{}//B类继承A类,B类是A类的子类
class C extends B{}//C类继承B类,B类继承A类,C类也是A类的子类、C类可以访问B类和A类所有可继承的成员
- 子类可以重写父类的方法。
子类中的重写方法和父类被重写的方法应具有相同的方法名、参数列表、返回值类型。
我们在Student类中添加父类的sleep()方法
//与父类相同的方法名、参数列表、返回值类型。
public void sleep(){
System.out.println(name+"要睡觉...");
}
程序入口,代码如下:
public static void main(String[] args) {
Student stu1 = new Student("大西瓜");
stu1.sleep();
}
结果如下:
Person类无参构造方法执行
大西瓜要睡觉...
此时发现,JVM调用的的是子类的sleep()方法而不是父类的,这就是方法的重写。
注意一点:子类重写父类方法时,不能使用比父类更严格的访问权限,
Person类中sleep()方法不变,子类Student中将修饰符改为比public权限更严格的private。
private void sleep(){//报错,不能使用比父类更严格的访问权限
System.out.println(name+"要睡觉...");
}
但是如果父类的sleep()方法被private修饰,子类sleep()方法被比private权限更宽松的public修饰,则能够实现重写,顺利运行。