面向对象一文了解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中,把这种行为称为方法重写。
方法重写:在子类中重新定义父类的方法。
方法重写有几个必须遵循的规则:
- 方法签名(方法名和参数类型、参数个数)一样
- 返回值需要和父类兼容(简单地说,返回值类型必须是该类的继承路径上的一种类)
- 重写的方法,访问权限
不得低于
父类的方法
根据这几个规则,我们对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);
}
}
命名冲突
让子类自定义属性和方法可以使得类的功能更加丰富,相应地,我们可以自然而然地遇见一个问题——如果子类中的属性/方法和父类中的属性/方法重名,那么,子类对象会调用哪个属性/方法?
我们从三点进行回答:
- 如果子类中的方法和父类中的方法重名了,且方法签名一样,那么,就属于方法重写,子类对象将会调用自己的方法
- 如果子类中的方法和父类中的方法重名,但是方法签名不一样,那么,就属于方法重载。
- 如果子类中的属性名和父类中的属性名重名,父类的属性将会被隐藏,子类进行调用时,调用的是自己的属性。
可以在子类中使用super
关键字调用父类的属性或者方法。
后继无人
使用fianl
修饰类,那么该类将不可以被继承。
public final class Student extends Person{
...
}
这里将Student
类声明称一个final类,那么你将不能将某一个类声明为它的子类。下面的程序是一个错误示例:
public class Undergraduate extends Student{
...
}
final
除了可以修饰类,还可以修饰方法和变量。
- 如果将某个方法声明为
final
,那么这个方法将不能允许重写。 - 如果将某个变量声明为
final
,那么,这个变量的值将不能被修改。
血脉相连——protected修饰符
我们陆续地接触过三种修饰符:public
,缺省
,private
。他们都有着各自的作用域:
这些作用域有一个特点——他们彼此之间都是独立的,没有依赖性。这是封装的一种很好的体现。但是,对于继承而言,便不是那么的友好。
想象一下,如果,子类和父类不在同一个包里,那么,应该怎么相互访问呢?将属性设置为public
的话,数据的安全无法得到保证,如果将属性设置为缺省
,那么,由于两个类位于不同的包,所以两者不能相互访问。
为了解决这个问题,Java引入了第四种访问权限修饰符——protected
。
protected:在缺省的基础上,允许不同包中的父子类相互访问。
以上是关于面向对象一文了解Java继承(超详细超有趣)的主要内容,如果未能解决你的问题,请参考以下文章