Java三板斧—继承

目录

1、前言

2、继承

2.1 为什么要继承

2.2 继承概念

2.3 继承语法

2.4 父类成员访问

2.4.1 子类中访问父类的成员变量

2.4.2 子类中访问父类的成员方法

2.5 super关键字 

2.5.1 访问父类的同名成员

2.5.2 访问父类的同名方法

2.5.3 子类的构造方法

2.5.4 super和this

2.6 继承的初始化

2.7 protected 关键字

2.8 继承方式

2.9 final 关键字

3、结语


1、前言

本次blog我们介绍Java的第二板斧头,继承与多态,那么什么是继承,什么又是多态,他俩又会擦出怎样的火花呢,相信看完这次的blog你就会有答案了,因为涉及的知识点较多,所以继承与多态我们放在两次blog中来讲解。

2、继承

2.1 为什么要继承

Java中使用类和对象对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。

  • 我们看下面这个例子
 class Dog{
    String name;
    int age;
    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    void Bark(){
        System.out.println(name + "汪汪汪~~~");
    }
}

 class Cat{
    String name;
    int age;
    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    void mew(){
    System.out.println(name + "喵喵喵~~~");
    }
}
  • 分别定义了猫类和狗类,我们发现他们类当中的成员变量以及成员方法有很多是一模一样,如下图所示,红色框出的即为相同的。
  • 那么我们能否给出一个类,专门用来存放这些共同的属性呢?
  • Java中面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。

2.2 继承概念

  • 继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类的特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。
  • 继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用
  • 例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。

  • 所以我们定义了一种Animal的类,来存储Dog和Cat类中的共性属性,我们将Animal叫做父类、基类或超类;将Dog和Cat的类叫做Animal的子类或者派生类,子类与父类直接就是所谓的继承关系。
  • 继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可
  • 从继承概念中可以看出继承最大的作用就是:实现代码复用,实现多态的形式。

2.3 继承语法

  • 在Java中如果要表示类之间的继承关系,需要借助extends关键字,形式如下:

修饰符 class 子类 extends 父类 {
// ...
}

  •  在如下代码中,Dog和Cat类都继承了父类Animal,所以父类中的name,age,和eat成员变量以及方法也都继承过来了,所以在main函数中,dog对象可以访问这些父类的方法和成员变量。
class Animal{
    private String name;
    private int age;
    public void eat(){
        System.out.println(name +" 正在吃饭!(Animal)");
    }
// Dog.java
class Dog extends Animal{
    void bark(){
        System.out.println(name + "汪汪汪~~~");
    }
}
// Cat.Java
public class Cat extends Animal{
    void mew(){
        System.out.println(name + "喵喵喵~~~");
    }
}
public class TestExtend {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的
        System.out.println(dog.name);
        System.out.println(dog.age);
        // dog访问的eat()和sleep()方法也是从Animal中继承下来的
        dog.eat();
        dog.bark();
    }
}
  • 注意:
  1. 子类会将父类中的成员变量或者成员方法继承到子类中了;
  2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。

2.4 父类成员访问

以下讨论都是基于此代码,其中Base是父类,Derived是子类,子类继承了父类中的属性。

class Base {
    public int a=1;
    public int b=2;

    public void methodA() {
        System.out.println("Base::methodA()");
    }

    public void methodB() {
        System.out.println("Base::methodB()");
    }
}
class Derived extends Base {
    public int c=3;
    public int d=4;

    public void methodA(int val) {
        System.out.println("Base::methodA(int)"+val);
    }

    public void methodB() {
        System.out.println("Derived::methodB()");
    }

    public  void test() {
        methodA();
        methodA(10);
        super.methodB();
        /*System.out.println(this.a);
        System.out.println(this.b);
        System.out.println(this.d);
        System.out.println("访问父类的a: "+super.a);*/
    }
}
public class Test2 {
    public static void main(String[] args) {
        Derived derived = new Derived();
        derived.test();
    }
}

2.4.1 子类中访问父类的成员变量

1、子类和父类不存在同名成员变量

  •  子类和父类不存在同名的变量,可以直接访问到子类或者父类的成员

  • 其中a、b是父类继承下来的,c、d是子类自己独有的,下面的图方便理解。

  2、子类和父类成员变量同名

  • 用super.a来调用父类Base的a,否则就是优先调用自己Derived类的a。
class Base {
    public int a=1;
    public int b=2;

    public void methodA() {
        System.out.println("Base::methodA()");
    }

    public void methodB() {
        System.out.println("Base::methodB()");
    }
}
class Derived extends Base {
    public int a=3;
    public int d=4;
}

 3、总结

  • 在子类方法中 或者 通过子类对象访问成员时:
  • 如果访问的成员变量子类中有,优先访问自己的成员变量
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错
  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的

2.4.2 子类中访问父类的成员方法

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、成员方法名字相同

(1)如果两个方法的名字相同,形参数不同,也就是方法的重载(可以不在一个类中),那么看你调用的时候的传参是什么,来调用子类或者父类;

(2)

  • 如果两个方法什么都是一模一样,优先调用子类(对父类的重写),如果在子类中非要访问父类的方法,用super.方法名即可
  •  super只能在子类中访问父类这一层关系,父类的父类不能进行访问。

 

  • 总结 :
  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递的参数选择合适的方法访问,如果没有则报错。

2.5 super关键字 

2.5.1 访问父类的同名成员

在上面的例子中已经说过,如果想访问同名的父类成员,就要使用到super.父类成员名这样的方法

2.5.2 访问父类的同名方法

上面的例子中已经提到,想要在子类中访问父类的同名方法,用super.父类的方法名即可

2.5.3 子类的构造方法

1、若父类显式定义无参或者默认(没有显示定义构造方法)的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用父类的构造方法

2、如果父类构造方法是带有参数的,此时编译器不会再给子类生成默认的构造方法,此时在子类中,第一步就要构造父类super(括号中的参数要与父类保持一致),否则会报错

  

 3、注意

  • 在子类构造方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分 。
  • 父子父子,肯定是先有父再有子,所以在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。

2.5.4 super和this

1、this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用;

2、在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,super和this不能在构造方法中同时存在,否则会报错;

3、构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有。

2.6 继承的初始化

1、我们在代码块blog中,讲到了代码的执行顺序是:静态代码块优先执行并且只会执行一次,其次是实例代码块,最后是构造方法,这里附一下图

 2、那么在继承关系中,我们代码的执行顺序是什么呢?我们请看如下代码:

class Animal {
    public String name;
    public int age;
    static {
        System.out.println("Animal的静态代码块!");
    }
    {
        System.out.println("Animal的实例代码块!");
    }
    public Animal() {
        System.out.println("Animal不带参数的构造方法!");
    }
}
class Dog extends Animal{
    public float weight;

    static {
        System.out.println("Dog的静态代码块!");
    }

    {
        System.out.println("Dog的实例代码块!");
    }

    public Dog() {
        super();
        System.out.println("Dog不带参数的构造方法!");
    }
}

public class Test {

    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println("==================");
        Dog dog2 = new Dog();

    }
}
  • 如果建立了对象的前提下,代码的执行顺序是:
  • 父类的静态代码块(执行一次)->子类的静态代码块(执行一次)->父类的实例代码块->父类的构造方法->子类的实例代码块->子类的构造方法
  •  如果有第二个对象的话,那么静态代码块不会执行,接下来的步骤一样
  • 以上代码的执行结果为:

Animal的静态代码块!
Dog的静态代码块!
Animal的实例代码块!
Animal的不带参数的构造方法!
Dog的实例代码块!
Dog的不带参数的构造方法!
===========================
Animal的实例代码块!
Animal的不带参数的构造方法!
Dog的实例代码块!
Dog的不带参数的构造方法!

2.7 protected 关键字

在类与对象中,我们遗留了一个protected关键字还没有说,因为这里面涉及到了继承,这里面我们详细介绍一下。

序号 范围 private default protected public
1 同一包中的同一类
2 同一包中的不同类
3 不同包中的子类
4 不同包中的非子类

1、在demo1这个包中定义一个类Test,其中有一个protected修饰的成员a。

 2、在同一个包的不同类Test2中,用创建对象的方式就能访问到,不能用super,因为不是父类和子类的关系。

3、在不同包的子类中,通过super.a的方式能访问到,不能通过创建对象的方式访问

 

2.8 继承方式

  • Java中只支持以下几种继承方式

  • 注意:Java中不支持多继承。 
  • 即便Java的语法上可以无限制的继承下去,但是我们并不希望类之间的继承层次太复杂,一般我们不希望出现超过三层的继承关系,如果继承层次太多,就需要考虑对代码进行重构了。
  • 如果想从语法上进行限制继承, 就可以使用 final 关键字。

2.9 final 关键字

final关键可以用来修饰变量、成员方法以及类。

1.、修饰变量或字段,表示常量(即不能修改)

final int A = 10;
A = 20; // 编译出错

2、修饰类:表示此类不能被继承

final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继承

3、String这个类,不能被继承了,因为在底层是用final修饰的,所以不能再继承了。

 4. 修饰方法:表示该方法不能被重写(重写的概念在多态的blog中进行介绍)

3、结语

至此,继承与多态中的继承我们已经全部讲完了,因为知识点过多,多态的部分在下个blog中,欢迎大家持续关注呦

猜你喜欢

转载自blog.csdn.net/weixin_44580000/article/details/124936299