学校里入门一门编程语言的时候,大都会配合着数据结构和算法来练习,用到继承的地方往往并不多,
倒是最近的一些工作中继承接口用的频繁,这里不谈这样做的优点,就简单描述下继承的机制,配合着IDE的调试信息给新手入个门。
继承是什么:继承描述的是一个“is-a”关系,比如说学生是人,那么学生这个类就可以继承自人这个类,这里学生就是子类,人就是父类(或者叫超类,有多种说法)。
Java只支持单继承,就是说一个父类只能继承自一个父类。
下面写一个继承的例子:
package
daily
P
rg0712;
public
class
Person {
private
String name;
private
String age;
public
String
getName() {
return name;
}
public
void
setName(
String
name) {
this.name
= name;
}
public
String
getAge() {
return age;
}
public
void
setAge(
String
age) {
this.age
= age;
}
}
package
daily
P
rg0712;
public
class
Student
extends
Person{
private
String school;
public
String
getSchool() {
return school;
}
public
void
setSchool(
String
school) {
this.school
= school;
}
}
可以看到Student继承自Person,但要多一条属性school,这也是Student特殊于Person之处。
————————————————————————————————————————————————————
现在我们给这两个类添加构造器,
package
daily
P
rg0712;
public
class
Person {
private
String name;
private
String age;
public
Person(
String
name,
String
age) {
this.name
= name;
this.age
= age;
}
public
String
getName() {
return name;
}
public
void
setName(
String
name) {
this.name
= name;
}
public
String
getAge() {
return age;
}
public
void
setAge(
String
age) {
this.age
= age;
}
}
package
daily
P
rg0712;
public
class
Student
extends
Person{
private
String school;
public
Student(
String
name,
String
age,
String
school) {
super(name, age);
this.school
= school;
}
public
String
getSchool() {
return school;
}
public
void
setSchool(
String
school) {
this.school
= school;
}
}
可以在Student类的构造器中看到这么一行代码super(name, age);
由于Student类的构造器不能访问Person的私有域,所以必须利用Person类的构造器对这部分私有域进行初始化。
需要注意的是,使用super调用父类构造器的语句必须是子类构造器的第一条语句,如果我们改一下顺序,
this.school
= school;
super(name, age);
编辑器就会报错提示了
![](https://i-blog.csdnimg.cn/blog_migrate/090f9026935561292c349f54aa94d855.png)
如果子类的构造器没有显式的调用父类的构造器,则将自动的调用超类默认(没有参数)的构造器。
我们可以在代码里做些手脚来看一下构造器的运行先后,
修改Person的构造器如下
public
Person(){
System.out.
println(
"running Person no param Constructor...");
}
public
Person(
String
name,
String
age) {
System.out.
println(
"running Person Constructor...");
this.name
= name;
this.age
= age;
}
当在Student类中显式调用的时候
public
Student(
String
name,
String
age,
String
school) {
super(name, age);
System.out.
println(
"running Student Constructor...");
this.school
= school;
}
控制台打印的消息如下
![](https://i-blog.csdnimg.cn/blog_migrate/6d6d18cac6f9a0b1f39d851fa45e6624.png)
如果去掉这行显式调用
public
Student(
String
name,
String
age,
String
school) {
System.out.
println(
"running Student Constructor...");
this.school
= school;
}
打印消息如下
![](https://i-blog.csdnimg.cn/blog_migrate/7a9d749b42ac2f2f4790df12a795b347.png)
————————————————————————————————————————————————————
但是现在这两个类啥也干不了,我们给Person添加一个speak()方法
package
daily
P
rg0712;
public
class
Person {
private
String name;
private
String age;
public
Person(
String
name,
String
age) {
this.name
= name;
this.age
= age;
}
public
String
getName() {
return name;
}
public
void
setName(
String
name) {
this.name
= name;
}
public
String
getAge() {
return age;
}
public
void
setAge(
String
age) {
this.age
= age;
}
public
void
speak(){
System.out.
println(
"Hi, my name is "
+name
+
", I'm "
+age
+
" years old.");
}
}
但是Student要特殊于Person,它的speak()方法要表明自己在哪里上学,
这时我们就需要在子类里覆盖父类的speak()方法了
package
daily
P
rg0712;
public
class
Student
extends
Person{
private
String school;
public
Student(
String
name,
String
age,
String
school) {
super(name, age);
this.school
= school;
}
public
String
getSchool() {
return school;
}
public
void
setSchool(
String
school) {
this.school
= school;
}
@Override
public
void
speak(){
System.out.
println
(
"Hi, my name is "
+
name
+
", I'm "
+
age
+
" years old, I study in "
+
school
+
"."
);
}
}
然而,这个方法并不能运行,我们可以看到编辑器提示了错误信息
![](https://i-blog.csdnimg.cn/blog_migrate/e60cddd1437a5418f547c5f0072ae3a5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/808233f538fabc83bb0dbf1a0ccdcfeb.png)
意思是说name和age不能被访问,
这事因为Student类的speak()方法不能够直接访问父类Person的私有域,
尽管每个Student对象都拥有名为name和age的域,但在Student的方法中并不能直接访问他们。
直白的说就是继承并不是把父类所有的东西拷贝了一份到自己的身上,父类的还是父类的,子类只是拥有了父类所有的功能。
这里的name和age可以用父类的共有get方法来获得,修改代码如下
package
daily
P
rg0712;
public
class
Student
extends
Person{
private
String school;
public
Student(
String
name,
String
age,
String
school) {
super(name, age);
this.school
= school;
}
public
String
getSchool() {
return school;
}
public
void
setSchool(
String
school) {
this.school
= school;
}
@Override
public
void
speak(){
System.out.
println(
"Hi, my name is "
+
super.
getName()
+
", I'm "
+
super.
getAge()
+
" years old, I study in "
+school
+
".");
}
}
这里用super关键字来调用父类的方法,方法可以正常运行,我们写个测试类并运行一下,
package
daily
P
rg0712;
public
class
Test {
public
static
void
main(
String[]
args){
Student student
=
new
Student(
"Jenny",
"18",
"WUT");
student.
speak();
}
}
运行结果
![](https://i-blog.csdnimg.cn/blog_migrate/97731b1e8b42562b01739e658f88d9be.png)
在子类中可以增加域、增加方法或者覆盖父类的方法,然而绝对不能删除继承的任何域和方法。
而且在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。
————————————————————————————————————————————————————提到继承就少不了多态,
在上面的Test中,如果修改为
package
daily
P
rg0712;
public
class
Test {
public
static
void
main(
String[]
args){
Person student
=
new
Student(
"Jenny",
"18",
"WUT");
student.
speak();
}
}
运行一下会怎么样呢?
我们测试一下发现是一样的结果
![](https://i-blog.csdnimg.cn/blog_migrate/97731b1e8b42562b01739e658f88d9be.png)
在Java中,一个对象变量是多态了,一个Person变量可以引用一个Person变量,也可以引用一个它的任何一个子类的对象。这样做有一定的好处,比如提高程序的可扩容性和可维护性。
如果我们把Student类中覆盖speak()方法部分注释掉,即Student没有重写Person的speak()方法,再运行会怎样呢?
package
daily
P
rg0712;
public
class
Student
extends
Person{
private
String school;
public
Student(
String
name,
String
age,
String
school) {
super(name, age);
System.out.
println(
"running Student Constructor...");
this.school
= school;
}
public
String
getSchool() {
return school;
}
public
void
setSchool(
String
school) {
this.school
= school;
}
// @Override
// public void speak(){
// System.out.println("Hi, my name is "+super.getName()+
// ", I'm "+super.getAge()+" years old, I study in "+school+".");
// }
}
打印的结果如下
![](https://i-blog.csdnimg.cn/blog_migrate/e70c96e8237c13986490863de575d9ae.png)
我们发现这时执行的就是父类的speak()方法了。
也就是说执行方法的时候会先在子类中找,如果找到了就直接调用,否则,就去父类中找,然后父类的父类,以此类推。
此外我们还可以在子类中直接用super.speak()来调用父类的speak()方法,
public
void
speak(){
super.
speak();
System.out.
println(
"Hi, my name is "
+
super.
getName()
+
", I'm "
+
super.
getAge()
+
" years old, I study in "
+school
+
".");
}
打印如下
![](https://i-blog.csdnimg.cn/blog_migrate/e809bff48eda867f8552fcd8984e0e70.png)
————————————————————————————————————————————————————
关于final关键字对继承的影响,final域是常量,这里我们说下final类和final方法。
不允许被扩展的类称为final类,如果我们将Person声明为final类会怎么样?
![](https://i-blog.csdnimg.cn/blog_migrate/a7fe85d9e25b6c56d29ea7c20005dba4.png)
在继承的时候编辑器开始报错不能继承final类。
而将方法声明为final,子类就不能覆盖这个方法,我们试验一下,将speak()声明为final
![](https://i-blog.csdnimg.cn/blog_migrate/74501b3df1bd7aa0ef6a7ed98ab41187.png)
一样的,也报错了。
这里有一点就是,如果将一个类声明为final,只有其中的方法自动的变成final,而不包括域。