0
点赞
收藏
分享

微信扫一扫

JavaSE基础(八)---继承的魅力

书呆鱼 2022-04-13 阅读 54
java后端

目录

一、继承

1、为什么要继承?

2、继承的含义

3、继承的语法 

4、访问父类成员 

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

4.2、子类中访问父类成员方法 

5、super关键字* 

二、继承中该注意的构造方法 *

1、子类构造方法(super调用)

 2、super和this之间的渊源

三、继承中代码块的执行顺序

四、继承方式

五、final关键字 

六、继承与组合


一、继承

1、为什么要继承?

在之前我们了解到,在Java中我们可以通过类来对现实世界中一些抽象的、具有一定属性和行为的实体进行描述。

类经过实例化后产生的对象,可以用来表示现实中的一些实体。

但现实世界错综复杂,一些定义的类之间可能会存在相同的属性或行为,那在设计程序时就需要考虑。

比如:猫、狗都作为动物(共性:都是动物),具有相同的特性,比如毛色、年龄、性别等等。

用Java语言来进行描述,就会设计出:

//定义Cat类
class Cat{
    public String name;
    public String hair;
    public int age;

    public void run(){
        System.out.println(this.name+"正在跑");
    }

    public void mew(){
        System.out.println(this.name+"喵喵叫");
    }
}
//定义Dog类
class Dog{
    public String name;
    public String hair;
    public int age;

    public void run(){
        System.out.println(this.name+"正在跑");
    }

    public void wan(){
        System.out.println(this.name+"汪汪叫");
    }
}

 从上面两个定义类,可以看出狗类和猫类都具有相同的属性,方法,只是存在些许的差异(叫声)。

🤔那我们是否可以把它们之间的共性抽取出来,不用再重复写相同的属性呢❓


2、继承的含义

面相对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用

【继承机制】:

        继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性 的基础上进行扩展,增加新功能,这样产生新的类,称派生类(也可称为子类)。

        继承的原有类,称为父类(又可称为基类、超类)

        继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。

        继承主要解决的问题是:共性的抽取,实现代码复用

=========================================================================

例如:狗和猫都是动物的一种,那么我们可以把猫和狗作为动物的特性抽取出来,然后将其继承给猫和狗类。

上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后序讲)。


3、继承的语法 

以上面的狗和猫为代码示例:

//定义父类
class Animal{
    public String name;
    public String hair;
    public int age;
    public void run(){
        System.out.println(this.name+"正在跑");
    }

}
//定义Cat这个子类
class Cat extends Animal{

    public void mew(){
        System.out.println(this.name+"喵喵叫");
    }
}
//定义Dog这个子类
class Dog extends Animal{

    public void wan(){
        System.out.println(this.name+"汪汪叫");
    }
}

public class TestDemo3 {
    public static void main(String[] args) {
        //实例化猫和狗
        Cat cat=new Cat();;
        Dog dog=new Dog();

        // dog类和cat类中并没有定义任何成员变量
        // name和hair属性肯定是从父类Animal中继承下来的
        System.out.println(dog.name);
        System.out.println(cat.hair);

        //run()方法也是从父类继承下来的
        dog.run();
        cat.mew();
    }
}

 运行结果:

由以上的代码可以得知,子类继承了父类的属性和方法,通过实例化狗和猫,可以访问父类(Animal)内的属性和方法,当然也可以访问自身的。                


4、访问父类成员 

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

(1)在子类继承了父类后,其子类内部会有父类的成员变量和方法,在子类中可以直接访问父类里的继承下来的成员变量和成员方法.

class Animal{
    public String name;
    public String hair;
    public int age;
}

class Dog extends Animal{
    public String sex;
    public void set(){
//访问子类从父类继承下来的name、hair、age
        name="小黄";        
        hair="yellow";
        age=3;
//访问子类自身的sex
        sex="boy";
    }
}

(2)如果子类和父类中都有同名的成员变量或成员方法,那在子类中访问同名的成员时,会优先访问子类自身的成员.


(3)如果父类里的成员变量名a是子类里没有的,那当我们访问a时,自然是访问父类的;那如果父类和子类里都没定义过一个成员变量b时,你又去访问这个凭空出现的b,自然会编译错误!

(4)要是当子类和父类存在同名的成员变量时,不想访问子类的成员变量,反而要访问父类的同名成员变量,就得用到super关键字(后文会介绍)。 


4.2、子类中访问父类成员方法 

在子类中访问父类的成员方法跟前面的子类访问父类成员变量差不太多。

访问不同名时,则访问对应的方法名。

访问同名的方法时,则遵循就近原则,访问子类的。

而要想访问父类的,那么父类与子类同名的方法之间得构成“重载”,再根据所调用的参数列表不同,去进行方法的调用。


(1)访问同名的成员方法(重载*)


 (2)访问同名的成员方法

【注意】:

        通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。 


(3)访问不同名的成员方法

此处跟前面的访问成员变量一致,简单带过。

通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。


5、super关键字* 

子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?

直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。

代码示例:

class A{
    public int a=1;
    public int b=2;
    public int c=3;
    public void func(){     // 与父类中func()构成重写(即原型一致)
        System.out.println("调用父类的成员方法");
    }
    public void add(){      // 与父类中methodA()构成重载
        System.out.println("add!");
    }
}
class B extends A{
   public int a=4;          // 与父类中成员变量同名且类型相同
   public int b=5;

    public void func(){
        System.out.println("调用子类的成员方法");
   }

   public void add(int a,int b){
       System.out.println("add="+(a+b));
   }

   public void show(){
       // 访问父类的成员变量时,需要借助super关键字
       // super是获取到子类对象中从父类继承下来的部分
       System.out.println("访问父类的成员变量a:"+super.a);

       // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
       add();
       add(3,5);

       //如果要在子类中访问与父类构成重写的方法(返回类型,方法名,参数列表都一样的方法),可以用super关键字访问
       func();//访问的是子类的方法,就近原则
       super.func();//访问父类的方法
   }
}
public class TestDemo4 {
    public static void main(String[] args) {
        B b=new B();
        b.show();
    }
}

运行结果:

在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。


二、继承中该注意的构造方法 *

1、子类构造方法(super调用)

众所周知,作为类,无论是子类还是父类,怎么说都得有构造方法吧!

那我们在实例化子类的时候,是只调用构造子类方法呢?还是子类和父类都有调用构造方法呢?

熟话说,“先有父,后有子”。在我们实例化子类对象时,会调用子类的构造方法,而其中会先调用完父类的构造方法,才会完成子类构造方法的调用。

代码示例:

class Base{
    public String namem;
    public int age;

    public Base() {
        System.out.println("父类的构造方法");
    }
}
class Derived extends Base{
    public Derived() {
        super();
//super()会调用子类的构造方法
//只要父类构造方法是无参的或没有写(编译器自动默认的),super()其实可以不用写
//其生命周期只存在于本次实例化当中,只出现一次
//而且super()必须得在构造方法的第一条语句,毕竟得先完成父类构造方法的调用
        System.out.println("子类的构造方法");
    }
}
public class TestDemo5 {
    public static void main(String[] args) {
        Derived derived=new Derived();
    }
}

//运行结果:
//父类的构造方法
//子类的构造方法

 从上面代码可知,在子类的构造方法内,是通过super关键字来调用父类的构造方法

要是父类内没有写任何有关父类的构造方法或无参的构造方法,子类构造方法内也是会先执行父类的构造方法,再执行子类自己的构造方法。毕竟,子类对象中成员是有两部分组成的,一部分是:父类继承下来的,另一部分是:子类新增加的部分

即使我们没有写super关键字调用编译器也是会默认提供一个,我们可以认为super()被隐式定义。这也是为什么我们刚开始接触继承时,即使没有定义子类或父类的构造方法,程序依旧可以跑起来的原因,因为编译器默认为我们提供了。

父子父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。


(1)调用父类内带参数的构造方法。

我们在写构造方法时,通过IDEA自带的功能可以很方便的直接进行定义。

(1)按住键盘上Alt+Insert,会弹出Generate!

 

 (2)按住Shift键,选中想初始化的成员,再点击OK即可。

下面会着重介绍this和super之间的区别!


 2、super和this之间的渊源

super和this都可以在成员方法中用来访问:成员变量和调用其他的成员方法,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

this和super之间的相同点:

        1、它们都只能在非静态方法中使用,(静态方法不是依赖对象的)用来访问非静态成员方法和字段。

        2、在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在super()和this()二者都是构造方法内的第一行,彼此发生冲突。


this和super之间的不同点:

        1、this是当前对象的引用(当前对象即调用实例方法的对象),是依赖于对象。而super是子类对象中从父类继承下来那一部分成员的引用

    

         2、在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。

        3、. this是非静态成员方法的一个隐藏参数,super不是隐藏的参数。

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


三、继承中代码块的执行顺序

在前面,我们学过了有关不同代码块之间的执行顺序,静态代码块优先于实例代码块,而实例代码块又优先于构造方法。

简单回顾以下它们之间的执行顺序。

class Student{
    public String name;
    public int age;
    public static String sex;
 
    {
       this.name="李华";
       this.age=17;
        System.out.println("实例化代码块");
    }
 
    static {
        sex="boy";
        System.out.println("静态代码块");
    }
    
    public Student(String name,int age){
        this.name=name;
        this.age=age;
        System.out.println("调用构造方法");
    }
 
    public void show(){
        System.out.println(this.name+" is "+sex+" 今年"+this.age);
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        Student student=new Student("小明",19);
        student.show();
    }
}


而继承中代码块执行顺序相比于之前代码块执行顺序有些许差异。

class Base{
    public String name;
    public int age;
    static {
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("=============");
        System.out.println("父类实例代码块");
    }
    public Base() {
        System.out.println("父类构造方法");
    }
}
class Derived extends Base{
    static {
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("=============");
        System.out.println("子类实例代码块");
    }
    public Derived() {
        System.out.println("子类构造方法");
    }
}
public class TestDemo5 {
    public static void main(String[] args) {
        Derived derived=new Derived();
    }
}


继承执行顺序还需注意的是:

        静态代码块的执行只会进行一次,当我们实例化第二个子类对象时,父类和子类的静态代码块不会再执行。

 通过分析执行结果,得出以下结论:

1、父类静态代码块优先于子类静态代码块执行,且是最早执行

2、父类实例代码块和父类构造方法紧接着执行

3、子类的实例代码块和子类构造方法紧接着再执行

4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行


四、继承方式

在我们的平常的生活当中,事物的方方面面基本都是由许多种类构成,每一种种类可以划分多中对象出来。而每一个划分出来的对象又可以再进行划分,往往复复。事物之间的关系错综复杂。

比如动物:

 而作为某一动物的品种,又可以将他们划分出毛色,斑纹等等特征。

但在Java中只支持以下几种继承方式:

1、单继承

class A{

}

class B extends A{

}

2、多层继承

class A{

}
class B extends A{

}
class C extends B{
//B是C的父类,A是B的父类,可以理解成A是C的爷类
//C也有继承A类的成员
}

3、不同类继同一个类

class A{

}
class B extends A{

}
class C extends A{

}

4、多继承(一个类不可以继承多个类)

//不能这么继承
class A{

}
class B{

}
class C extends A,B{

}

【注意】:不支持多继承,一次只能继承一个,(后续学到接口,可以利用接口来实现多继承)。


五、final关键字 

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

1、被final修饰的变量或成员变量,会表示成为不变的量(常量)不能被修改.

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

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

final class Animal {
...
}
class Bird extends Animal {
...
}
// 编译会出错,无法进行继承

3、 修饰方法:表示该方法不能被重写(后序介绍) 


六、继承与组合

以汽车为例,汽车无非就是由许多零件构成的。

汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的 。

 

// 轮胎类
class Tire{
// ...
}
// 发动机类
class Engine{
// ...
}
// 车载系统类
class VehicleSystem{
// ...
}
class Car{
Tire tire=new Tire(); // 可以复用轮胎中的属性和方法
Engine engine=new Engine(); // 可以复用发动机中的属性和方法
VehicleSystem vs=new VehicleSystem(); // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car{
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用 组合。


举报

相关推荐

0 条评论