JavaTutorial

[TOC]

面向对象有三大特征:

其中封装我们之前已经讲过了,利用class,我们将属性和操作封装了起来。

现在我们将要讲解继承

3.继承:

想一想,之前我们已经写好了一个计算三角形面积的类,

假如有一天,我们要写一个计算直角三角形的类,或者要写一个计算圆形、正方形的类,难道我们要重新再一次次把这些类写出来吗?

这时候我们就需要——继承

graph TB; BaseObject --- Triangle --- RightTriangle; BaseObject --- Circle; BaseObject --- Square;

现在让我们来看继承的第一个例子:

类继承的格式如下:

修饰符 class 子类名 extends 父类名 {
    类成员
}

下面来看一个例子:

graph TB; People --- Student;

这个例子的目录结构为:

src
|--People.java
|--Student.java
|--Main.java

其中,People.java内容如下:

public class People {
    //人的一般属性
    private String pId; //身份证号
    private String name;
    private boolean gender;
    private int age;

    public People() {
    }

    public People(String pId, String name, boolean gender, int age) {
        this.pId = pId;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public String getpId() {
        return pId;
    }

    public void setpId(String pId) {
        this.pId = pId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isGender() {
        return gender;
    }

    public void setGender(boolean gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void showGender() {
        if(this.gender) {
            System.out.println("I am a boy");
        }
        else {
            System.out.println("I am a girl");
        }
    }
}

而Student.java的内容如下:

public class Student extends People{
    //学生特有的属性
    private String stuId;
    private String school;
    private String major;   //专业

    public Student(String pId, String name, boolean gender, int age,
                   String stuId, String school, String major) {
        super(pId, name, gender, age); //调用父类的构造器
        this.stuId = stuId;
        this.school = school;
        this.major = major;
    }

    public String getStuId() {
        return stuId;
    }

    public void setStuId(String stuId) {
        this.stuId = stuId;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    public String getMajor() {
        return major;
    }

    public void setMajor(String major) {
        this.major = major;
    }

    public void study() {
        System.out.println("I majored in " + this.major);
    }
}

3.1 继承机制

super引用的还是当前实例,但是属性和方法绑定到父类的定义,有两种用途:

字段继承的危害性

继承机制对变量的处理,会造成严重的混淆。下例中,父类和子类定义了同名但类型不同的变量,如果实例化子类对象,外界会访问到子类的列表,但是对象内有父类定义的和子类定义的两个列表,如果直接继承父类的添加元素方法,就会导致来自父类的列表和来自子类的列表,不同步。

// 省略构造函数等部分
class Father {
    List<String> hobby;
    public void putHobby() {
        System.out.println(hobby);
    }
    public void setHobby(String hobby) {
        hobby.append(hobby)
    }
}
enum Hobbies{
    public static Hobbies getHobby(String str) {/*... */}
    // ...
}
class Child {
    List<Hobbies> hobby;
    @Override public void putHobby() {
        System.out.println(hobby);
    }
    @Override public void setHobby(String hobby) {
        hobby.append(Hobbies.getHobby(hobby))
    }
}

为了避免这一危害,现在的类型结构中,尽可能地会避免继承,而改用接口和JavaBean来实现。

3.2 继承后对象的构造规则

当我们使用new语句来创建子类的对象的时候,在默认情况下,会先执行父类的构造器,然后再执行子类的构造器。

在构造函数的第一条语句,可以使用super关键字来调用父类的构造函数,或者使用this关键字来调用本类构造函数的其他重载。如果第一条语句不是上述两种情况,就会调用父类的无参构造函数。无论如何,子类的构造函数中总有一个会调用父类的构造函数。

class Grandpa {
    public Grandpa() {
        System.out.println("grandpa");
    }
}

class Father extends Grandpa {
    public Father() {
        System.out.println("father");
    }
}

class Son extends Father {
    public Son() {
        System.out.println("son");
    }
}

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

关于类定义方式的说明

这里大家应该已经了解,可以使用两种方式定义类

如果一个类声明的时候使用了public class进行了声明,则类名称必须与文件名称完全一致

在一个*.java文件中,只能有单个public class声明,但是允许有多个class的声明

3.3 继承性规则

继承并不是简单的拥有所有父类的成员。实际上,子类能享用的父类的成员存在严格的限制,权限符限制了它们可以继承哪些东西。

权限 本类 同包 不同包的子类
public 允许 允许 允许
protected 允许 允许 允许
默认 允许 允许 不允许
private 允许 不允许 不允许

public和默认修饰符可以修饰类

protected和private只能修饰成员

3.3.1 测试本类的继承性规则

目录结构

src
|--a
   |---Father
   |---Son
   |---Other

Father的内容:

package a;

class Father {  //默认权限类
    int x;  //默认权限成员
    protected int y;    //保护成员
    public Father(){};
    public Father(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

Son的内容:

package a;

public class Son extends Father{    //继承默认权限类
    public Son(){};
    public Son(int x, int y) {
        this.x = x; //this.x是继承A默认权限的成员x
        this.y = y; //this.y是继承A的保护成员y
    }
    public void print() {
        System.out.println("x = " + x);
        System.out.println("y = " + y);
    }
}

Other的内容:

package a;

public class Other {
    public static void main(String[] args) {
        //可以访问同包的友类
        Father f = new Father(1,2);
        //可以访问同包中某类的默认成员
        System.out.println(f.x);
        //可以访问同包中某类的保护成员
        System.out.println(f.y);
        //同一包中,Son继承Father的默认成员和保护成员
        Son s = new Son(1,2);
        s.print();  //测试继承权限
    }
}

3.3.2 不同包的继承规则的解释

当父类与子类在不同包的时候,

父类的protected成员可以被子类继承,而默认权限的成员不能被子类继承

可以这样理解:只要能能证明儿子S是爸爸F的儿子,即便S是私生子(不再同一个包里),他就有权继承受法律保护的财产(受protected保护的变量)

但是私生子得到的还是不如和父亲一直生活在一起的儿子(在同一个包里面的,还能继承默认权限的财产)

3.3.3 判断某对象是否为某类的子孙

当继承关系越来越复杂时,我们可能需要对某些对象进行“亲子鉴定”。

这时候就需要用到 instanceof

instanceof运算符是Java语言特有的运算符,用来在运行时判定某对象是否为特定类或该特定类子类的一个实例

class A{}

class B extends A{}

public class Other{
    public static void main(String[] args) {
        A a = null;
        B b = null;
        boolean flag;
        flag = a instanceof A;
        System.out.println("a instanceof A: " + flag);
        flag = b instanceof B;
        System.out.println("b instanceof B: " + flag);



        a = new B();
        b = new B();
        flag = a instanceof A;
        System.out.println("a instanceof A: " + flag);
        flag = b instanceof B;
        System.out.println("b instanceof B: " + flag);
    }
}

3.3.4 成员覆盖

一般而言,子类以父类为基础构建类体,增加新的属性和方法。特别的,在子类中声明与父类同名的类成员即可屏蔽父类中的成员。

对于属性的屏蔽称为属性隐藏。

对于方法的屏蔽称为方法覆盖或者方法重写(Override)

@Override 注解

@Override是注解,表示该方法必须覆盖父类的同签名方法。这个注解可以省略,有如下好处:

  1. 可以当注释用,方便阅读;
  2. 编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错

3.3.5 final

由于子类中有方法覆盖,假如我们不想让方法被覆盖应该怎么办呢?这时候就需要用到final

final在基础语法的时候我们讲到,它是常量的写法

而在面向对象中,一般会把静态常量定义为public static final

final修饰的方法称为最终方法。不允许子类重写父类的final方法。

使用final有两个优点:

此外,类的private方法被隐式地指定为是final

值得一提的是,如果一个类被final修饰,它不能被继承。