Java之继承

一 、继承相关基础

1. 为什么需要继承

先看以下代码

// 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 + 喵喵喵~~~”); } }

以上代码中
通过观察上述代码会发现,猫和狗的类中存在大量重复,那能否将这些共性抽取呢?面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。

2.继承概念

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用(即通用代码重复使用)例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想达到共用。上述代码中,DogCat都继承了Animal类,其中:Animal类称为父类/基类或超类,DogCat可以称为Animal的 子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态(后面讲)。

3. 继承的语法

Java中如果要表示类之间的继承关系,需要借助extends关键字

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

见以下代码

// 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 + 喵喵喵~~~”); } }

4. 父类成员访问

在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?

4.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 } }

4.2子类和父类成员变量同名

 class Base { int a; char b; int c; } public class Derived extends Base{ int a; // 与父类中成员a同名,且类型相同 int  b; // 与父类中成员b同名,但类型不同 public void method(){ a = 100; // 访问父类继承的a,还是子类自己新增的a

b=’e’;      //char类型只有父类中有,故访问父类的b

c = 102; // 子类没有c,访问的肯定是从父类继承下来的c // d = 103; // 编译失败,因为父类和子类都没有定义成员变量b } }

在子类方法中 或者 通过子类对象访问成员时先根据类型判断访问谁的变量。如果访问的成员变量子类中有,优先访问自己的成员变量。 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。 成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找

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

同变量一样,只是当父类与子类发生方法重写(参数也一样)时访问子类,发生方法重载时根据参数判断访问谁。当父类与子类都没有该方法时,编译器报错。

二、super关键字

1.为什么要使用super关键字

如果子类中存在与父类中相同名字的成员时,这是该怎么办呢?使用super在子类中访问父类的变量和方法。

class Base {

int a;

int b;

public void methodB(){

System.out.println(“Base中的methodB()”);

}

}

public class Derived extends Base{

int a;

int b;

public void methodB() {

super.a = 200;

super.b = 201;

System.out.println(“Derived中的methodB()方法”);

System.out.println(a);

System.out.println(b); //子类的b

System.out.println(super.a);

System.out.println(super.b); //调用父类的b要用super

super.methodB(); //调用父类的方法要写super 

methodB();这里调用自己,形成死递归

}

}

class c {

public static void main(String[] args) {

Derived s=new Derived();

s.a=2;

s.b=5;

s.methodB();

}

}

屏幕输出结果

Derived中的methodB()方法
2
5
200
201
Base中的methodB()

以上代码我们发现一个问题,为什么不直接将主方法写在methodB这个类里面呢,这样因为父子的关系可以直接引用变量及方法,不用去实例化对象了。见以下代码

class Base {
int a;
int b;
public void methodB(){
System.out.println(“Base中的methodB()”);
}
}
public class Derived extends Base{
int a;
int b;
public void methodB() {
public static void main(String[] args) {
super.a = 200;
super.b = 201;
a=2;
b=5;
System.out.println(“Derived中的methodB()方法”);
System.out.println(a);
System.out.println(b); //子类的b
System.out.println(super.a);
System.out.println(super.b); //调用父类的b要用super
super.methodB(); //调用父类的方法要写super 
methodB();这里调用自己,形成死递归
}
}

上面这个代码编译器会报错,这是因为super是不能用在静态代码块中(
因为子类继承到父类的东西也属于对象),而super可以看成父类的对象名(与this看成对象名一样),正好main方法是一个静态方法(属于类,不属于对象),故只能间接访问。总结:1.在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可2.super只能在非静态方法中使用(因为子类继承到父类的东西也属于对象)super的其他用法在后文中介绍。

2.子类构造方法

父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

public class Base { public Base(){ System.out.println(“Base()”); } } public class Derived extends Base{ public Derived(){ // super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(), // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句(类比this在构造方法的作用)System.out.println(“Derived()”); } } public class Test { public static void main(String[] args) { Derived d = new Derived(); } } 结果打印: Base() Derived()

从以上代码可以看出:先执行基类的构造方法,然后执行子类的构造方注意: 1. 若父类显式定义无参的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法 2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用(参数要一致),否则编译失败。 3. 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句4. super(…)只能在子类构造方法中出现一次,并且不能和this同时出现(因为都要第一条)

3.superthis的对比

superthis都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢? 相同点1. 都是Java中的关键字 2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段 3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在 不同点1. this是当前对象的引用,super是父类对象的引用。this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中使用。

class Base { int a;public Base(){ System.out.println(“Base()”); }} public class Derived extends Base{ int a;public Derived(){ System.out.println(“Derived()”); }this.a=1;  //看成Derived.a=1super.a=2;  //看成Base.a=2this();   //调用子类构造方法看成Derived()super()  //调用父类构造方法看成Base()}

//该代码没有主方法,不能运行,只是为了演示方便。

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

4. 再谈初始化

我们还记得之前讲过的代码块吗?我们简单回顾一下几个重要的代码块:实例代码块和静态代码块。在没有继承关系时的执行顺序。1. 静态代码块先执行,并且只执行一次,在类加载阶段执行 2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行那么问题来了,继承关系上的执行顺序是怎样的呢?看下面代码

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:静态代码块执行); } } public class TestDemo4 { public static void main(String[] args) { Student student1 = new Student(张三,19); System.out.println(“===========================”); Student student2 = new Student(“gaobo”,20); } public static void main1(String[] args) { Person person1 = new Person(“bit”,10); System.out.println(“============================”); Person person2 = new Person(“gaobo”,20); } //执行结果Person:静态代码块执行 Student:静态代码块执行 Person:实例代码块执行 Person:构造方法执行 Student:实例代码块执行 Student:构造方法执行 =========================== Person:实例代码块执行 Person:构造方法执行 Student:实例代码块执行 Student:构造方法执行

通过分析执行结果,得出以下结论: 1、父类静态代码块优先于子类静态代码块执行,且是最早执行 2、父类实例代码块和父类构造方法紧接着执行 3、子类的实例代码块和子类构造方法紧接着再执行 4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

如果涉及了变量初始化怎么办呢?

首先说下类的加载(在生成class文件创建类时就执行了)会导致静态成员变量先初始化(父类的静态变量先初始化,子类的静态变量后初始化),而后是静态代码块初始化(父类的静态代码块先执行,子类的静态代码块再执行),因为类的加载只有一次,所以它们只能执行一次。然后就按上面的顺序。注意如果没有静态,则先父类先把构造方法执行完后再初始化实例成员变量。

三、protected关键字

在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。

如果有时我们想要一个变量能在一个包之外去使用,但又不会像public范围那么大,这时就出现了protected这个修饰符。
允许变量还能在另一个包的所属子类中去使用。我们先看在同一个包的情况(同一个包的情况下基本不会报错)

public class Date3 {

    protected int a=30;

}

class Date2 extends Date3{

    public void a(){

        System.out.println(super.a);

        System.out.println(a);

//以下的部分复杂化了,在继承关系中我们可以直接使用a,这里运行没有问题

Date3 date3 = new Date3();

        System.out.println(date3.a);

    }

    public static void main(String[] args) {

        Date2 date2  =new Date2();

        Date3 date3  =new Date3();

        System.out.println(date2.a);

        System.out.println(date3.a);

        date2.a();

    }

}

我们看下不同包的情况

package cot;

public class Date1 {

    protected int a=30;

}

package com;

import cot.Date1;

class Date2 extends Date1 {

    public void a(){

        System.out.println(super.a);

        System.out.println(a);

//以下代码会报错

Date1 date1 = new Date1();

        System.out.println(date1.a);

    }

    public static void main(String[] args) {

        Date2 date2  =new Date2();

        Date1 date1  =new Date1();

        System.out.println(date2.a);

       System.out.println(date1.a); //这里会报错:a 在 cot.Date1 中是 protected 访问控制

       date2.a();

    }

}

观察上面的代码,protected要求在不同包的子类中可以访问,但是上面的报错它都在子类中啊,
这里记住:protected修饰的变量或方法在不同包子类中只能通过super访问,而super不能出现在主方法中,所以只能通过方法间接访问。见以下代码。

package cot;

public class Date1 {

    protected int a=30;

}

package com;

import cot.Date1;

class Date2 extends Date1 {

    public void a(){

        System.out.println(super.a);

    }

    public static void main(String[] args) {

        Date2 date2  =new Date2();

       // Date1 date1  =new Date1();

       // System.out.println(date2.a);

        //System.out.println(date1.a);

        date2.a();

    }

}

//输出结果:30

注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中去了。 

四、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. 修饰方法:表示该方法不能被重写(后面介绍)

五、继承与组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。 继承表示对象之间是is-a(是a)的关系,比如:狗是动物,猫是动物 组合表示对象之间是has-a(有a)的关系,比如:汽车有轮胎、发动机、方向盘、车载系统等
汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。

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

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

版权声明:本文为博主作者:不互关就取关原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/2301_79404878/article/details/138027402

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2024年5月6日
下一篇 2024年5月6日

相关推荐