
继承,面向对象语言的三大基本特征之一,实现软件复用的重要操作,通过extends关键字实现,例如猫类继承动物类,苹果类继承水果类,实现继承的类叫做子类,被继承的类叫做父类或者基类……
4、继承
继承
继承,面向对象语言的三大基本特征,实现软件复用的重要操作,通过extends关键字实现,例如猫类继承动物类,苹果类继承水果类,实现继承的类叫做子类,被继承的类叫做父类或者基类。举例:
1 |
public class |
1 |
public class Ostrich extends |
上面的实例中我们先写了一个Birds作为父类,定义了一个属性color,一个view方法打印颜色,用一个Ostrich类继承Birds,Ostrich类就拥有了父类的方法,创建Ostrich对象就可以调用父类的方法,但是Ostrich对象并不能直接访问父类的私有属性,上面程序输出:
1 |
color:黑色 |
可是,为什么要用继承呢?这样的方法自己写了不是更好?
在上面的实例中,鸟类涵盖范围太大了,从麻雀到老鹰,从候鸟到留鸟,大部分的鸟类都会飞,但是飞的行为却不一样,所有的鸟类都要吃东西,但是有的食草有的食肉,还有各种各样的颜色。如果每一种鸟都去定义这样的方法,势必对出现大量重复的代码,所以才将共性的部分提取出来作为父类,事实上很多的继承并不是一开始就这么制定了,而是在优化的过程中不断抽取重复代码才出现的。
继承主要有以下特性:
- 子类拥有父类非private的方法和属性;
- 子类可以拥有自己独立的方法和属性,也就是子类的可扩展性;
- 子类可以重写父类的方法;
- java中的继承是单继承,也就是说一个父类可以有多个子类,但是一个子类只能继承于一个父类,如果我们要实现多重继承,就可以构造多个类,类A继承类B,类B继承类C。
方法重写:
上面的例子中,鸵鸟类集成了鸟类,有了显示颜色的方法,鸟类会飞翔,但是鸵鸟并不会飞翔,所以鸵鸟要去重写飞翔的方法实现自己的逻辑,修改如下:
1 |
public class |
1 |
public class Ostrich extends |
上面的例子中,如果子类没有重写fly方法,那么Ostrich的对象调用fly方法就会打印:鸟儿会飞,显然是不合逻辑的,而重写了这个方法之后,再去调用fly方法就是自己的实现了,打印出:鸵鸟会跑不会飞。一般我们会在重写的方法上一行写上@override标签来表示这是一个重写的方法,需要注意重写和重载的不同之处。
super关键字和构造方法调用:
super关键字和this关键字很相似,不过super指向的是父类的对象,this指向的是当前类的对象,同样的super也不能出现在static修饰的方法中,实例如下:
1 |
|
在fly方法中使用super调用的是父类的方法,super表示父类的对象。这里使用的super实际上是调用了父类无参的构造器,而且在默认情况下也是调用父类无参的构造器。但如果我们想用super去调用父类声明的有参构造方法呢?
重新写一个实例,我们知道构造方法是用来初始化一个对象的,来看一下子类是怎么执行父类的构造方法:
1 |
public class Employee { |
1 |
public class Manager extends Employee { |
看一下这两个类,父类是雇员类,子类是经理类,这里同样也可以再说明一下什么时候使用继承,我们在处理两个对象时,如果有“is-a”的关系,就可以使用继承,比如上面的实例中,经理作为公司管理人员,在项目结束时会拿到奖金,而普通员工并没有,但同时经理也隶属于公司,同样作为雇员存在,所以可以用继承减少相同代码,去进行自己功能的扩展,同样作为雇员,人事、行政、技术、销售等各个岗位都会有自己的特性,各个岗位又有共性,此时就可以用继承来实现。
继续说到构造方法,上面的实例中,运行main()方法会输出如下结果:
1 |
父类无参的构造方法 |
我们对子类构造了三个不同的对象,分别使用子类无参、有参构造器和super调用父类构造器,可以看到,不管是否使用super关键字,每一个对象的创建都会先去调用父类中的构造方法一次,总结一下:
- 如果子类构造器既没有super也没有this,则默认执行子类构造器前先执行父类无参构造器;
- 如果子类构造器使用this调用子类另一个重载的构造器,系统会根据传入的实参去掉用对应的构造器,执行该构造器之前又会先去调用父类无参构造器;
- 如果子类构造器第一行使用super调用父类构造器,系统会根据实参调用对应的父类构造器。
需要注意的是:this和super对构造器的调用都在代码第一行,所以它们不能同时出现。
Object类
在前面的例子中,我们构造的普通java类中有重写toString、hashCode和equals方法,然而我们并没有使用extends关键字去集成任何一个类,那么怎么会有这些重写的方法呢?原因很简单,Java中定义了一个Object类,这个类是所有类的超类,也就是说每一个类都是由它扩展而来,默认每一个类都继承这个类,但是并不需要显式指定。结合前面构造方法的调用,我们初始化一个子类会先去调用父类的构造方法,Object类就是最顶层的父类。既然是作为最顶级的父类,那就一定提供了一些所有类通用的方法,下面看几个常用的方法:
- equals()方法:用来判断两个对象的内容是否相同,还记得前面说比较运算符说到的==吗,==在引用数据类型中比较的是对象的引用。先看一段代码,再讨论一下这二者之间的区别:
1
2
3
4
5
6
7
8
9
10
11
12public class EqualTest {
private int id;
public EqualTest(int id) {
this.id = id;
}
public static void main(String[] args) {
EqualTest e1 = new EqualTest(1);
EqualTest e2 = new EqualTest(1);
System.out.println(e1.equals(e2));
System.out.println(e1 == e2);
}
}这个实例的输出结果是:
1
2false
false很奇怪是不是,对于”==”运算符比较两个EqualsTest对象返回false,我们明白这点,因为e1和e2分别指向不同的对象,所以二者内存地址是不相同的。但是equals()方法呢?我们前面说比较的是内容,也就是说上例中拥有相同id的情况下我们认为两个对象是相等的,却返回了false。原因就是我们使用equals方法的时候没有去重写,所以此时调用的是Object类的equals()方法,这个原始方法内部的实现其实就是”==”,,看一下源码:
1
2
3public boolean equals(Object obj) {
return (this == obj);
}所以,为了达到我们的期望值,就要重写equals()方法,让对象之间的比较按照我们需要的逻辑去比较内容,而不是内存地址,重写如下:
1
2
3
4
5
6
7
8
public boolean equals(Object o) {
if (o instanceof EqualTest) {
EqualTest test = (EqualTest) o;
return this.id == test.id;
}
return false;
}再次运行,我们发现输出的结果就满足我们的预期了:
1
2true
false上面重写的方法中用到了instanceof关键字,它可以用来判断某对象所指向的类型,判断返回boolean值,在上面的例子中传入的对象是EqualsTest类型,然后去比较id,也就是内容,返回比较的结果。
- hashCode()方法:
一般要求在重写equals()方法的同时也重写hashCode()方法,该方法返回一个对象的哈希值,后面再说。
- toString()方法:
该方法主要用来返回当前对象的字符串表达式,继续在上面的例子中重写如下:
1
2
3
4
public String toString() {
return "EqualTest{" + "id=" + id + "}";
}打印该对象输出:
1
EqualTest{id=1}
如果不去重写toString()方法,那么打印对象会输出如下结果(类名[email protected]+hashCode):
1
[email protected]1540e19d
- 线程同步相关方法:
wait()、notify()、notifyAll()方法,这几个方法在后面章节线程中会详细说明。
- getClass()方法:
该方法会返回一个对象的类对象,关于这个方法会在后面章节反射机制中说明。




近期评论