面向对象一文了解Java继承(超详细超有趣)

Posted 过气老学长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象一文了解Java继承(超详细超有趣)相关的知识,希望对你有一定的参考价值。

引子

在具体讲解之前,先看下面两个程序(查看注释可以更加快速地浏览程序)

public class Person {
    private String name;
    private int age;
    private String sex;
	//构造器
	public Person(){}
	public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex; 
	}
	//name的访问器
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {

        return name;
    }
	//age的访问器
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return age;
    }
	//sex的访问器
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getSex() {
        return sex;
    }
}
public class Student{
    private String name;
    private int age;
    private String sex;
    private double chinese;
    private double math;
    private double english;
	//构造器
	public Student(){}
	public Student(String name, int age, String sex, double chinese, double math, double english) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }
	//name的访问器
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
	//age的访问器
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
	//sex的访问器
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
	//Chinese的访问器
    public void setChinese(double chinese) {
        this.chinese = chinese;
    }
    public double getChinese() {
        return chinese;
    }
	//Math的访问器
    public void setMath(double math) {
        this.math = math;
    }
    public double getMath() {
        return math;
    }
	//English的访问器
    public void setEnglish(double english) {
        this.english = english;
    }
    public double getEnglish() {
        return english;
    }
}

看完之后,有没有觉得他们非常的相似?
其实,从封装的角度讲,这两个程序是非常标准的。不论是Person类还是Student类都将自身的属性设置为私有,同时还给予了相应的访问器方法和修改器方法。
但是,这两个类的重合度太高了,而且,位于Person类中的所有属性在Student中被全部重新定义了。这就好像是,在进行自我介绍时,说,我是一个人…

如果每个类都必须这样设计的话,程序的代码将会显得有点累赘,开发效率也不是很高。我们希望写出来的代码更加简洁,复用率较高。
Java通过实现继承来解决这样的问题,下面我们来了解一下继承。

何为继承

继承:一种在已有类的基础上构造新类的机制

继承是面向对象的三种特性之一。它和现实世界中的继承概念大同小异。都表示新生儿(新类)会继承父母(已有类)的一些固有属性(外貌特征、姓氏,甚至一些性格特征),同时,新生儿会对父母现有的生活方式(行为)造成一定的影响,甚至会进入新的领域······所有的这些在Java中都有相应的体现,下面我们来了解了解Java中的继承。

超类和子类

在Java中,常常用超类(superclass)或者父类 表示已有的类,使用子类(subclass) 表示构造的新类。

继承的实现

使用extends关键字定义子类。

class subclass extends superclass{
}

例如,让Student类继承Person

public class Students extends Person {
    private double chinese;
    private double math;
    private double english;

    public Students() {
    }
	public Student(String name, int age, String sex, double chinese, double math, double english) {
        super(name, age, sex);
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }
    
    public void setChinese(double chinese) {
        this.chinese = chinese;
    }

    public double getChinese() {
        return chinese;
    }

    public void setMath(double math) {
        this.math = math;
    }

    public double getMath() {
        return math;
    }

    public void setEnglish(double english) {
        this.english = english;
    }

    public double getEnglish() {
        return english;
    }
}

下图表示了这种继承关系:

子承父业

在子类继承父类之后,子类便拥有了父类所有的属性、方法。
在创建子类对象时,编译器也会给父类的所有属性、方法分配内存空间。需要注意的是,子类对象只可以访问父类的非私有属性、非私有方法。毕竟,父母也想要有自己的私人小空间。

类的继承层次

在Java中,一个类只能有一个父类,但是可以有多个子类,用专业术语来讲,Java是一种单继承的程序设计语言。这和现实生活中的家庭关系更加相似,所以,Java中的类继承关系也可以使用树形结构表示。

Object类是所有类的祖先类,如果一个类没有显式地继承某一个类,那么它会默认继承于Object

类的继承路径

某一个类的继承路径是指:从祖先类到该类所经过的所有类

Student类的继承路径:Object->Person->Student

青出于蓝——方法重写

有的时候,父类所提供的方法并不能满足我们的实际需求。为了说明这种情况,举个典型的例子:

在Object类中,有一个equals方法,这个方法用于比较两个对象是否相等,来看一下它的源代码

public boolean equals(Object obj) {
        return (this == obj);    
}

这里,代码直接进行this == obj。因为obj是一个引用变量,所以,这个方法将会比较两个引用值是否相等。引用值相等,说明参与比较的双方是同一个对象。

但有的时候,这并不能满足我们的需求。看下面这个例子:

public class StudentTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer("李明");
        StringBuffer s2 = new StringBuffer("李明");
        if(s1.equals(s2)){
            System.out.println("他们是同一个人");
        }else{
            System.out.println("他们不是同一个人");    
        }
    }
}

在这个例子中,我们想看看s1和s2是不是同一个人,因为对象只给出了李明这个信息,所以程序应该认为s1 == s2。但是结果却有些不同:

这是因为StringBuffer直接继承了Object类的equals方法,相等的依据仅仅是引用值,而不对对象中存储的值进行比较。

为了满足我们的需求,我们需要为equals方法赋予新的比较规则,在Java中,把这种行为称为方法重写

方法重写:在子类中重新定义父类的方法。

方法重写有几个必须遵循的规则

  1. 方法签名(方法名和参数类型、参数个数)一样
  2. 返回值需要和父类兼容(简单地说,返回值类型必须是该类的继承路径上的一种类)
  3. 重写的方法,访问权限不得低于父类的方法

根据这几个规则,我们对Student类中的equals方法进行重写:

public class Student extends Person {
...
    @Override		//可以判断该方法是否被重写
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Double.compare(student.chinese, chinese) == 0 &&
                Double.compare(student.math, math) == 0 &&
                Double.compare(student.english, english) == 0;
    }
}

在子类中重写方法之后,父类的方法将会被隐藏。这就是说,当子类重写方法之后,子类对象调用方法时,将会调用子类中的方法。

public class StudentTest {
    public static void main(String[] args) {
        Student s1 = new Student("李明",12,"男",88,99,66);
        Student s2 = new Student("李明",12,"男",88,99,66);
        if(s1.equals(s2)){
            System.out.println(s1.getName() + "和" + s2.getName() + "是同一个人");
        }else {
            System.out.println(s1.getName() + "和" + s2.getName() + "不是同一个人");
        }
    }
}

结果为:

创新驱动发展

除了使用父类中的属性、方法之外,子类还可以定义自己的域、方法。这种现象,称为子类扩展功能

Person可以做的事有很多,但是学生会更加专注地学习,所以在定义学生类时,我们可以引入study方法,让学生类更有特色。

public class Student extends Person {
	//子类中的自定义属性
    private double chinese;
    private double math;
    private double english;

    public Student() {
    }

    public void setChinese(double chinese) {
        this.chinese = chinese;
    }

    public double getChinese() {
        return chinese;
    }

    public void setMath(double math) {
        this.math = math;
    }

    public double getMath() {
        return math;
    }

    public void setEnglish(double english) {
        this.english = english;
    }

    public double getEnglish() {
        return english;
    }
    //自定义方法
    public void study() {
        System.out.println(getName() + "is studing");
    }
    public void study(String s){
        System.out.println(getName() + "is studing " + s);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Double.compare(student.chinese, chinese) == 0 &&
                Double.compare(student.math, math) == 0 &&
                Double.compare(student.english, english) == 0;
    }

    @Override
    public int hashCode() {
        return Objects.hash(chinese, math, english);
    }
}

命名冲突

让子类自定义属性和方法可以使得类的功能更加丰富,相应地,我们可以自然而然地遇见一个问题——如果子类中的属性/方法和父类中的属性/方法重名,那么,子类对象会调用哪个属性/方法?

我们从三点进行回答:

  1. 如果子类中的方法和父类中的方法重名了,且方法签名一样,那么,就属于方法重写,子类对象将会调用自己的方法
  2. 如果子类中的方法和父类中的方法重名,但是方法签名不一样,那么,就属于方法重载
  3. 如果子类中的属性名和父类中的属性名重名,父类的属性将会被隐藏,子类进行调用时,调用的是自己的属性。

可以在子类中使用super关键字调用父类的属性或者方法。

后继无人

使用fianl修饰类,那么该类将不可以被继承。

public final class Student extends Person{
...
}

这里将Student类声明称一个final类,那么你将不能将某一个类声明为它的子类。下面的程序是一个错误示例:

public class Undergraduate extends Student{
...
}

final除了可以修饰类,还可以修饰方法和变量。

  1. 如果将某个方法声明为final,那么这个方法将不能允许重写。
  2. 如果将某个变量声明为final,那么,这个变量的值将不能被修改。

血脉相连——protected修饰符

我们陆续地接触过三种修饰符:public缺省private。他们都有着各自的作用域:

这些作用域有一个特点——他们彼此之间都是独立的,没有依赖性。这是封装的一种很好的体现。但是,对于继承而言,便不是那么的友好。
想象一下,如果,子类和父类不在同一个包里,那么,应该怎么相互访问呢?将属性设置为public的话,数据的安全无法得到保证,如果将属性设置为缺省,那么,由于两个类位于不同的包,所以两者不能相互访问。
为了解决这个问题,Java引入了第四种访问权限修饰符——protected

protected:在缺省的基础上,允许不同包中的父子类相互访问。

以上是关于面向对象一文了解Java继承(超详细超有趣)的主要内容,如果未能解决你的问题,请参考以下文章

超详细的Java面试题总结之Java基本知识

Redis二三事一文了解Redis的哨兵机制(超详细步骤图解)

以面向对象的思想实现数据表的添加和查询,JDBC代码超详细

java多线程编程从入门到卓越(超详细总结)

Java——类和对象超详细总结

超详细干货收藏向Java面试必备一文搞定Java集合容器