Java子类与继承完整解析

发布于 2021-3-29 17:39
浏览
0收藏

1 子类与父类
继承是一种由已有的类创建新类的机制。利用继承,我们可以先创建一个共有属性的一般类,根据该一般类再创建具有特殊属性的新类,新类继承一般类的状态和行为,并根据需要增加它自己的新的状态和行为。由继承而得到的类称为子类,被继承的类称为父类(超类) 。
Java不支持多重继承(子类只能有一个父类)
习惯地称子类与父类是"is-a”关系。

1.1 子类
使用关键字extends来定义一个类的子类,格式如下:
class 子类名 extends 父类名{

}
例如:
class Student extends People {

}
说明:把Student类定义为People类的子类、People类是Student
类的父类

1.2 类的树形结构
Java的类按继承关系形成树形结构这个树形结构中,根节点是Object类( Object是java.lang包中的类),即Object是所有类的祖先类。除了Object类,每个类都有且仅有一个父类,一个类可以有多个或零个子类。如果一个类(除了Object类)的声明中没有使用extends关键字,这个类被系统默认为是
Object的子类,即类声明“class A”与“class A extends Object"是等同的。

2 子类的继承性
类可以有两种重要的成员:成员变量和方法。子类的成员中有一部分是子类自己声明定义的,另一部分是从它的父类继承的。所谓子类继承父类的成员变量就是把继承来的变量作为自己的一个成员变量,就好象它们是在子类中直接声明一样,可以被子类中自己定义的任何实例方法操作。所谓子类继承父类的方法就是把继承来的方法作为子类中的一个方法,就好象它们是在子类中直接定义了一样,可以被子类中自己定义的任何实例方法调用。

//子类的继承
class Father {
    float weight, height;
    String head;

    void speak(String s) {
        System.out.println(s);
    }
}

class Son extends Father {
    String hand, foot;
}

public class Test {
    public static void main(String[] args) {
        Son s = new Son();
    }
}

2.1 子类和父类在同一包中的继承性
如果子类和父类在同一个包中,那么,子类自然地继承了其父类中不是private的成员变量作为自己的成员变量,并且也自然地继承了父类中不是private的方法作为自己的方法,继承的成员变量或方法的访问权限保持不变。
下面的例子2.1中有4个类: People, Student.java,UniverStudent.java和Example2_1,这些类都没有包名,其中UniverStudent类是Student的子类,Student 是People的子类。程序运行效果

People.java

public class People {
    int age,leg = 2,hand = 2;
    protected void showPeopleMess() {
        System.out.printf("%d岁,%d只脚,%d只手\t",age,leg,hand);
    }
}

 

Student.java

public class Student extends People {
    int number;
    void tellNumber() {
        System.out.printf("学号:%d\t",number);
    }
    int add(int x,int y) {
        return x+y;
    }
}

 

UniverStudent.java

public class UniverStudent extends Student {
    int multi(int x,int y) {
        return x*y;
    }
}

 

Example2_1.java

public class Example2_1 {
    public static void main(String[] args) {
        Student zhang = new Student();
        zhang.age = 17;           //访问继承的成员变量
        zhang.number=100101;
        zhang.showPeopleMess();  //调用继承的方法
        zhang.tellNumber();
        int x=9,y=29;
        System.out.print("会做加法:");
        int result=zhang.add(x,y);
        System.out.printf("%d+%d=%d\n",x,y,result);
        UniverStudent geng = new UniverStudent();
        geng.age = 21;          //访问继承的成员变量
        geng.number=6609;
        geng.showPeopleMess();  //调用继承的方法
        geng.tellNumber();        //调用继承的方法
        System.out.print("会做加法:");
        result=geng.add(x,y);      //调用继承的方法
        System.out.printf("%d+%d=%d\t",x,y,result);
        System.out.print("会做乘法:");
        result=geng.multi(x,y);
        System.out.printf("%d×%d=%d\n",x,y,result);
    }
}


2.2 子类和父类不在同一包中的继承性
如果子类与父类不在同一个包中,则子类只会继承父类public、protected的变量和方法,继承的变量和方法的访问权限保持不变。

2.3 继承关系(Generalization)的UML图
如果一个类是另一个类的子类,那么UML通过使用一个实线连接两个类的UML图来表示二者之间的继承关系,实线的起始端是子类的UML图,终点端是父类的UML图,但终点端使用一个空心的三角形表示实线的结束。

Java子类与继承完整解析-开源基础软件社区
3 子类与对象
3.1 子类对象的特点
子类创建对象时,子类的构造方法总是先调用父类的某个构造方法,完成父类部分的创建;然后再调用子类自己的构造方法,完成子类部分的创建。如果子类的构造方法没有明显地指明使用父类的哪个构造方法,子类就调用父类的不带参数的构造方法。
子类在创建一个子类对象时,不仅子类中声明的成员变量被分配了内存,而且父类的所有的成员变量也都分配了内存空间,但子类只能操作继承的那部分成员变量。
子类可以通过继承的方法来操作子类未继承的变量和方法

Java子类与继承完整解析-开源基础软件社区
例子3.1中,子类ChinaPeople的对象调用继承的方法操作未被子类继承却分配了内存空间的变量

class People {
    private int averHeight = 166;
    public int getAverHeight() {
        return averHeight;
    }
}
class ChinaPeople extends People {
    int height;
    public void setHeight(int h) {
        //height = h+averHeight; // 非法,子类没有继承averHeight
        height = h;
    }
    public int getHeight() {
        return height;
    }
}
public class Example5_2 {
    public static void main(String[] args) {
        ChinaPeople zhangSan = new ChinaPeople();
        System.out.println("子类对象未继承的averageHeight的值是:"+zhangSan.getAverHeight());
        zhangSan.setHeight(178);
        System.out.println("子类对象的实例变量height的值是:"+zhangSan.getHeight());
    }
}

 

Java子类与继承完整解析-开源基础软件社区


3.2 关于instanceof运算符
instanceof运算符是Java独有的双目运算符,其左面的操作元是对象,右面的操作元是类,当左面的操作元是右面的类或其子类创建的对象时,instanceof运算的结果是true,否则是false.

class Test {
    String s;
    Test() {
        stu s = new String();
        if (s instanceof String) {
            System.out.println("YES");
        }
    }
}


4 成员变量的隐藏和方法重写
4.1 成员变量的隐藏
➢对于子类可以从父类继承的成员变量,只要子类中声明的成员变量和父类中的成员变量同名时,子类就隐藏了继承的成员变量。
➢在子类中要操作这个与父类同名的成员变量时,子类操作的是子类重新声明的这个成员变量。而不是被隐藏掉的。
➢子类对象仍然可以调用从父类继承的方法操作被子类隐藏的成员变量,也就是说,子类继承的方法操作的成员变量一定是被子类继承或者隐藏的成员变量。

例子4.1(Example4_1.java)中, Goods类有一 个 名字为weight的double型成员变量,本来子类CheapGoods可以继承这个成员变量,但是子类CheapGoods又重新声明了一个int型的名字为weight的成员变量.
Goods.java

public class Goods {
    public double weight;
    public void oldSetWeight(double w) {
        weight=w;
        System.out.println("double型的weight="+weight);
    }
    public double oldGetPrice() {
        double price = weight*10;
        return price;
    }
}


CheapGoods.java

public class CheapGoods extends Goods {
    public int weight;
    public void newSetWeight(int w) {
        weight=w;
        System.out.println("int型的weight="+weight);
    }
    public double newGetPrice() {
        double price = weight*10;
        return price;
    }
}


Example4_1.java

public class Example4_1 {
    public static void main(String[] args) {
        CheapGoods cheapGoods=new CheapGoods();
        //cheapGoods.weight=198.98; 是非法的,因为子类对象的weight已经是int型
        cheapGoods.newSetWeight(198);
        System.out.println("对象cheapGoods的weight的值是:"+cheapGoods.weight);
        System.out.println("cheapGoods用子类新增的优惠方法计算价格:"+
                cheapGoods.newGetPrice());
        cheapGoods.oldSetWeight(198.987); //子类对象调用继承的方法操作隐藏的double型变量weight
        System.out.println("cheapGoods使用继承的方法(无优惠)计算价格:"+
                cheapGoods.oldGetPrice());
    }
}


注意:子类继承的方法只能操作子类继承和隐藏的成员变量,子类新定义的方法可以操作子类继承和子类新声明的成员变量,但无法操作子类隐藏的变量(需要使用super关键字操作子类隐藏的成员变量)

4.2 方法重写
同样,子类通过重写可以隐藏已继承的实例方法。
1.重写的语法规则
如果子类继承了父类的实例方法,那么子类就有权利重写这个方法。
方法重写是指:子类中定义一个方法,这个方法的类型和父类的方法的类型一致或是父类方法的类型的子类型,且这个方法的名字、参数个数、参数的类型和父类的方法完全相同.
2.重写的目的
子类通过方法的重写可以隐藏继承的方法,子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。
3.重写后方法的调用
子类创建的一个对象,如果子类重写了父类的方法,则运
行时系统调用的是子类重写的方法;
子类创建的一个对象,如果子类未重写父类的方法,则运
行时系统调用的是子类继承的方法;
4.重写的注意事项
重写父类的方法时,不允许降低方法的访问权限,但可以提高访问权限(访问限制修饰符按访问权限从高到低的排列顺序是:public、protected、 友好的、private。 )

在下面的例子4_2(Example4_2. java)中,ImportantUniversity是University类的子类,子类重写了父类的enterRule0方法

University.java

public class University {
    void enterRule(double math,double english,double chinese) {
        double total=math+english+chinese;
        if(total>=180)
            System.out.println("考分"+total+"达到大学最低录取线");
        else
            System.out.println("考分"+total+"未达到大学最低录取线");
    }
}


ImportantUniversity.java

public class ImportantUniversity extends University{
    void enterRule(double math,double english,double chinese) {
        double total=math+english+chinese;
        if(total>=220)
            System.out.println("考分"+total+"达到重点大学录取线");
        else
            System.out.println("考分"+total+"未达到重点大学录取线");
    }
}


Example4_2.java

public class Example4_2 {
    public static void main(String args[]) {
        double math=64,english=76.5,chinese=66;
        ImportantUniversity univer = new  ImportantUniversity();
        univer.enterRule(math,english,chinese); //调用重写的方法
        math=89;
        english=80;
        chinese=86;
        univer = new  ImportantUniversity();
        univer.enterRule(math,english,chinese); //调用重写的方法
    }
}

 

5 super关键字
5.1 用super操作被隐藏的成员变量和方法
➢子类可以隐藏从父类继承的成员变量和方法,如果在子类中想使用被子类隐藏的成员变量或方法就可以使用关键字super.比如super.x、super.play() 就是访问和调用被子类隐藏的成员变量x和方法play()
例子5.1中,子类使用super访问和调用被子类隐藏的成员变量和方法。

class Sum {
    int n;
    float f() {
        float sum=0;
        for(int i=1;i<=n;i++)
            sum=sum+i;
        return sum;
    }
}
class Average extends Sum {
    int n;
    float f() {
        float c;
        super.n=n;
        c=super.f();
        return c/n;
    }
    float g() {
        float c;
        c=super.f();
        return c/2;
    }
}
public class Example5_1 {
    public static void main(String args[]) {
        Average aver=new Average();
        aver.n=100;
        float resultOne=aver.f();//代码1
        float resultTwo=aver.g();//代码2
        System.out.println("resultOne="+resultOne);
        System.out.println("resultTwo="+resultTwo);
    }
}

 

上述例子中如果交换代码1和代码2,则输出为:
resultOne=50.5
resultTwo=0.0

5.2 用super调用父类的构造方法
子类不继承父类的构造方法,因此,子类如果想使用父类的构造方法,必须在子类的构造方法中使用,并且必须使用关键字super来表示,而且super必须是子类构造方法中的头一条语句。
注:
创建子类对象时,子类总是按层次结构从上到下的顺序调用所有超类的构造函数,默认调用父类中没有参数的构造方法。如果父类没有不带参数的构造方法,则在子类的构造方法中必须明确的告诉调用父类的某个带参数的构造方法。

例子5.2中,UniverStudent是Student的子类,UniverStudent子类
在构造方法中使用了super关键字

class Student {
    int number;String name;
    Student() {
    }
    Student(int number,String name) {
        this.number=number;
        this.name=name;
        System.out.println("我的名字是:"+name+ "学号是:"+number);
    }
}
class UniverStudent extends Student {
    boolean 婚否;
    UniverStudent(int number,String name,boolean b) {
        super(number,name);
        婚否=b;
        System.out.println("婚否="+婚否);
    }
}
public class Example5_1 {
    public static void main(String args[]) {
        UniverStudent zhang=new UniverStudent(9901,"何晓林",false);
    }
}

 

Java子类与继承完整解析-开源基础软件社区


6 final关键字
final关键字可以修饰类、成员变量、方法中的局部变量和方法。
可以使用final将类声明为final类。final类不能 被继承,即不能有子类。
如:
final class A {
}
如果用final修饰父类中的一个方法,那么这个方法不允许子类重写。
一如果成员变量或局部变量被修饰为final的,就是常量。

**例6 **

class A {
  final double PI=3.1415926;// PI是常量
  public double getArea(final double r) {
     return PI*r*r;
  }
  public final void speak() {
     System.out.println("您好,How's everything here ?");
  } 
}
public class Example6 {
   public static void main(String args[]) {
      A a=new A();
      System.out.println("面积:"+a.getArea(100));
      a.speak();     
   }
}


7 对象的上转型对象
假设,A类是B类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,称对象a是对象b的上转型对象,比如:

A a;
a = new B();
或者
A a;
B b = new B();
a = b;

Java子类与继承完整解析-开源基础软件社区上转型对象的使用
1.上转型对象不能操作子类新增的成员变量;不能调用子类新增的方法。
2.上转型对象可以访问子类继承或隐藏的成员变量,也可以调用子类继承的方法或子类重写的实例方法。
3.如果子类重写了父类的某个实例方法后,当用上转型对象调用这个实例方法时一定是调用了子类重写的实例方法。

**例7 **

class  类人猿 {
    void crySpeak(String s) {
        System.out.println(s);
    }
}
class People extends 类人猿 {
    void computer(int a,int b) {
        int c=a*b;
        System.out.println(c);
    }
    void crySpeak(String s) {
        System.out.println("***"+s+"***");
    }
}
public class Example7 {
    public static void main(String args[]) {
        类人猿 monkey;
        People geng = new People();
        monkey = geng ; //monkey是People对象geng的上转型对象
        monkey.crySpeak("I love this game");//等同于geng.crySpeak("I love this game");
        People people=(People)monkey; //把上转型对象强制转化为子类的对象
        people.computer(10,10);
    }
}

 

Java子类与继承完整解析-开源基础软件社区

注意:monkey.crySpeak("I love this game");
得到的是:***I love this game***
而不是:I love this game,因为monkey调用的是子类的重写方法crySpeak

monkey.computer(10,10);是错误的,因为computer方法是子类新增的方法


1、不要把父类创建的对象和子类对象的上转型对象混淆;
2、可以将对象的上转型对象再强制转换到一个子类对象,这时,该子类对象又具备了子类所有的属性和功能;
3、不可以将父类创建的对象的引用赋值于子类声明的对象;
4、如果子类重写了父类的静态方法,那么子类对象的.上转型对象不能调用子类重写的静态方法,只能调用父类的静态方法。

 

8 继承与多态
多态性就是指父类的某个方法被其子类重写,可以各自产生自己的功能行为。

class  动物 {
    void cry() {
    }
}
class 狗 extends 动物 {
    void cry() {
        System.out.println("汪汪.....");
    }
}
class 猫 extends 动物  {
    void cry() {
        System.out.println("喵喵.....");
    }
}
public class Example5_1 {
    public static void main(String args[]) {
        动物 animal;
        animal = new 狗();
        animal.cry();
        animal=new 猫();
        animal.cry();
    }
}


9 abstract类和abstract方法
用关键字abstract修饰的类称为abstract类(抽象类)。
例如:
abstract class A {
}
用关键字abstract修饰的方法称为abstract方法(抽象方法)
例如:
abstract int min(int x,int y);

abstract类有如下特点
1、和普通的类相比,abstract类里可以有abstract方法。也可以没有。对于abstract方法, 只允许声明,不允许实现,而且不允许使用final修饰abstract方法。
2、对于abstract类,不能使用new运算符创建该类的对象,只能产生其子类,由子类创建对象。
3、如果一个非abstract类是abstract类的子类,它必须具体实现父类的所有的abstract方法。

理解的关键点是:
(1)抽象类可以抽象出重要的行为标准,该行为标准用抽象方法来表示。即抽象类封装了子类必需要有的行为标准。
(2)抽象类声明的对象可以成为其子类的对象的上转型对象,调用子类重写的方法,即体现子类根据抽象类里的行为标准给出的具体行为。

开发者可以把主要精力放在一个应用中需要那些行为标准(不用关心行为的细节),不仅节省时间,而且非常有利于设计出易维护、易扩展的程序。抽象类中的抽象方法,可以由子类去实现,即行为标准的实现由子类完成。

例9使用了abstract类封装了男孩对女朋友的行为要求,即封装了他要找的任何具体女朋友都应该具有的行为。

abstract class GirlFriend {//抽象类,封装了两个行为标准
    abstract void speak();
    abstract void cooking();
}
class ChinaGirlFriend extends GirlFriend {
    void speak(){
        System.out.println("你好");
    }
    void cooking(){
        System.out.println("水煮鱼");
    }
}
class AmericanGirlFriend extends GirlFriend {
    void speak(){
        System.out.println("hello");
    }
    void cooking(){
        System.out.println("roast beef");
    }
}
class Boy {
    GirlFriend friend;
    void setGirlfriend(GirlFriend f){
        friend = f;
    }
    void showGirlFriend() {
        friend.speak();
        friend.cooking();
    }
}
public class Example9{
    public static void main(String[] args) {
        GirlFriend girl = new ChinaGirlFriend(); //girl是上转型对象
        Boy boy = new Boy();
        boy.setGirlfriend(girl);
        boy.showGirlFriend();
        girl = new AmericanGirlFriend(); //girl是上转型对象
        boy.setGirlfriend(girl);
        boy.showGirlFriend();
    }
}


10 面向抽象编程
在设计一个程序时,可以先声明一个abstract类,通过在类中声明若干个abstract方法,表明这些方法在整个系统设计中的重要性,方法体的内容细节由它的非abstract子类去完成。.然后利用多态实现编程。使用多态进行程序设计的核心技术是使用方法重写和上转型对象,即将abstract类声明对象作为其子类的上转型对象,那么这个上转型对象就可以调用子类重写的方法。
所谓面向抽象编程,是指当设计某种重要的类时,不让该类面向具体的类,而是面向抽象类,即所设计类中的重要数据是抽象类声明的对象,而不是具体类声明的对象。

例题:
Pillar类就是面向抽象类Geometry, Cirle和Rectangle 都是Geometry的子类. Application.java 可以用Pillar类创建出具有矩形底或圆形底的柱体了。

首先编写一个抽象类Geometry,该抽象类中定义了一个抽象的getArea()方法,Geometry类如下:

Geometry.java

public abstract class Geometry {    //抽象类
    public abstract double getArea();
}


上述抽象类将所有计算面积的算法抽象为一个标识:getArea(),即抽象方法,不再考虑算法细节。
现在Pillar类的设计者可以面向Geometry类编写代码,即Pillar类应该把Geometry对象
作为自己的成员,该成员可以调用Geometry的子类重写的getArea()方法。这样一来, Pillar类就可以将计算底面积的任务指派给Geometry类的子类的实例(用户的各种需求将由不同的子类去负责)。

以下Pillar类的设计不再依赖具体类,而是面向Geometry类,即Pillar类中的bottom是
用抽象类Geometry声明的对象,而不是具体类声明的对象。重新设计的Pillar类的代码如下:
Pillar.java

public class Pillar {
    Geometry bottom;    //bottom是抽象类Geometry声明的变量
    double height;

    Pillar(Geometry bottom, double height) {
        this.bottom = bottom;
        this.height = height;
    }

    public double gerVolume() {
        if (bottom == null) {
            System.out.println("没有底,无法计算体积");
            return -1;
        }
        return bottom.getArea() * height; //bottom可以调用子类重写的getArea方法
    }
}


下列Circle和Rectangle类都是Geometry的子类,二者都必须重写Geometry类的getArea()方法来计算各自的面积。

Circle.java

public class Circle extends Geometry { //  圆类
    double r;

    Circle(double r) {
        this.r = r;
    }

    public double getArea() {
        return (3.14 * r * r);
    }
}


Rectangle.java

public class Rectangle extends Geometry {
    double a, b;

    Rectangle(double a, double b) {
        this.a = a;
        this.b = b;
    }

    public double getArea() {
        return a * b;
    }
}


注意到,当增加了Circle和Rectangle类后,我们不必修改Pillar类的代码,现在,我们就可以用Pillar类创建出具有矩形或者圆底的柱体了,如下列Application.java所示:

public class Application {  //主类
    public static void main(String[] args) {
        Pillar pillar;
        Geometry bottom = null;
        pillar = new Pillar(bottom, 100);//没有底的柱体
        System.out.println("体积" + pillar.gerVolume());

        bottom = new Rectangle(12, 22);
        pillar = new Pillar(bottom, 58);//pillar是具有矩形底的柱体
        System.out.println("体积" + pillar.gerVolume());
        
        bottom = new Circle(10);
        pillar = new Pillar(bottom, 58);//pillar是具有圆形底的柱体
        System.out.println("体积" + pillar.gerVolume());
    }
}


通过面向抽象来设计Pillar类,使得该Pillar类不再依赖具体类,因此每当系统增加新的
Geometry的子类时,例如增加一个 Triangle子类,那么我们不需要修改Pillar类的任何代码,就可以使用Pillar 创建出具有三角形底的柱体。

11 开-闭原则
所谓“开闭原则”(Open-Closed Principle) 就是让设计的系统应当对扩展开放,对修改关闭。
在设计系统时,应当首先考虑到用户需求的变化,将应对用户变化的部分设计为对扩展开放,而设计的核心部分是经过精心考虑之后确定下来的基本结构,这部分应当是对修改关闭的,即不能因为用户的需求变化而再发生变化,因为这部分不是用来应对需求变化的。如果系统的设计遵守了“开-闭原则”,那么这个系统一定是易维护的,因为在系统中增加新的模块时,不必去修改系统中的核心模块。
————————————————
版权声明:本文为博主「Codeplus.」的原创文章

收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐