本章主要介绍Java中继承原因及实现以及继承的特点,使用抽象类的原因及实现和其特点,方法重写的特点,以及final和super关键词的使用。
8.1 继承
8.1.1 为什么要继承?
在说明为什么要继承之前,先来看看下面两个范例。
首先定义了学生类。
范例1:Student.java
class Student{ private String name;//姓名 private int age; //年龄 public void speak(){ System.out.println(name + "在说话"); } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public String getName(){ return name; } public int getAge(){ return age; } }
然后再定义了老师类。
范例2:Teacher.java
class Teacher{ private String name;//姓名 private int age; //年龄 public void speak(){ System.out.println(name + "在说话"); } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public String getName(){ return name; } public int getAge(){ return age; } }
从这两个类中可以看出,它们除了类名不同外,程序代码基本上都是相同的。既然有这么多相同代码,那么能不能把这些相同的代码抽取出来,形成一个单独的类,然后让这两个类和抽取出来的这个类产生一定的关系,让它们同时拥有这个单独的类的全部功能呢?
为了解决上面的问题,Java提供了继承这样一个概念来处理。在实际开发中,经常都会遇到上面的这类问题——几个类中含有相同代码。使用继承后,可以提高代码的复用性。
8.1.2 如何继承?
既然Java提供了继承,那么在程序中如何使用继承呢?在Java中,通过使用关键词extends来实现继承。语法格式如下:
class 子类 extends 父类{ }
假设从Student类和Teacher类中抽取出相同的代码组成父Person类,示例代码如下。
范例3:InheritanceDemo01.java
class Person{ private String name;//姓名 private int age; //年龄 public void speak(){ System.out.println(name + "在说话"); } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public String getName(){ return name; } public int getAge(){ return age; } } class Student extends Person{ } class Teacher extends Person{ } public class InheritanceDemo01{ public static void main(String[] args){ Student s = new Student(); s.setName("小明"); s.setAge(15); s.speak(); Teacher t = new Teacher(); t.setName("王老师"); t.setAge(30); t.speak(); } }
程序输出结果为:
小明在说话
王老师在说话
分析:首先从Student类和Teacher类中抽取它们相同部分代码,生成了一个新的Person类(父类),然后Student类通过关键词extends来继承Person类,Teacher类也继承Person类。通过extends继承后,会发现Student类和Teacher类代码变得非常的简单了。虽然代码变得简单了,但是程序的功能却并没有发生变化。当Student类extends继承Person类后,Student类就拥有了Person类中的属性和方法,而且这些方法在Student类中直接可以使用。
8.1.3 使用继承后实例化顺序
通过上面的示例已经知道,使用extends关键词可以让两个关产生继承关系,有了继承关系后,子类就可以使用父类中的属性和方法。那么使用继承后,在对象实例化时,是如何进行的呢?看下面两个示例就明白了。
范例4:InheritanceDemo02.java
class Father{ public Father(){ System.out.println("父类被实例化了!"); } } class Son extends Father{ public Son(){ System.out.println("子类被实例化了!"); } } public class InheritanceDemo02{ public static void main(String[] args){ Son son = new Son(); } }
程序的运行结果为:
父类被实例化了!
子类被实例化了!
分析:从程序的运行结果上可以看出,使用继承后是先实例化父类,再实例化子类。换句话说,是先有父类,再有子类。这显然和现实世界相符合的,这也再一次证明Java是从现实世界抽取出来的。
为什么使用了extends关键词来继承后,实例化子类的时候会先实例化父类呢?
实际上当子类继承父类后,在子类的构造方法中隐含调用了父类的构造方法,而这个隐含调用是关键词super来实现的。看下面示例:
范例5:InheritanceDemo03.java
class Father{ public Father(){ System.out.println("父类被实例化了!"); } } class Son extends Father{ public Son(){ super(); System.out.println("子类被实例化了!"); } } public class InheritanceDemo03{ public static void main(String[] args){ Son son = new Son(); } }
范例5和范例4的代码差别不大,输出结果是一样的。在本例Son类的构造方法中,添加了super();代码,这段代码就是调用父类的无参构造方法。super关键词和this关键词的作用一样。它可以代表的是父类对象,也可以调用父类的属性、方法和构造方法。如果是调用父类中的构造方法,super()方法需要放到子类构造方法的第一行;同样,super关键词也不能出现在static修饰的方法中。
8.1.4 子类继承访问限制
前面提到,子类继承父类后,就可以直接访问父类中的属性和方法。那么是不是所有的属性和方法都可以直接使用呢?看下面的示例:
范例5:InheritanceDemo04.java
class Role{ private String name; int damage; private void attack(){ System.out.println(name + "发起攻击"); } } class SwordMan extends Role{ } public class InheritanceDemo04{ public static void main(String[] args){ SwordMan man = new SwordMan(); man.name = "大唐剑士"; man.attack(); } }
程序编译时报如下错误信息:
InheritanceDemo04.java:13: 错误: name可以在Role中访问private
man.name = "大唐剑士";
^
InheritanceDemo04.java:14: 错误: 找不到符号
man.attack();
^
符号: 方法 attack()
位置: 类型为SwordMan的变量 man
2 个错误
该错误信息指出,Role类中的name属性和attack()方法是private(私有的),在InheritanceDemo04类中不能访问,只能在Role类中才能访问。换名话说,子类继承父类后,只能访问或继承父类中不是private(私有)的属性或方法。另外,子类也不能继承父类中的构造方法,原因就在于构造方法的特殊性。但子类可以调用父类的构造方法。
注:在继承中,子类不能继承父类中私有属性或方法,也不能继承父类的构造方法。
8.1.5 继承的缺性
在Java中,一个子类只能有一个直接的父类,换句话说,Java只支持单继承。
范例6:InheritanceDemo05.java
class One{ } class Two{ } class Three extends One,Two{ } public class InheritanceDemo05{ public static void main(String[] args){ Three three = new Three(); } }
程序编译时会输出如下错误信息:
InheritanceDemo05.java:5: 错误: 需要'{'
class Three extends One,Two{
^
1 个错误
分析:为什么会出现这样的错误呢?其实Java是为了程序的安全性来考滤的。假如类One中有一个方法void show(){},而类Two中也有一个void show(){}方法,那么类Three同时继承了类One和类Two后,实例化Three类后,调用show()方法,这时Java虚拟机就不知道是应该执行类One中的show()方法还是执行类Two中的show()方法,而这种不确定性,会导致程序会出现错误。为了让程序不会出现这种不确定的错误发生,在Java中只支持单继承。
为了弥补单继的缺性,在Java中可以使用多重继承,看下面示例:
范例6:InheritanceDemo06.java
class One{ void oneMethod(){ } } class Two extends One{ void twoMethod(){ } } class Three extends Two{ } public class InheritanceDemo06{ public static void main(String[] args){ Three three = new Three(); } }
上面的示例中,Three类既拥有类One中的oneMethod()方法,也拥有类Two中的twoMethod()方法。
8.1.6 方法重写
假设在父类中有一个方法public void show(){},而子类中也有一个方法public void show(){},那么,实例化子类后调用show()方法是输出父类的呢还是输出子类的?看下面示例:
范例6:InheritanceDemo07.java
class Shape{ public void getArea(){ System.out.println("输出面积"); } } class Circle extends Shape{ public void getArea(){ System.out.println("输出圆的面积"); } } public class InheritanceDemo07{ public static void main(String[] args){ Circle circle = new Circle(); circle.getArea(); } }
程序的输出结果为:
输出圆的面积
为什么会发生这种情况呢?前面提到子类继承父类后,是先实例化父类,那么为什么不是输出:“输出面积”呢?那是因为子类重写了父类中的getArea()方法。当子类重写了父类中的方法后,输出的结果就是子类的结果(重写后的结果)。
8.1.6.1 为什么方法重写?
很多时候,父类中定义的方法输出结果并不是程序最终执行想要的结果。比如我们没办法在父类中定义圆形的面积计算和方形的面积计算,而是在子类中去具体实现相应的面积计算方法,所以需要进行方法的重写。
8.1.6.2 方法重写特点
要想实现方法重写,必须满足如下要求:
1. 子类和父类中方法名称必须相同;
2. 子类和父类中方法的参数列表必须相同;
3. 两个方法必须是在子父类中;
4. 子类方法的返回值类型要么和父类相同,要么是父类返回类型的子类;
5. 子类的访问修饰符必须大于或等于父类访问修饰符。比如父类方法是public修饰的那么子类方法必须是public修饰。
8.2 抽象类
8.2.1 为什么要抽象类?
上一节介绍了方法重写,知道方法重写后输出的结果是重写后的输出结果。那么既然重写后输出的结果是子类的方法输出的结果,为什么还要在父类中定义被重写的方法呢?父类中定义的这个方法有什么意思呢?
以前面求几何图形面积为例,根据不同的几何图形计算面积,要求计算面积的方法必须叫getArea()方法。
范例7:AbstractDemo01.java
class Shape{ public float getArea(){ return 0.0f; } } class Circle extends Shape{ private float r; private float PI = 3.14f; public float getArea(){ return r * r * PI; } public void setR(float r){ this.r = r; } public float getR(){ return r; } } public class AbstractDemo01{ public static void main(String[] args){ Circle c = new Circle(); c.setR(2.0f); float area = c.getArea(); System.out.println(area); } }
从本例中可以看出,父类Shape中getArea()方法主要的作用是为了限制子类必须通过这个方法来计算面积,该方法本身在父类中的实现意义不大。也就是说,父类只需要有这个方法声明就行了,不必要实现这个方法。那么如何做到只声明方法而不去实现(方法后面的大括号)它呢?
8.2.2 如何定义抽象类
在Java中,使用抽象方法就可以实现上面的需求。定义抽象方法的语法格式如下:
public abstract 返回值类型 方法名称();
使用关键词abstract来修饰的方法就叫抽象方法。抽象方法是只能有方法的声明,不能有方法的实现。也就是说抽象方法后面不能有“{}”存在。当一个类中有抽象方法后,这个类必须是抽象类,它必须被abstract来修饰。定义抽象类的语法格式为:
abstract class 类名{}
因此,上面示例修改如下:
范例8:AbstractDemo02.java
abstract class Shape{ public abstract float getArea(); } class Circle extends Shape{ private float r; private float PI = 3.14f; public float getArea(){ return r * r * PI; } public void setR(float r){ this.r = r; } public float getR(){ return r; } } public class AbstractDemo02{ public static void main(String[] args){ Circle c = new Circle(); c.setR(2.0f); float area = c.getArea(); System.out.println(area); } }
父类被定义为了抽象类,并且把getArea()方法定义为了抽象方法。经过这样定义后,程序更加合理一些。因为父类不需要也不会知道子类是如何实现面积计算的,只需要规定面积计算方法的声明即可。
8.2.3 抽象类的特点
前面已经介绍了抽象类和抽象方法,那么抽象类有那些特点呢?
范例9:AbstractDemo03.java
abstract class Animal{ public abstract void eat(); public void run(){ } } class Cat extends Animal{ public void sleep(){ } } public class AbstractDemo03{ public static void main(String[] args){ Animal animal = new Animal(); } }
程序在编译时会出现如下错误信息:
AbstractDemo03.java:6: 错误: Cat不是抽象的, 并且未覆盖Animal中的抽象方法eat()
class Cat extends Animal{
^
AbstractDemo03.java:12: 错误: Animal是抽象的; 无法实例化
Animal animal = new Animal();
^
2 个错误
从上面的错误信息中可以得出抽象类有如下特点:
1. 必须是被abstract关键词修饰的类;
2. 抽象类中可以有抽象方法,也可以有非抽象方法;
3. 抽象类不能直接实例化,必须有子类;
4. 如果子类继承抽象类后,没有重写父类中的抽象方法,那么子类也只能是抽象类。
8.3 final关键词
在Java中声明类、属性、方法时,可以使用final关键词来修饰。被final修饰的类不能有子类,也就是不能被继承;被final修饰的变量即为常量,必须赋初值,且赋值后其值不能改变;被final修饰的方法不能被重写。
范例10:FinalDemo01.java
public class FinalDemo01{ public static void main(String[] args){ final int num = 5; num *= 2; } }
程序编译时会报如下错误:
FinalDemo01.java:4: 错误: 无法为最终变量num分配值
num *= 2;
^
1 个错误
这个错误信息说明被final修饰的变量其值不能再改变。
范例11:FinalDemo02.java
class FinalFather{ public final void show(){ } } public class FinalDemo02 extends FinalFather{ public final void show(){ } }
程序编译时会报如下错误:
FinalDemo02.java:6: 错误: FinalDemo02中的show()无法覆盖FinalFather中的show()
public final void show(){
^
被覆盖的方法为final
1 个错误
这个错误说明被final修饰的方法不能被重写。
范例12:FinalDemo03.java
final class FinalClass{ } public class FinalDemo03 extends FinalClass{ }
程序编译时会报如下错误:
FinalDemo03.java:3: 错误: 无法从最终FinalClass进行继承
public class FinalDemo03 extends FinalClass{
^
1 个错误
这个错误信息说明被final修饰的类不能被继承