继承详细讲解
什么是继承
为什么要继承
Java使用类对现实世界中实体进行描述,类经过实例化的对象可以用来表示现实中实体,现实世界错综复杂,事物之间会存在关联,我们在设计的时候就要去考虑.
类如:猫和狗都是动物,我们使用java就可以设计出:
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+"汪汪叫");
}
}
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+"喵喵叫");
}
}
我们会发现代码有大量重复:
我们该如何将这些共性抽取?面向对象专门提出继承思想,用来对共性的抽取,实现代码的复用.
继承概念
继承机制:是面向对象程序设计使其代码可以实现复用,允许在原有类特性基础上进行扩展,增加新的功能,产生新的类被称为派生类,继承呈现了面向对象程序设计的设计结构.继承解决的问题是:
对共性的抽取,实现代码的复用.
例如:猫和狗都是动物,我们可以将共性的内容抽取,采用继承的思想达到共用.
由上图可知,dogcat都继承了animal类,animal类称为父类或基类,sog和cat被称为子类,派生类,继承过后,子类可以实现复用父类中成员,子类只需关注自己新增加成员就好了.
继承的语法
在Java中使用继承,要借助extends关键字
修饰符 class 子类 extends 父类{
//
}
我们可以去重新设计:
class Animal {
String name;
int age;
float weight;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep()
{
System.out.println(name+"正在睡觉");
}
}
class Dog extends Animal{
void bark(){
System.out.println(name+"汪汪叫");
}
}
class Cat extends Animal{
void mew(){
System.out.println(name+"喵喵叫");
}
}
public class Test1 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.age = 10;
//dog类没有定义成员变量,age属性是从父类Animal中继承下来的
System.out.println(dog.age);
dog.eat();
//dog访问的eat方法也是从Animal中继承下来的
}
}
注意
- 子类会将父类中成员方法或成员变量继承到子类当中.
- 子类继承父类,必须添加自己特有的成员,体现出与父类不同,否则就没有必要继承了.
###父类成员访问
在继承体系中,子类将父类中方法和字段继承下来了,是否可以直接在子类中访问父类继承下来的成员
子类访问父类成员变量
- 子类父类不存在同名成员变量
class A{
int a;
int b;
}
class B extends A{
int c;
public void methon(){
a = 10;//访问从父类继承下来的a
b = 20;//访问从父类继承下来的b
c = 30;//访问自己的c
}
}
- 子类父类成员变量同名
class A{
int a;
int b;
}
class B extends A{
int a;
char b;
int c;
public void method(){
a = 100; // 访问父类继承的a,还是子类自己新增的a?
b = 101; // 访问父类继承的b,还是子类自己新增的b?
c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
}
}
在子类方法或通过子类对象访问成员时:
1.如果访问的变量子类成员中有,优先访问自己的成员变量
2.如果访问的成员变量 与父类中同名,优先访问自己的
3.如果都没有定义,编译报错
成员1变量访问遵循就近原则,自己有则优先自己的,没有则向父类中找.
子类中访问父类的成员方法
- 成员方法名字不同
在子类方法或通过子类对象访问方法,优先访问自己的,自己没有时再在父类中寻找,父类没有则编译器报错. - 成员方法名字相同
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
那如果子类存在与父类相同成员时,如何在子类访问父类相同名称成员?
super关键字
子类父类可能会存在相同名称的成员,想要在子类成员中访问父类方法,直接访问肯定是做不到,这时就提供了super关键字, 作用就是在子类方法中访问父类成员.
class A{
int a;
int b;
public void methodA(){
System.out.println("A中的方法A");
}
public void methodB(){
System.out.println("A中的方法B");
}
}
class B extends A{
int a;//与父类成员变量同名类型相同
char b;//与父类成员变量相同类型不同
public void methodA(int a){
System.out.println("B中的方法A");
}
//与父类中方法重载
public void methodB(){
System.out.println("B中的方法B");
}
//与父类中方法构成重写
public void methodC(){
a= 100;//等价于this.a =100;
b= 101;//等价于this.b =101;
//注意:this是当前对象的引用
//访问父类成员变量,要借助super关键字
//Ssuper获取的是子类对象从基类继承下来的部分
super.a = 200;
super.a = 201;
//子类和父类构成重载,可以直接通过参数列表区分访问的是父类还是子类
methodA();//没有传参,访问父类的
methodA(a);//传递int参数,访问子类的
//子类中访问重写的父类方法,要借助super关键字
methodB();//直接访问,永远访问到的是是子类方法
super.methodB();//访问父类方法
}
}
注意:
- 只能在非静态方法中使用
- 在子类方法中,访问父类成员变量方法
子类构造方法
子类对象在构造时,需要先调用基类构造方法,再去执行子类构造方法
public class Base {
public Base(){
System.out.println("Base()");
}
public class Derived extends Base{
public Derived(){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
// 并且只能出现一次
System.out.println("Derived()");
}
}
public class Test {
public static void main(String[] args) {
Derived d = new Derived();
}
}
结果打印:
Base()
Derived()
子类对象成员有两部分组成,父类继承下来及子类新增加的部分,在构造子类对象的时候,要先调用基类构造方法,将基类继承下来的成员构造完整,再调用自己的构造方法,将子类自己新增加的成员初始化完整.
注意:
- 若父类显式定义无参或者默认构造方法,在子类构造方法第一行默认含有super()调用,即调用基类构造方法
- 如果父类构造方法带有参数,就要子类显示定义构造方法,在子类选择合适的父类构造方法调用,否则编译失败
- super()只能出现一次,不能和this同时出现
- 在子类构造方法,super()调用父类构造时,必须是子类构造函数第一条语句
super和this
他们都可以在成员方法中访问成员变量和调用其他的成员函数,都可以作为构造方法第一条语句,那有什么区别呢?
相同点:
- 都是Java关键字
- 在类的非静态方法使用,来访问非静态成员和字段
- 在构造方法中调用,需要是构造方法第一条语句,并且不能同时存在
不同点: - this是当前对象引用 ,当前对象调用实例方法对象,super相当于子类对象从父类继承下来部分的引用
- 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
- 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造
方法中出现 - 构造方法中一定会存在super(…)的调用,没有写编译器也会增加,但是this(…)不写则没有
再谈初始化
我们之前就说过代码块,我们知道有几个重要的代码块:实例代码块,静态代码块,在没有继承时执行顺序.
class Animal {
public String name;
public int age;
public float weight;
public Animal(String name, int age, float weight) {
this.name = name;
this.age = age;
this.weight = weight;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class Test1 {
public static void main(String[] args) {
Animal animal1 =new Animal("zahngsan1",11,12.1f);
System.out.println("================");
Animal animal2 =new Animal("lisi",12,12);
}
}
执行结果:
静态代码块执行
实例代码块执行
构造方法执行
================
实例代码块执行
构造方法执行
- 静态代码块只执行一次,最先执行,在类加载阶段执行
- 有对象创建,才执行实例代码块,实例代码块执行完成后 ,最后构造方法执行
在继承关系上执行顺序:
class Animal {
public String name;
public int age;
public float weight;
public Animal(String name, int age, float weight) {
this.name = name;
this.age = age;
this.weight = weight;
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
class Cat extends Animal{
public Cat(String name, int age, float weight) {
super(name, age, weight);
System.out.println("构造方法执行");
}
{
System.out.println("实例代码块执行");
}
static {
System.out.println("静态代码块执行");
}
}
public class Test1 {
public static void main(String[] args) {
Cat cat1=new Cat("zahngsan1",11,12.1f);
System.out.println("================");
Cat cat2 =new Cat("lisi",12,12);
}
}
执行结果
静态代码块执行
静态代码块执行
实例代码块执行
构造方法执行
实例代码块执行
===========
实例代码块执行
构造方法执行
实例代码块执行
构造方法执行
我们可知:
- 父类静态代码块优先于子类代码块执行,且最早执行
- 父类实例代码块和父类构造方法再接着执行,其次是子类实例代码块和构造方法
- 第二次实例化子类对象,父类和子类静态代码块都不再被执行
protected关键字
在类和对象中,为实现封装,java要引入访问限定符,限定类或者类中成员是否在类外或其他包中访问.
那父类中不同访问权限的成员,在子类中可见性是什么呢
// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类B中就不设置成员方法了
// extend01包中
public class B {
private int a;
protected int b;
public int c;
int d;
}
// extend01包中
// 同一个包中的子类
public class D extends B{
public void method(){
// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
super.c = 30; // 父类中public成员在相同包子类中可以直接访问
super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
}
}
// extend02包中
// 不同包中的子类
public class C extends B {
public void method(){
// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
// extend02包中
// 不同包中的类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
}
父类中private成员变量在子类中不能直接访问,但是会继承到子类中.
我们要让类尽量做到"封装",隐藏内部细节,只暴露出必要信息.
我们要在使用时使用比较严格的访问权限,不能对访问权限滥用
继承方式
在生活中,很复杂,如:
在Java中支持以下几种:
final关键字
final可以修饰类,变量和成员方法.
- 修饰变量字段,则成为常量,不能改变.
- 修饰类,则不能继承.
- 修饰方法则不能重写
继承和组合
组合也是表达类之间的关系,做到代码重用,就是将一个类的实例作为另外一个类的字段.
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合
会继承到子类中.
我们要让类尽量做到"封装",隐藏内部细节,只暴露出必要信息.
我们要在使用时使用比较严格的访问权限,不能对访问权限滥用
继承方式
在生活中,很复杂,如:
[外链图片转存中…(img-9jiUqWVS-1691725704874)]
在Java中支持以下几种:
[外链图片转存中…(img-uYbsrOeg-1691725704874)]
final关键字
final可以修饰类,变量和成员方法.
- 修饰变量字段,则成为常量,不能改变.
- 修饰类,则不能继承.
- 修饰方法则不能重写
继承和组合
组合也是表达类之间的关系,做到代码重用,就是将一个类的实例作为另外一个类的字段.
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合