文章目录
6.2 继承
6.2.1 继承的概述
生活中的继承有延续(如继承财产,基因等)、扩展(下一代在上一代的基础上又有所成就)的意思
Java中的继承:
- 多个类中存在相同属性时,将这些内容抽取到单独的一个类中,而存在这些属性和行为的类无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系即可。
- 其中需要这些属性和方法的多个类称为
子类
,也叫派生类
;而抽取出来的这个类被称为父类、超类或基类
。 - 继承描述的是事物之间的所属关系,这种关系是:
is-a
的关系。如猫属于动物,狗也属于动物。由此可见父类更通用或更一般
,子类更具体
。通过继承可以使多种事物之间形成一种关系体系。
继承的好处:
- 提高了代码的复用性
- 提高了代码的可扩展性
继承的弊端:
- 增加了类与类之间的耦合性
6.2.2 继承的语法格式
使用extends
关键字来声明类之间的继承关系
[修饰符] class 父类 {
...
}
[修饰符] class extends 父类 {
...
}
案例:
public class Animal { // 定义Animal作为父类
String name ;
int age ;
public void eat() {
System.out.println(name + "在吃东西");
}
}
public class Cat extends Animal { // 创建Cat类继承Animal类
String color ;
public void sleep() {
System.out.println("在睡觉...");
}
}
class CatTest {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "小黑" ;
cat.age = 1 ;
cat.color = "黑色" ;
cat.eat();
cat.sleep();
}
}
6.2.3 继承的特点
- 子类继承了父类,就
继承了父类的方法和属性
。- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和 方法。
- 在Java 中,继承的关键字用的是“
extends
”,即子类不是父类的子集,而是对父类的“扩展
”。
- 关于继承的规则:
- 子类不能直接访问父类中
私有的(private)
的成员变量和方法。
示例:
- 子类不能直接访问父类中
public class People {
private String name ;
private int 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;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age;
}
}
public class Student extends People {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String getInfo(){
// return "姓名:" + name + ",年龄:" + age;
// 在子类中不能直接使用父类私有的name和age
return "姓名:" + getName() + ",年龄:" + getAge();
}
}
class StudentTest {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(23);
student.setScore(89);
System.out.println(student.getInfo());
}
}
- Java只支持
单继承和多层继承
,不允许多继承- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
- Java只支持单继承
public class A{}
class B extends A{}
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} // 可以
class C extends A,B...{} // 报错
- Java支持多层继承
class A{}
class B extends A{}
class C extends B{}
- 一个父类可以同时拥有多个子类
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述
。而实例变量和实例方法就是事物的特征
,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
-
当子类对象被创建时,
在堆中给对象申请内存
时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。 -
当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板
6.2.4 IDEA中查看继承关系
快捷键:Ctrl + H 显示类的继承树
快捷键:Ctrl + Alt + U 图形化显示类的继承结构
- 子类和父类是一种相对的概念,比如说A类是B类的父类,B类又可能是E类的父类
6.2.5 权限修饰符限制问题
权限修饰符:public,protected,缺省,private
外部类要跨包使用必须是public
,否则仅限于在本包使用- 外部类的权限修饰符缺省,可以在本包使用
外部类的权限修饰符缺省,不能跨包使用
成员的权限修饰符问题
- 本包下,成员的权限修饰符可以是public、protected、缺省
- 跨包使用
- 跨包使用时,如果类的权限修饰符缺省,即使其成员权限修饰符 > 类的权限修饰符 也没有意义
6.2.6 this和super的区别
this 表示当前所在类对象的引用
- this.成员变量 调用本类的成员变量,也可以调用父类的成员变量
- this() 调用本类的构造方法
- this.成员方法调用本类的成员方法,也可以调用父类的成员方法
super 代表当前对象父类的引用
- super.成员变量 调用父类的而成员变量
- super() 调用父类的构造方法
- super.成员方法 调用父类的成员方法
public class Demo1 extends Object{
public static void main(String[] args) {
Son son = new Son();
son.method();
}
}
class Father {
int num = 10 ;
int num1 = 30 ;
}
class Son extends Father{
int num = 20 ;
int num2 = 40 ;
public void method() {
System.out.println("num = " + num);
System.out.println("super.num = " + super.num);
System.out.println("super.num1 = " + super.num1);
System.out.println("num2 = " + num2);
}
}
对于子类和父类中的变量的问题,相同变量名的变量(实际开发中不会出现)会优先采用子类中的变量,就近原则
6.2.7 继承中构造器的关系
- 子类继承父类时,
不会继承父类的构造器
,只能通过super()或super(实参列表)
的方式来调用父类的构造器- 子类构造器中一定会调用父类的构造器,默认调用父类的无参构造,
super();可以省略。
- 如果父类没有无参构造或者有无参构造但是子类就是想要调用父类的有参构造,则必须使用super(实参列表);
- super()和super(实参列表)都只能出现在子类构造器的首行,并且与this(…)只能出现其一
案例:
public class Employee {
private String name ;
private int age ;
private double salary ;
public Employee() {
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age +",薪资:" + salary;
}
}
class Manager extends Employee {
private String sex ;
public Manager() {
super(); // 调用父类的无参构造器,可以省略
}
public Manager(String name, int age, double salary) {
super(name, age, salary); // 调用父类的有参构造器
}
public Manager(String name, int age, double salary, String sex) {
super(name, age, salary); // 调用父类的有参构造器
this.sex = sex;
}
@Override
public String getInfo() {
return super.getInfo() + " , 性别 : " + sex;
}
}
class Test1 {
public static void main(String[] args) {
Manager manager1 = new Manager();
Manager manager2 = new Manager("张三" , 26 , 6000);
Manager manager3 = new Manager("李四" , 30 , 7000 , "男");
System.out.println(manager1.getInfo());
System.out.println(manager2.getInfo());
System.out.println(manager3.getInfo());
}
}
6.2.7.1 子父类构造器的八种形式
1. 第一种形式:
/**
*
* A类和B类都是默认有一个无参构造器,
* B类的无参构造器还会默认调用A类的无参构造
*/
public class A {
}
class B extends A {
}
class Test2 {
public static void main(String[] args) {
B b = new B();
}
}
2. 第二种形式:
/**
* 第二种形式:
* 在A类中声明一个无参构造器,并打印输出一句话
* B类默认有一个无参构造
* 子类B会默认调用父类A的无参构造
*/
public class A {
public A() {
System.out.println("A类的无参构造器");
}
}
class B extends A {
}
class Test2 {
public static void main(String[] args) {
B b = new B();
}
}
3. 第三种形式:
/**
* 第三种形式:
* 在A类中声明一个无参构造器,并打印输出一句话
* 在B类中声明一个无参构造器,并打印输出一句话
* 虽然没有在B类的无参构造中写super()
* 但子类B还是会默认调用父类A的无参构造
*/
public class A {
public A() {
System.out.println("A类的无参构造器");
}
}
class B extends A {
public B() {
// super();
System.out.println("B类的无参构造器");
}
}
class Test2 {
public static void main(String[] args) {
B b = new B();
}
}
4. 第四种形式:
/**
* 第四种形式:
* 在A类中声明一个无参构造器,并打印输出一句话
* 在B类中声明一个无参构造器,并打印输出一句话
* 在B类的无参构造中写上super()
* 子类B会调用父类A的无参构造
*/
public class A {
public A() {
System.out.println("A类的无参构造器");
}
}
class B extends A {
public B() {
super();
System.out.println("B类的无参构造器");
}
}
class Test2 {
public static void main(String[] args) {
B b = new B();
}
}
5. 第五种形式:
/**
* 第五种形式:
* 在A类中声明一个有参构造器,并打印输出一句话
* B类不定义构造方法,但是系统会默认有一个无参构造,且会默认调用父类A的无参构造
* 但是我们知道,在我们没有定义构造方法时系统会默认提供一个无参构造,
* 但是当我们定义了一个有参构造后,系统就不会再提供无参构造器了,
* 所以说A类中现在没有无参构造
*/
public class A {
public A(int a) {
System.out.println("A类的有参构造器");
}
}
class B extends A {
/*public B() { // 报错
System.out.println("B类的无参构造器");
}*/
}
class Test2 {
public static void main(String[] args) {
B b = new B();
}
}
6. 第六种形式:
/**
* 第六种形式:
* 在A类中声明一个有参构造器,并打印输出一句话
* 在B类中声明一个无参构造器,且调用父类无参构造
*/
public class A {
public A(int a) {
System.out.println("A类的有参构造器");
}
}
class B extends A {
public B() {
super(); // 报错
System.out.println("B类的无参构造器");
}
}
class Test2 {
public static void main(String[] args) {
B b = new B();
}
}
7. 第七种形式:
/**
* 第七种形式:
* 在A类中声明一个有参构造器,并打印输出一句话
* 在B类中声明一个有参构造器,且调用父类有参构造
*/
public class A {
public A(int a) {
System.out.println("A类的有参构造器");
}
}
class B extends A {
public B(int a) {
super(a);
System.out.println("B类的有参构造器");
}
}
class Test2 {
public static void main(String[] args) {
B b = new B(1);
}
}
8. 第八种形式:
/**
* 第六种形式:
* 在A类中声明一个无参构造器和一个有参构造器
* 在B类中声明一个无参构造器和一个有参构造器
*/
public class A {
public A() {
System.out.println("A类的无参构造器");
}
public A(int a) {
System.out.println("A类的有参构造器");
}
}
class B extends A {
public B() {
super(); // 可省略
System.out.println("B类的无参构造器");
}
public B(int a) {
super(a);
System.out.println("B类的有参构造器");
}
}
class Test2 {
public static void main(String[] args) {
B b1 = new B() ;
B b2 = new B(1);
}
}
6.2.8 继承中成员方法的关系
- 在子父类中,如果存在不同名的方法,子类可以直接调用父类的
- 同名的方法,被重写的方法,用子类对象调用的是子类重写后的方法,没有被重写的方法调用的是父类的方法
- 想要调用父类的方法,可以在子类重写方法里添加
super();
6.2.8.1 方法重写(Override)
父类的所有成员方法都会被子类继承,但是某些继承的方法可能不太适用于子类
,这时候我们就需要用到方法的重写
了。
- 方法重写
案例:
public class Phone {
public void call() {
System.out.println("打电话");
}
public void sendMessage() {
System.out.println("发短信");
}
public void showNum() {
System.out.println("来电显示号码");
}
}
// 继承Phone类
class SmartPhone extends Phone {
// 重写父类来电显示的方法
@Override
public void showNum() {
super.showNum(); // 调用父类的showNum()方法
System.out.println("显示来电人姓名");
}
}
class Test3 {
public static void main(String[] args) {
SmartPhone phone = new SmartPhone();
phone.showNum();
}
}
@Override
:写在方法上面,用来检测该方法是不是满足重写方法的要求
。可以省略,只要满足重写要求也会是方法覆盖重写。但是建议保留该注解,编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
- IDEA中方法
重写的快捷键:Ctrl + O
- 方法重写的要求
必须保证父类和子类之间要重写的
方法名称完全一致
参数列表完全相同
子类方法的返回值类型必须要小于等于父类方法的返回值类型
。(如果返回值是引用数据类型,小于的意思就是它的子类;如果返回值类型是基本数据类型或void,那么必须相同)
子类方法的权限修饰符必须大于等于父类方法的权限修饰符
- public > protected > 缺省 > private
- 父类的私有方法不能重写,跨包的父类缺省方法也不能重写
注意:子类与父类中同名同参数的方法必须同时声明为非static的(为重写)
,或者同时声明为static的(不是重写)
。因为static方法是属于类的,子类无法覆盖父类的方法
6.2.8.2 方法的重载和重写对比
- 方法的重载:
- 在同一个类中
- 方法名必须相同,参数列表必须不同
- 参数列表不同的体现:参数的类型不同或参数的个数不同
- 与返回值无关
- 方法的重写:
- 父子类中
- 必须保证方法名称完全一致
- 参数列表完全相同
- 子类方法的返回值类型必须要小于等于父类方法的返回值类型。(如果返回值是引用数据类型,小于的意思就是它的子类;如果返回值类型是基本数据类型或void,那么必须相同)
- 子类方法的权限修饰符必须大于等于父类方法的权限修饰符