继承详细讲解

继承详细讲解

什么是继承

为什么要继承

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+"喵喵叫");
    }
}

我们会发现代码有大量重复:

image-20230810101819030
我们该如何将这些共性抽取?面向对象专门提出继承思想,用来对共性的抽取,实现代码的复用.

继承概念

继承机制:是面向对象程序设计使其代码可以实现复用,允许在原有类特性基础上进行扩展,增加新的功能,产生新的类被称为派生类,继承呈现了面向对象程序设计的设计结构.继承解决的问题是:
对共性的抽取,实现代码的复用.
例如:猫和狗都是动物,我们可以将共性的内容抽取,采用继承的思想达到共用.

image-20230810103756728
由上图可知,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中继承下来的
    }
}

注意

  1. 子类会将父类中成员方法或成员变量继承到子类当中.
  2. 子类继承父类,必须添加自己特有的成员,体现出与父类不同,否则就没有必要继承了.
    ###父类成员访问
    在继承体系中,子类将父类中方法和字段继承下来了,是否可以直接在子类中访问父类继承下来的成员
子类访问父类成员变量
  1. 子类父类不存在同名成员变量
class A{
    int a;
    int b;
}
class B extends A{
    int c;
    public void methon(){
        a = 10;//访问从父类继承下来的a
        b = 20;//访问从父类继承下来的b
        c = 30;//访问自己的c
    }
}
  1. 子类父类成员变量同名
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变量访问遵循就近原则,自己有则优先自己的,没有则向父类中找.

子类中访问父类的成员方法
  1. 成员方法名字不同
    在子类方法或通过子类对象访问方法,优先访问自己的,自己没有时再在父类中寻找,父类没有则编译器报错.
  2. 成员方法名字相同
    通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
    通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
    那如果子类存在与父类相同成员时,如何在子类访问父类相同名称成员?

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();//访问父类方法
    }
}

注意:

  1. 只能在非静态方法中使用
  2. 在子类方法中,访问父类成员变量方法

子类构造方法

子类对象在构造时,需要先调用基类构造方法,再去执行子类构造方法

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()

子类对象成员有两部分组成,父类继承下来及子类新增加的部分,在构造子类对象的时候,要先调用基类构造方法,将基类继承下来的成员构造完整,再调用自己的构造方法,将子类自己新增加的成员初始化完整.
注意:

  1. 若父类显式定义无参或者默认构造方法,在子类构造方法第一行默认含有super()调用,即调用基类构造方法
  2. 如果父类构造方法带有参数,就要子类显示定义构造方法,在子类选择合适的父类构造方法调用,否则编译失败
  3. super()只能出现一次,不能和this同时出现
  4. 在子类构造方法,super()调用父类构造时,必须是子类构造函数第一条语句

super和this

他们都可以在成员方法中访问成员变量和调用其他的成员函数,都可以作为构造方法第一条语句,那有什么区别呢?
相同点:

  1. 都是Java关键字
  2. 在类的非静态方法使用,来访问非静态成员和字段
  3. 在构造方法中调用,需要是构造方法第一条语句,并且不能同时存在
    不同点:
  4. this是当前对象引用 ,当前对象调用实例方法对象,super相当于子类对象从父类继承下来部分的引用
  5. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
  6. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造
    方法中出现
  7. 构造方法中一定会存在super(…)的调用,没有写编译器也会增加,但是this(…)不写则没有
    iage-20230810152122271

再谈初始化

我们之前就说过代码块,我们知道有几个重要的代码块:实例代码块,静态代码块,在没有继承时执行顺序.

 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);
     }
}

执行结果:

静态代码块执行
实例代码块执行
构造方法执行
================
实例代码块执行
构造方法执行
  1. 静态代码块只执行一次,最先执行,在类加载阶段执行
  2. 有对象创建,才执行实例代码块,实例代码块执行完成后 ,最后构造方法执行
    在继承关系上执行顺序:
 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);
     }
}
执行结果
静态代码块执行
静态代码块执行
实例代码块执行
构造方法执行
实例代码块执行
===========
实例代码块执行
构造方法执行
实例代码块执行
构造方法执行

我们可知:

  1. 父类静态代码块优先于子类代码块执行,且最早执行
  2. 父类实例代码块和父类构造方法再接着执行,其次是子类实例代码块和构造方法
  3. 第二次实例化子类对象,父类和子类静态代码块都不再被执行

protected关键字

在类和对象中,为实现封装,java要引入访问限定符,限定类或者类中成员是否在类外或其他包中访问.

image-20230811111501724
那父类中不同访问权限的成员,在子类中可见性是什么呢

// 为了掩饰基类中不同访问权限在子类中的可见性,为了简单类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成员变量在子类中不能直接访问,但是会继承到子类中.
我们要让类尽量做到"封装",隐藏内部细节,只暴露出必要信息.
我们要在使用时使用比较严格的访问权限,不能对访问权限滥用

继承方式

在生活中,很复杂,如:

image-20230811114027030
在Java中支持以下几种:

image-20230811114115984

final关键字

final可以修饰类,变量和成员方法.

  1. 修饰变量字段,则成为常量,不能改变.
  2. 修饰类,则不能继承.
  3. 修饰方法则不能重写

继承和组合

组合也是表达类之间的关系,做到代码重用,就是将一个类的实例作为另外一个类的字段.
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合
会继承到子类中.
我们要让类尽量做到"封装",隐藏内部细节,只暴露出必要信息.
我们要在使用时使用比较严格的访问权限,不能对访问权限滥用

继承方式

在生活中,很复杂,如:

[外链图片转存中…(img-9jiUqWVS-1691725704874)]
在Java中支持以下几种:

[外链图片转存中…(img-uYbsrOeg-1691725704874)]

final关键字

final可以修饰类,变量和成员方法.

  1. 修饰变量字段,则成为常量,不能改变.
  2. 修饰类,则不能继承.
  3. 修饰方法则不能重写

继承和组合

组合也是表达类之间的关系,做到代码重用,就是将一个类的实例作为另外一个类的字段.
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值