🎄🎄🎄继承
🍆🍆继承的概念:
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类(父类)特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类(子类)。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用。
🍆🍆为什么需要继承?
在现实生活中的实体有着各种各样的联系,java的类是对现实实体的描述;现实生活错综复杂,实体和实体之间可能存在这某种关系。通过继承可以实现对某些实体共性的抽取,简化我们的代码。就比如生活中,猫和狗都是动物,那么就可以继承animal这个类,它们一样会睡觉,一样会吃东西,会发出叫声。那么我们就可以通过代码对它们的共性进行抽取。
// Dog.java public class Dog{ string name; int age; float weight; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep(){ System.out.println(name + "正在睡觉"); } void Bark(){ System.out.println(name + "汪汪汪~~~"); } } // Cat.Java public class Cat{ string name; int age; float weight; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep(){ System.out.println(name + "正在睡觉"); } void mew(){ System.out.println(name + "喵喵喵~~~") } }
继承整理之后:
// Animal.java public class Animal{ String name; int age; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep(){ System.out.println(name + "正在睡觉"); } } // Dog.java public class Dog extends Animal{ void bark(){ System.out.println(name + "汪汪汪~~~"); } } // Cat.Java public class Cat extends Animal{ void mew(){ System.out.println(name + "喵喵喵~~~"); } }
🍆🍆父类成员的访问
🐽子类中访问父类的成员变量
1.子类和父类不存在同名的成员变量
public class Base { int a; int b; } public class Derived extends Base{ int c; public void method(){ a = 10; // 访问从父类中继承下来的a b = 20; // 访问从父类中继承下来的b c = 30; // 访问子类自己的c } }
2. 子类和父类成员变量同名
public class Base { int a; int b; int c; } / public class Derived extends Base{ int a; // 与父类中成员a同名,且类型相同 char b; // 与父类中成员b同名,但类型不同 public void method(){ a = 100; // 访问父类继承的a,还是子类自己新增的a? b = 101; // 访问父类继承的b,还是子类自己新增的b? c = 102; // 子类没有c,访问的肯定是从父类继承下来的c // d = 103; // 编译失败,因为父类和子类都没有定义成员变量b } }
子类中访问成员变量时,若子类成员变量与父类成员变量同名,优先访问子类的成员变量,若子类中没有再去父类中寻找,父类子类都没有,则编译报错;
访问成员变量遵循就近原则,自己有先访问自己的,自己没有向父类中找
🐽子类中访问父类的成员方法
1.当成员方法名字不同时:
public class Base { public void methodA(){ System.out.println("Base中的methodA()"); } } public class Derived extends Base{ public void methodB(){ System.out.println("Derived中的methodB()方法"); } public void methodC(){ methodB(); // 访问子类自己的methodB() methodA(); // 访问父类继承的methodA() // methodD(); // 编译失败,在整个继承体系中没有发现方法methodD() } }
和成员变量类似,就近原则;
2.当成员名字方法相同时:
public class Base { public void methodA(){ System.out.println("Base中的methodA()"); } public void methodB(){ System.out.println("Base中的methodB()"); } } public class Derived extends Base{ public void methodA(int a) { System.out.println("Derived中的method(int)方法"); } public void methodB(){ System.out.println("Derived中的methodB()方法"); } public void methodC(){ methodA(); // 没有传参,访问父类中的methodA() methodA(20); // 传递int参数,访问子类中的methodA(int) methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到 } }
1.当子类和父类的成员方法不同名时会优先执行子类的成员方法,若子类没有这个方法就在父类中找,若父类中也没找到则编译出错。
2.通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
问题:如果子类和父类成员相同时,子类应该如何访问父类的成员呢?
🐽super关键字
主要作用:在子类中访问父类成员
/**
* Created with IntelliJ IDEA
* Description:
* User:luzhimin
* Date:2023-07-24
* Time:16:08
*/
public class Base {
int a;
int b;
public void methodA() {
System.out.println("Base中的methodA()");
}
public void methodB() {
System.out.println("Base中的methodB()");
}
}
class Derived extends Base {
int a; // 与父类中成员变量同名且类型相同
char b; // 与父类中成员变量同名但类型不同
// 与父类中methodA()构成重载
public void methodA(int a) {
System.out.println("Derived中的method()方法");
} // 与基类中methodB()构成重写
public void methodB() {
System.out.println("Derived中的methodB()方法");
}
public void methodC() {
// 对于同名的成员变量,直接访问时,访问的都是子类的
a = 100; // 等价于: this.a = 100;
b = 101; // 等价于: this.b = 101;
// 注意:this是当前对象的引用
// 访问父类的成员变量时,需要借助super关键字
// super是获取到子类对象中从基类继承下来的部分
super.a = 200;
super.b = 201;
// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
methodA(); // 没有传参,访问父类中的methodA()
methodA(20); // 传递int参数,访问子类中的methodA(int)
// 如果在子类中要访问重写的基类方法,则需要借助super关键字
methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
super.methodB(); // 访问基类的methodB()
}
}
子类和父类的构造方法
子类如果是继承于父类,当子类对象构造的时候会先调用父类的构造方法,然后再执行子类的构造方法。super();语句就是用来调用父类构造方法的。
public class A { public A(){ System.out.println("A"); } } class B extends A{ public B(){ //super(); //如果父类的构造方法是无参的,编译器会自动加上,如果不是就要自己写上去,按照父类构造方法的参数写上去; System.out.println("B"); } public static void main(String[] args) { B b = new B();//实例化B对象会先对A进行构造 } }
运行结果:
注意:
1.若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构
造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的
父类构造方法调用,否则编译失败。
3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
🐽继承关系上静态代码块,实例代码块,构造方法的执行顺序
非继承时:
class Person { public Person() { System.out.println("构造方法执行"); } { System.out.println("实例代码块执行"); } static { System.out.println("静态代码块执行"); } } class TestDemo { public static void main(String[] args) { Person person1 = new Person(); System.out.println("============================"); Person person2 = new Person(); } }
运行结果:
在非继承时,我们可以发现代码执行的顺序依次是 静态代码块->实例代码块->构造方法,并且静态代码块只执行一次(第二次构造的时候不执行了)
继承关系的执行顺序:
public class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person:构造方法执行"); } { System.out.println("Person:实例代码块执行"); } static { System.out.println("Person:静态代码块执行"); } } class Student extends Person { public Student(String name, int age) { super(name, age); System.out.println("Student:构造方法执行"); } { System.out.println("Student:实例代码块执行"); } static { System.out.println("Student:静态代码块执行"); } } class TestDemo4 { public static void main(String[] args) { Student student1 = new Student("lzm", 19); System.out.println("==========================="); Student student2 = new Student("luzhimin", 20); } }
运行结果:
总结:在继承关系上我们可以发现静态代码块同样是只执行一次;
继承关系上的执行顺序是 父类静态代码块->子类静态代码块->父类实例代码块->父类构造方法->子类实例代码块->子类构造方法。