文章目录
🚀Java继承
🥝为什么需要继承?
在Extends01.java
中, 我们编写了两个类, 一个是Pupil(小学生), 一个是Graduate(研究生).
问题: 两个类的属性和方法有很多是相同的, 怎么办?
===> 继承(代码复用性)
//小学生->模拟小学生考试 的情况
public class Pupil {
public String name;
public int age;
public double score;//成绩
public void setScore(double score) {
this.score = score;
}
public void testing() {
System.out.println("小学生 " + name + " 正在考小学数学");
}
public void showInfo() {
System.out.println("学生名字: " + name + " 年龄 " + age + " 成绩 " + score);
}
}
//大学生-> 模拟大学生考试的简单情况
public class Graduate {
public String name;
public int age;
public double score;//成绩
public void setScore(double score) {
this.score = score;
}
public void testing() {//和Pupil不一样
System.out.println("大学生 " + name + " 正在考大学数学");
}
public void showInfo() {
System.out.println("学生名字: " + name + " 年龄 " + age + " 成绩 " + score);
}
}
public class Extends01 {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "小明";
pupil.age = 12;
pupil.testing();
pupil.setScore(60);
pupil.showInfo();
System.out.println("==================");
Graduate graduate = new Graduate();
graduate.name = "金角大王";
graduate.age = 23;
graduate.testing();
graduate.setScore(60);
graduate.showInfo();
}
}
🥝原理示意图
继承可以解决代码复用, 让我们的编程更加靠近人类思维. 当多个类存在相同的属性(变量)和方法时, 可以从这些类中抽象出父类, 在父类中定义这些相同的属性和方法, 只需要通过extends
来声明继承父类即可
· 继承的基本语法
class 子类 extends 父类 { }
1)子类就会拥有父类定义的属性和方法
2)父类又叫 超类, 基类
3)子类又叫派生类
🥝快速入门
我们对Extends01.java
改进, 使用继承的方法, 体会使用继承的好处.
//父类, 是Pupil和Graduate的父类
public class Student {
//共有的属性
public String name;
public int age;
public double score;//成绩
//共有的方法
public void setScore(double score) {
this.score = score;
}
public void showInfo() {
System.out.println("学生名字: " + name + " 年龄 " + age + " 成绩 " + score);
}
}
//让Pupil 继承 Student类
public class Pupil extends Student {
public void testing() {
System.out.println("小学生 "+ name +" 正在考小学数学");
}
}
public class Graduate extends Student {
public void testing() {//和Pupil不一样
System.out.println("大学生 " + name + " 正在考大学数学");
}
}
public class Extends01 {
public static void main(String[] args) {
com.zzw.extend_.Pupil pupil = new Pupil();
pupil.name = "小明~";
pupil.age = 13;
pupil.testing();
pupil.setScore(80);
pupil.showInfo();
System.out.println("==================");
com.zzw.extend_.Graduate graduate = new Graduate();
graduate.name = "金角大王~";
graduate.age = 22;
graduate.testing();
graduate.setScore(70);
graduate.showInfo();
}
}
继承给编程带来的便利
1)代码的复用性提高了
2)代码的扩展性和维护性提高了
🥝注意事项和使用细节
1.子类继承了父类所有的属性和方法, 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问, 要通过父类提供公共的方法去访问
public class Base { //父类
//4个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public Base() { //无参构造器
System.out.println("Base()....");
}
//父类提供一个public的方法, 返回了n4
protected int getN4() {
return n4;
}
public void test100() {
System.out.println("test100");
}
protected void test200() {
System.out.println("test200");
}
void test300() {
System.out.println("test300");
}
private void test400() {
System.out.println("test400");
}
//call
public void callTest400() {
test400();
}
}
public class Sub extends Base {
public Sub() {
System.out.println("Sub()...");
}
public void sayOk() { //子类方法
//非私有的属性和方法可以在子类直接访问
//但是私有的属性和方法不能在子类直接访问
System.out.println(n1 + " " + n2 + " " + n3);
test100();
test200();
test300();
//test400();错误
//要通过父类提供公共的方法去访问
System.out.println("n4=" + getN4());
callTest400();//
}
}
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub();
sub.sayOk();
}
}
2.子类必须调用父类的构造器, 完成父类的初始化
3.当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器. 如果父类没有提供无参构造器, 则必须在子类的构造器中用super去指定使用父类的哪个构造器以完成对父类的初始化工作. 否则, 编译不会通过.
案例一:
public class Base { //父类
public Base() { //无参构造器
System.out.println("父类Base()构造器被调用....");
}
}
public class Sub extends Base {
public Sub() {
//super();//默认调用父类的无参构造器
System.out.println("子类Sub()构造器被调用...");
}
//当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器
public Sub(String name) {
//do nothing
System.out.println("子类Sub(String name)构造器被调用...");
}
}
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub();//创建子类对象 sub
System.out.println("=======第二个对象=======");
Sub sub2 = new Sub("赵志伟");//创建子类对象 sub2
}
}
输出结果
父类Base()构造器被调用....
子类Sub()构造器被调用...
=======第二个对象=======
父类Base()构造器被调用....
子类Sub(String name)构造器被调用..
案例二:
public class Base { //父类
public Base(String name, int age) { //有参构造器
System.out.println("父类Base(String name, int age)构造器被调用...");
}
}
public class Sub extends Base {
public Sub() {
super("smith", 21);
System.out.println("子类Sub()构造器被调用...");
}
//当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器
public Sub(String name) {
super("scott", 23);
//do nothing
System.out.println("子类Sub(String name)构造器被调用...");
}
}
输出结果
=======第一个对象=======
父类Base(String name, int age)构造器被调用...
子类Sub()构造器被调用...
=======第二个对象=======
父类Base(String name, int age)构造器被调用...
子类Sub(String name)构造器被调用...
4.如果希望指定去调用父类的某个构造器, 则显示地调用一下: super(参数列表)
public class Base { //父类
public Base() { //无参构造器
System.out.println("父类Base()构造器被调用....");
}
public Base(String name, int age) { //有参构造器
System.out.println("父类Base(String name, int age)构造器被调用...");
}
public Base(String name) {//有参构造器
System.out.println("父类Base(String name)构造器被调用...");
}
}
public class Sub extends Base {
public Sub(String name, int age) {
//1.调用父类的无参构造器, 如下; 或者什么都不写, 默认就是调用super()
super();//父类的无参构造器
//2.调用父类的Base(String name) 构造器
super("scott");
//3.调用父类的Base(String name, int age) 构造器
super("scott", 23);
System.out.println("子类Sub()构造器被调用...");
}
}
@SuppressWarnings({"all"})
public class ExtendsDetail {
public static void main(String[] args) {
//System.out.println("=======第一个对象=======");
//Sub sub = new Sub();//创建子类对象 sub
//System.out.println("=======第二个对象=======");
//Sub sub2 = new Sub("赵志伟");//创建子类对象 sub2
System.out.println("=======第三个对象=======");
Sub sub3 = new Sub("赵志伟", 23);//创建子类对象 sub3
}
}
5.super在使用时, 必须放在构造器第一行 (super只能在构造器中使用)
6.super()和this()都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
7.java所有类都是Object类的子类, Object 是所有类的基类.
8.父类构造器的调用不限于直接父类. 将一直往上追溯到Object类(顶级父类)
public class TopBase {//父类是Object
public TopBase() {
//super(); Object的无参构造器
System.out.println("构造器TopBase() 被调用...");
}
}
public class Base extends TopBase{ //父类
public Base(String name, int age) { //有参构造器
//默认的super()
System.out.println("父类Base(String name, int age)构造器被调用...");
}
}
//输入ctrl+H, 可以看到类的继承关系
public class Sub extends Base {
public Sub(String name, int age) {
//调用父类的Base(String name, int age) 构造器
super("scott", 23);
System.out.println("子类Sub()构造器被调用...");
}
}
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub("赵志伟", 23);//创建子类对象 sub
}
}
输出
构造器TopBase() 被调用...
父类Base(String name, int age)构造器被调用...
子类Sub()构造器被调用...
9.子类最多只能继承一个父类(指直接继承), 即java中是单继承机制.
思考: 如何让A类继承B类和C类?
答: 让A继承B, B继承C.
10.不能滥用继承, 子类和父类之间必须满足 is-a 的逻辑关系
Person is a Music?
Music extends Person //不合理
Cat is a Animal
Cat extends Animal //合理
🥝继承的本质分析
案例: 子类继承父类. 创建子类对象时, 内存中到底发生了什么?
讲解继承的本质
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//这时请注意, 要按照查找关系来返回信息
//1.首先查看子类是否有该属性
//2.如果子类有这个属性, 并且可以访问, 则返回信息
//3.如果子类没有这个属性, 就看父类有没有这个属性(如果父类有该属性,并且可以访问, 则返回信息)
//4.如果父类没有就按照3的规则, 继续找上级父类, 直到Object
System.out.println(son.name);//返回的就是大头儿子
//System.out.println(son.age);//返回地就是39
System.out.println(son.getAge());//返回地就是39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {//父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father {//子类
String name = "大头儿子";
}
🥝课堂练习
案例1: main中: B b = new B(); 会输出什么?
分析: 有默认的super()
public class ExtendsExercise01 {
public static void main(String[] args) {
B b = new B();//a, b name, b
}
}
class A {
A() {
System.out.println("a");
}
A(String name) {
System.out.println("a name");
}
}
class B extends A {
B() {
this("abc");
System.out.println("b");
}
B(String name) {
//super(); 默认有个super()
System.out.println("b name");
}
}
输出
a
b name
b
案例2: main中: C c = new C(); 会输出什么?
class A {
public A() {
System.out.println("我是A类");
}
}
class B extends A {
B() {
System.out.println("我是B类的无参构造");
}
B(String name) {
//默认super()
System.out.println(name + "我是B类的有参构造");
}
}
class C extends B {
C() {
this("hello");
System.out.println("我是C类的无参构造");
}
C(String name) {
super("haha");
System.out.println("我是C类的有参构造");
}
}
输出
我是A类
haha我是B类的有参构造
我是C类的有参构造
我是C类的无参构造
案例3: 编写Computer类, 包含CPU, 内存, 硬盘等属性, getDetails方法用于返回Computer的详细信息
编写PC类, 继承Computer类, 添加特有属性 品牌brand
编写NotePad子类, 继承Computer类, 添加特有属性 颜色color
编写ExtendsExercise03
类, 在main方法中创建PC和NotePad对象, 分别给对象中特有的属性赋值, 以及从Computer类继承的属性赋值, 并使用方法打印输出信息
//编写Computer类, 包含CPU, 内存, 硬盘等属性, getDetails方法用于返回Computer的详细信息
public class Computer {
private String cpu;
private int memory;
private String disk;
public Computer(String cpu, int memory, String disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
//返回Computer详细信息
public String getDetails() {
return "电脑 cpu=" + cpu + " 内存=" + memory + " 硬盘=" + disk;
}
//getter, setter方法
}
//编写PC类, 继承Computer类, 添加特有属性 品牌brand
public class PC extends Computer {
private String brand;
//这里idea直接根据继承的规则, 自动地把构造器的调用写好
// 这里也体现 继承设计的基本思想.
// 父类的构造器完成父类属性初始化, 子类的构造器完成子类属性初始化
public PC(String cpu, int memory, String disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void printInfo() {
System.out.println("PC信息: ");
//System.out.println(getCpu() + getMemory() + getDisk());
//调用父类的getDetails方法, 得到相关属性信息
System.out.println(getDetails() + " 品牌=" + brand);
}
}
//编写NotePad子类, 继承Computer类, 添加特有属性 颜色color
public class NotePad extends Computer {
private String color;
public NotePad(String cpu, int memory, String disk, String color) {
super(cpu, memory, disk);
this.color = color;
}
public void printInfo() {
System.out.println("NotePad信息: ");
System.out.println(getDetails() + " 颜色=" + color);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class ExtendsExercise03 {
public static void main(String[] args) {
PC pc = new PC("intel", 1024, "500G", "戴尔");
pc.printInfo();
System.out.println("=================================");
NotePad notePad = new NotePad("intel", 16, "1024G", "DELL");
notePad.printInfo();
}
}
🥝super基本语法
※基本介绍:
- super代表父类的引用, 用于访问父类的属性, 方法, 构造器
※基本语法
- 访问父类的属性, 但不能访问父类的private属性 super.属性名
- 访问父类的方法, 不能访问父类的private方法 super.方法名(参数列表)
- 访问父类的构造器, super(参数列表). 只能放在构造器的第一句, 只能出现一句
public class A {
//4个属性
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public A() {}
public A(String name) {}
public A(String name, int age) {}
public void test100() {
System.out.println("test100");
}
protected void test200() {
System.out.println("test200");
}
void test300() {
System.out.println("test300");
}
private void test400() {
System.out.println("test400");
}
}
public class B extends A {
//访问父类的属性, 但不能访问父类的private属性 super.属性名
public void hi() {
System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
}
//访问父类的方法, 不能访问父类的private方法 super.方法名(参数列表)
public void ok() {
super.test100();
super.test200();
super.test300();
}
B() {
//super();
//super("tom");
super("tom", 23);
}
}
🥝super细节
※super给编程带来的便利/好处
- 调用父类构造器的好处 (分工明确, 父类属性由父类初始化, 子类属性由子类初始化)
- 当子类中有和父类中的成员(属性和方法)重名时, 为了访问父类的成员, 必须通过super. 如果没有重名, 使用super, this和 直接访问是一样的效果.
public class A {
public int n1 = 100;
public void cal() {
System.out.println("A类的cal()方法...");
}
}
public class B extends A {
public int n1 = 333;
public void sum() {
System.out.println("B类的sum()方法...");
//希望调用父类A的cal方法
//这时, 因为子类B没有cal方法, 因此我们可以使用下面三种方式
//找cal方法时(cal() 和 this.cal()), 顺序是:
// (1)先找本类, 如果有, 则调用.
// (2)如果没有, 则找父类(如果有, 并且可以调用, 则调用)
// (3)如果父类没有, 则继续找父类的父类. 整个规则, 都是一样的, 直到Object类.
// 提示: 如果在查找方法的过程中, 找到了, 但不能访问, 则报错, cannot access
// 如果查找方法的过程中, 没有找到, 则提示该方法不存在, cannot resolve
//cal();
//this.cal();//等价 cal()
//找cal方法(super.cal())的顺序是直接查找父类, 其它的规则一样
super.cal();
//演示访问属性的规则
//n1 和 this.n1 查找的规则是
//(1)先找本类, 如果有, 则调用.
//(2)如果没有, 则找父类(如果有, 并且可以调用, 则调用)
//(3)如果父类没有, 则继续找父类的父类. 整个规则, 都是一样的, 直到Object类.
// 提示: 如果在查找属性的过程中, 找到了, 但不能访问, 则报错, cannot access
// 如果查找属性的过程中, 没有找到, 则提示该属性不存在, cannot resolve
System.out.println(n1);//333
System.out.println(this.n1);//333
//找n1(super.n1)的顺序是直接查找父类属性, 其它的规则一样
System.out.println(super.n1);//100
}
}
public class Super01 {
public static void main(String[] args) {
B b = new B();
b.sum();
}
}
- super的访问不限于直接父类, 如果爷爷类和本类中有同名的成员, 也可以使用super去访问爷爷类的成员; 如果多个基类(上级类)中都有同名的成员, 使用super访问遵循就近原则. A->B->C, 当然也需要遵守访问权限的相关规则
public class Base { //父类是Object
public int n1 = 999;
public void cal() {
System.out.println("Base类的cal()方法...");
}
}
public class A extends Base {
//public int n1 = 100;
//public void cal() {
// System.out.println("A类的cal()方法...");
//}
}
public class B extends A {
//编写测试方法
public void test() {
//super的访问不限于直接父类, 如果爷爷类和本类中有同名的成员, 也可以使用super去访问爷爷类的成员;
// 如果多个基类(上级类)中都有同名的成员, 使用super访问遵循就近原则. A->B->C
System.out.println("super.n1=" + super.n1);
super.cal();
}
}
public class Super01 {
public static void main(String[] args) {
B b = new B();
b.test();
}
}
🥝super和this比较
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性, 如果本类没有此属性, 则从父类中继续查找 | 从父类开始查找属性 |
2 | 调用方法 | 访问本类中的方法, 如果本类没有此方法, 则从父类继续查找 | 从父类开始查找方法 |
3 | 调用构造器 | 调用本类构造器, 必须放在构造器的首行 | 调用父类构造器, 必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 子类中访问父类对象 |
🚀方法重写 / 覆盖
🥝基本介绍
简单地说: 方法覆盖(重写) 就是子类有一个方法, 和父类的某个方法的名称, 返回类型, 参数一样, 那么我们就说子类的这个方法覆盖了父类的方法
public class Animal {
public void cry() {
System.out.println("动物发出声音...");
}
}
public class Dog extends Animal{
//解读
//1. 因为Dog 是 Animal子类
//2. Dog的 cry方法 和 Animal的 cry方法 形式一样
//3. 这时我们就说 Dog的cry方法, 重写了Animal的cry方法
public void cry() {
System.out.println("小狗汪汪叫...");
}
}
public class Override01 {
public static void main(String[] args) {
//演示方法重写的情况
Dog dog = new Dog();
dog.cry();//ctrl+b
}
}
🥝注意事项和细节
方法重写也叫方法覆盖, 需要满足下面的条件
- 子类方法的形参列表, 方法名称要和父类方法的形参列表, 方法名称完全一样.
- 子类方法的返回类型要和父类方法返回类型一样, 或者是父类返回类型的子类. 比如, 父类 返回类型是 Object, 子类方法返回类型是String
public class Animal {
public Object m1() {
return null;
}
public String m2() {
return "";
}
public AAA m3() {
return null;
}
}
public class Dog extends Animal{
//细节: 子类方法的返回类型要和父类方法返回类型一样, 或者是父类返回类型的子类.
// 比如, 父类 返回类型是 Object, 子类方法返回类型是String
public String m1() {
return "";
}
//这里Object不是String的子类, 所以编译错误
//public Object m2() {
// return null;
//}
public BBB m3() {
return null;
}
}
class AAA {}
class BBB extends AAA {}
- 子类不能缩小父类方法的访问权限. public > protected > 默认 > private
public class Animal {
protected void eat() { }
}
public class Dog extends Animal{
//细节: 子类方法不能缩小父类方法的访问权限
// public > protected > 默认 > private
public void eat() { }
}
🥝课堂练习
题1: 请对方法重写和方法重载做一个比较
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload ) | 本类 | 必须一样 | 类型, 个数或者是顺序至少有一个不同 | 没有要求 | 没有要求 |
重写(override) | 父子类 | 必须一样 | 相同 | 子类重写的方法, 返回的类型和父类返回的类型一致, 或者是其子类 | 子类方法不能缩小父类方法的访问范围 |
提2:
1.编写一个Person类, 包括属性/private (name, age), 构造器, 方法say(返回自我介绍的字符串)
2.编写一个Student类, 继承Person类, 增加属性/private (id, score), 以及构造器, 定义say方法(返回自我介绍的信息)
3.在main中, 分别创建Person类和Student对象, 调用say方法输出自我介绍.
//编写一个Person类, 包括属性/private (name, age), 构造器, 方法say(返回自我介绍的字符串)
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say() {
return "名字=" + name + " 年龄=" + age;
}
}
//编写一个Student类, 继承Person类, 增加属性/private (id, score), 以及构造器, 定义**say**方法(返回自我介绍的信息)
public class Student extends Person {
private int id;
private double score;
public Student(String name, int age, int id, double score) {
super(name, age);
this.id = id;
this.score = score;
}
public String say() {// 这里体现super的一个好处. 代码复用
return "学生" + super.say() + " id=" + id + " score=" + score;
}
}
public class OverrideExercise {
public static void main(String[] args) {
Person person = new Person("李四", 27);
System.out.println(person.say());
System.out.println("===========================");
Student student = new Student("张三", 23, 1, 89);
System.out.println(student.say());
}
}