《JAVA程序设计与实例》记录与归纳--继承与多态
继承与多态
概念贴士:
1. 继承,即是在已经存在的类的基础上再进行扩展,从而产生新的类。已经存在的类成为父类、超类和基类,而新产生的类成为子类或派生类。
2. Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
3. 继承分为单继承和多重继承。单继承是指一个子类最多只能有一个父类。多继承是一个子类可以有两个以上的父类。Java语言中的类只支持单继承,多继承是通过接口(interface)来间接实现的。
4. Java中类的继承是通过关键字extends来修饰的,通过extends的关键字表明前者具备后者的公共的变量和方法,在具备了所有公共的成员变量和方法后,本身还能定义一些特有的成员的变量和方法。(详见实例4-1)
5. 继承有个需要注意的重点:如果子类中定义的成员变量和父类中的成员变量同名,则父类中的成员变量不能被继承,此时称子类的成员变量隐藏了父类的成员变量。同理,当子类中定义了一个方法,并且这个方法的名字、返回类型、参数个数和类型与父类的某个方法完全相同时(不同时,就相当重载),父类的这个方法被隐藏,即不能被子类继承下来。如果在子类中想被某个子类隐藏的父类的成员变量或方法,则可以使用关键字super。
6. 子类不继承父类的构造方法。在创建子类对象时,会先调用父类构造器,初始化继承自父类的成员,然后调用子类构造器初始化子类的成员。
7. 如果在子类的构造方法中,没有显式地使用super关键字调用父类的某个具体构造方法,那么默认地有super()语句,即调用父类的不带参数的构造方法。如果父类没有提供不带参数的构造方法,只提供了带参数的构造方法,则会出现错误。 因此,子类如果想使用父类的构造方法,必须在子类的构造方法中使用,并且得使用关键字super来表示,而且super必须是子类构造方法中的第一个语句。(详见实例4-2)
8. 与类中this关键字相似,Java语言中使用关键字super表示父类对象。通过在子类中使用super做前缀可以引用被子类隐藏的父类变量或被子类重写的父类方法,super用来引用大年对象的父类,假如成员变量x和方法y()分别是被子类隐藏的父类的变量和方法,则super.x表示父类的成员变量x,super.y()表示父类的成员方法y()。(详见实例4-3)
9. super用法总结:
1)在子类构造方法中要强调父类的构造方法,用“super(参数列表)”的方式调用,参数不是必需的。同时还要注意:“super(参数列表)”这条语句只能用在子类构造方法体中的第一行。
2)当子类方法的局部变量或者子类的成员变量与父类成员变量同名时,也就是子类局部变量覆盖父类成员变量时,用“super.成员变量名”来引用父类成员变量。当然,如果父类的成员变量没有被覆盖,也可以用“super.成员变量名”来引用父类成员变量,但这样是不必要的。
3)当子类的成员方法覆盖了父类的成员方法时,也就是子类和父类有完全相同的方法定义(但方法体可以不同),此时,用“super.方法名(参数列表)”的方法访问父类的方法。
4)super关键字只能用在类体中非静态部分,如构造函数与成员方法中,若在main()函数中调用或用在静态方法中则会编译出错,报出Cannot use super in a static context的错误。
10. 在面向对象的领域一切都是对象,同时所有的对象都是通过类来描述的,但并不是所有的类都是用来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类称之为抽象类。
11. 抽象类的一般格式:
abstract class 类名 {
类体
}
12. 在面向对象领域,由于抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象对象概念的抽象类是不能实例化的。 同时,抽象类体现了数据抽象的思想,是实现多态的一种机制,它定义了一组抽象的方法,至于这组抽象方法的具体表现形式,则由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则即没有存在的任何意义,所以说定义的抽象类一定是用来继承的。(详见实例4-4)
13. 使用抽象类应当注意以下七点:
1)抽象类不能被实例化,实例化的工作应该交由它的子类来完成,它只需要有一个引用即可。
2)抽象方法必须由子类来进行重写。
3)只要包含一个抽象方法的抽象类,则该方法必须要定义成抽象类,不管是否还包含其他方法。
4)抽象类中可以包含具体的方法,当然也可以不包含抽象方法。
5)子类中的抽象方法不能与父类的抽象方法同名。
6)abstract不能final并列修饰同一个类。
7)abstract不能与private、static、final或native并列修饰同一个方法。
14. 接口是一种比抽象类更抽象的“类”,接口本身就不是类,因此无法将接口实例化。 实现接口的实现类必须要实现该接口的所有方法,通过使用implements关键字类,表示该类遵循某个或某组特定的接口。(详见实例4-5)
15. 接口不同,一个类可以实现多个接口,不管这些接口之间有没有关联,这样就弥补了抽象类无法多重继承的缺陷。故继承与接口的共同使用,既可以保证数据安全性,又可以实现多重继承。
16. 接口的定义:通过interface定义一个接口。接口定义同类的定义类似,也分为接口的声明和接口体。其中,接口体由常量定义和方法定义两部分构成。 定义接口的一般格式如下:
[修饰符] interface 接口名 [extends 父接口名列表] {
[public] [static] [final] 常量;
[public] [abstract] 方法;
}
PS:1)修饰符:可选,用于指定接口的访问权限,可选值为public。如果省略则使用默认的访问权限。
2)接口名:必选参数,可用于指定接口的名称,接口名必须是合法的Java标识符。一般情况下,要求首字母大写。
3)extends父接口名列表:可选参数,用于指定要定义的接口继承于那个父接口。当使用extends关键字时,父接口名为必选参数。
4)方法:接口中的方法只有定义而没有被实现。
17. 接口的实现:接口定义后,就可以在类中实现该接口了。在类中实现接口可以使用关键字implements。 实现接口的一般格式如下:
[修饰符] class <类名> [extends 父类名] [implements 接口列表] {
}
PS:1)修饰符:可选参数,用于指定类的访问权限,可选值为public、abstract和final。
2)类名:必选参数,用于指定类的名称,类名必须是合法的Java标识符。一般情况下,要求首字母大写。
3)extends父类名:可选参数,用于指定要定义的类继承于哪个父类。当使用extends关键字时,父类名为必选参数。
4)implements接口列表:可选参数,用于指定该类实现的是那些借口。当使用implements关键字时,借口列表为可选参数。当接口列表中存在多个接口名时,各个接口之间使用逗号分隔。
18. 在类中实现接口时,方法的名字、返回值的类型、参数的个数及类型必须与接口中完全一致,并且必须实现接口中的所有方法。
19. 使用接口过程中的注意事项:
1)interface的所有方法访问权限自动被声明为public。确切来说只能为public,如果声明为其它修饰符,将会编译错误。
2)接口中可以定义“成员变量”,或者说不可变的变量,因为接口中的“成员变量”会自动变为public static final类型。可以通过类命名直接访问ImplementClass.name。
3)接口中不存在实现的方法(比抽象类还抽象嘛)。
4)实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。
5)不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用(refer to)一个实现该接口的类的对象。可以使用instanceof检查一个对象是否实现了某个特定的接口,如if(anObject instanceof Comparable){}。
6)在实现多接口的时候一定要避免方法名的重复。
20. 抽象类和接口的区别(在应用上,接口多在系统框架设计方面发挥作用,主要定义模块之间的通信;而抽象类在代码实现方面发挥作用,可以实现代码的重用):(语法区别)
1)抽象类里可以有构造方法,而接口内不能有构造方法。
2)抽象类中可以有普通成员变量,而接口中不能有普通成员变量。
3)抽象类中可以包含非抽象的普通方法,而接口中所有的方法必须是抽象的,不能有非抽象的普通方法。
4)抽象类中的抽象方法的访问类型可以是public、protected和默认类型,但接口的抽象方法只能是public类型,并且默认即为public abstract类型。
5)抽象类中可以包含静态方法,接口内不能包含静态方法。
6)抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以随意,但接口中定义的变量只能是public static类型,并且默认为public static类型。
7)一个类可以实现多个接口,但只能继承一个抽象类(一个类只能继承一个父类,抽象类也是父类的一种)。
21. 多态,即是“一个接口,多种实现”(如:早上老爸说上班,老爸开车上a班,老妈骑自行车上b班,老哥步行上c班)。
22. 多态的实现条件有3个必要条件(Java):继承、重写和向上转型。
1)继承:在多态中必须存在有继承关系的子类和父类。
2)重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
3)向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具有调用父类的方法和子类的方法。
23. 在Java中有两种形式可以实现多态,即继承和接口。
1)基于继承实现的多态:基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。(详见实例4-7)
2)基于接口实现的多态:通过定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用被调用方法时,它会根据实际引用的实例来判断具体调用哪个方法。(详见实例4-8)
PS:与接口相比继承都是单继承,只能为一组相关的类提供一致的服务接口;但是接口可以是多继承多实现,能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口,所有接口相对继承来说有更好的灵活性。
代码解释:
实例4-1
程序说明:定义一个学生类Student,继承自Person类。
1 package duke.example.ch4;
2
3 public class Person {
4 private String name;
5 private int age;
6
7 public void setName(String name) {
8 this.name=name;
9 }
10
11 public String getName() {
12 return name;
13 }
14
15 public void setAge() {
16 this.age=age;
17 }
18
19 public int getAge() {
20 return age;
21 }
22 }
代码说明: 代码行.4-5中 定义name和age两个属性。
代码行.7-21中 定义name和age连个属性的setter和getter方法。
实例4-1*
1 package duke.example.ch4;
2
3 public class Student extends Person {
4
5 private String school;
6
7 public void setSchool(String school) {
8 this.school=school;
9 }
10
11 public String getSchool() {
12 return school;
13 }
14
15 public static void main(String[] args) {
16 Student st=new Student();
17 st.setName("张三");
18 st.setAge(23);
19 st.setSchool("中国xx大学");
20 System.out.println("姓名: "+st.getName()+",年龄: "+st.getAge()+",学校: "+st.getSchool());
21 }
22 }
输出结果:
姓名: 张三,年龄: 23,学校: 中国xx大学
代码说明: 代码行.5中 定义school属性。
代码行.7-13中 定义school属性的setter和getter方法。
代码行.16中 实例化Student对象。
代码行.17-21中 使用setter方法给name、age和school属性分别注入实际的值,并使用getter方法取出注入的值,打印输出“姓名: 张三,年龄: 23,学校: 中国xx大学”。
PS:以上程序中的Student类扩充了Person类,增加了学校的属性及对应的setter和getter方法,由于Person类中已经存在了name和age两个属性,也就是说此时Student类中已经存在3个属性及3组setter合getter方法,所以在Strdent类中不需要再重新声明这两个属性。
实例4-2
程序说明:修改实例4-1中Person类和Student类,分别加入带参数的构造函数。
1 package duke.example.ch4;
2
3 public class Person1 {
4 private String name;
5 private int age;
6
7 public Person1(String name,int age) {
8 this.name=name;
9 this.age=age;
10 System.out.println("我是"+this.name);
11 System.out.println("我的年龄是"+this.age);
12 }
13 }
代码说明: 代码行.7-12中 定义父类Person1的构造方法,带有两个参数name和age。
实例4-2*
程序说明:定义父类Person1的构造方法,带有两个参数name和age。
1 package duke.example.ch4;
2
3 public class Student1 extends Person1 {
4
5 private String school;
6
7 public Student1(String name,int age,String school) {
8 super("张三",23); //用super调用父类Person1的构造方法
9 this.school=school;
10 System.out.println("我来自"+this.school);
11 }
12
13 public static void main(String[] args) {
14 Student1 st=new Student("张三",23,"战争学院");
15 }
16 }
输出结果:
我是张三,我的年龄是23,我来自战争学院
代码说明: 代码行.8中 用super调用父类Person1的构造方法,而且super必须是子类构造方法中的第一个语句。
实例4-3
程序说明:定义父类Sum和子类Average,子类super调用父类的成员变量和方法。
1 package duke.example.ch4;
2
3 public class Sum {
4 int n;
5
6 float f() {
7 float sum=0;
8 for(int i=1;i<=n;i++)
9 sum=sum+i;
10 return sum;
11 }
12 }
代码说明: 代码行.4中 定义父类Sum的成员变量n。
代码行.6-11中 定义父类Sum的方法f()。
实例4-3*
程序说明:使用super调用父类的成员变量和方法。
1 package duke.example.ch4;
2
3 public class Average extends Sum {
4 int n;
5
6 float f() {
7 float c;
8 super.n=n; //使用super调用父类的成员变量
9 c=super.f(); //使用super调用父类的方法
10 return c/n;
11 }
12
13 float g() {
14 float c;
15 c=super.f(); //使用super调用父类的方法
16 return c/2;
17 }
18
19 public static void main(String[] args) {
20 Average aver=new Average();
21 aver.n=100;
22 float result_1=aver.f();
23 float result_2=aver.g();
24 System.out.println("result_1="+result_1);
25 System.out.println("result_2="+result_2);
26 }
27 }
PS:相关super用法总结详见概念-9.
实例4-4
程序说明:定义一个抽象动物类Animal,提供抽象方法cry(), Cat和Dog都是动物类的子类,由于cry()为抽象方法,所以Cat和Dog必须实现cry()方法。
1 package duke.example.ch4;
2
3 public abstract class Animal { //定义抽象类
4 public abstract void cry();
5 }
6
7 public class Cat extends Animal {
8
9 @Override
10 public void cry() {
11 System.out.println("猫咪叫:喵喵喵...");
12 }
13 }
14
15 public class Dog extends Animal {
16
17 @Override
18 public void cry() {
19 System.out.println("小狗叫:汪汪汪...");
20 }
21 }
22
23 public class AbstractTest {
24
25 public static void main(String[] args) {
26 //创建Cat对象,并使该类引用cat指向该对象
27 Cat cat=new Cat();
28 //创建Dog对象,并使该类引用dog指向该对象
29 Dog dog=new Dog();
30
31 cat.cry(); //调用Cat类中的方法
32 dog.cry(); //调用Dog类中的方法
33 }
34 }
代码说明: 代码行.7-13中 Cat类继承抽象类Animal,并实现抽象方法cry()。
代码行.15-21中 Dog类继承抽象类Animal,并实现抽象方法cry()。
PS:抽象类在Java语言中所表示的是一种继承关系,一个子类只能存在一个父类。在抽象类中可以拥有自己的成员变量和非抽象类方法。
实例4-5
程序说明:定义一个用于计算的接口Cal,在该接口中定义一个常量PI和两个方法。编写一个名称为Cire的类,该类实现定义的接口。
1 package duke.example.ch4;
2
3 public interface Cal { //定义接口
4 //定义用于表示圆周率的常量PI
5 final float PI=3.1415926f;
6
7 //定义一个用于计算面积的方法getArea()
8 float getArea(float r);
9
10 //定义一个用于计算周长的方法getCir()
11 float getCir(float r);
12 }
代码说明:代码行.3中 使用interface定义接口cal。与Java类文件一样,接口文件的文件名必须与接口名相同。
实例4-5*
程序说明:使用interface定义接口Cal。与Java的类文件一样,接口文件的文件名必须与接口名一致。
1 package duke.example.ch4;
2
3 public class Cire implements Cal { //实现接口
4
5 @Override
6 public float getArea(float r) {
7 //计算圆面积并赋值给变量area
8 float area=PI*r*r;
9 return area; //返回计算后的圆面积
10 }
11
12 @Override
13 public float getCir(float r) {
14 //计算圆周长并赋值给变量circum
15 float circum=2*PI*r;
16 return circum //返回计算后的圆周长
17 }
18 }
19
20 public class Imp1Test {
21
22 public static void main(String[] args) {
23 Cire c=new Cire();
24 float r=5.0f;
25 System.out.println("圆面积:"+c.getArea(r)+"圆周长:"+c.getCri(r));
26 }
27 }
代码说明: 代码行.3中 Cire类使用implements关键字实现接口Cal。
代码行.6-10中 实现一个用于计算面积的方法getArea()。
代码行.13-17中 实现一个用于计算周长的方法getCir()。
代码行.20-28中 实例化一个Cire对象,进行测试。
PS:实现接口时,一次可以实现多个接口,每个接口间使用“,”分隔。这时就可能出现常量或方法名冲突的情况,解决该问题时,如果常量冲突,则需要明确指定常量的接口,可以通“接口名,常量”实现。如果方法冲突,则只要实现一个方法即可。
实例4-6
程序说明:创建两个接口,分别是电话的协议管理接口IConnecionManager和电话的数据传送接口IDataTransfer,值得注意的是,实现两个接口必须实现这两个接口下的所有方法。
1 package duke.example.ch4;
2
3 public interface IConnecionManager {
4
5 public void dial(String phoneNumber); //拨通电话
6
7 public void huangup(); //通话完毕,挂电话
8 }
9
10 public interface IDataTransfer {
11
12 public void chat(String req); //请求通话
13
14 public void answer(String rep); //回应
15 }
16
17 public class Phone implements IConnecionManager,IDataTransfer {
18
19 @Override
20 public void chat(String req) {
21 System.out.println(req);
22 }
23
24 @Override
25 public void answer(String rep) {
26 System.out.println(rep);
27 }
28
29 @Override
30 public void dial(String phoneNumber) {
31 System.out.println("999-999-999");
32 }
33
34 @Override
35 public void huangup() {
36 System.out.println("通话完毕!");
37 }
38 }
39
40 public class PhoneTest {
41
42 public static void main(String[] args) {
43 Phone phone=new Phone();
44 phone.chat=("云鹤云鹤,我是潜鱼,收到请回答!");
45 phone.answer("潜鱼潜鱼,我是云鹤,已经收到!"):
46 phone.dial("123-456-789");
47 phone.huangup();
48 }
49 }
代码说明: 代码行.3-8中 创建一个电话协议管理接口IConnecionManager,定义两个方法dial()和huangup()。
代码行.10-15中 创建一个电话数据传送接口IDataTransfer,定义两个方法chat()和answer()。
代码行.17中 Phone类使用implements关键字实现两个接口,接口间使用逗号分隔开。
代码行.19-37中 实现两个接口所定义的4个方法。
代码行.40-49中 实例化一个Phone对象,进行测试。
实例4-7
程序说明:模拟USB设备的使用。定义父类USB,子类UDisk、Umouse和UKeyboard对父类的方法重写,表现出各自的行为特性。
1 package duke.example.ch4;
2
3 public class USB { //定义USB类
4 private String name;
5
6 public String getName() {
7 return name;
8 }
9
10 public void setName(String name) {
11 this.name=name;
12 }
13
14 public String load() { //装载USB的方法
15 return"USB设备正在装载......";
16 }
17 }
18
19 public class UDisk extends USB { //定义一个USB设备U盘
20 public UDisk() {
21 setName("16G U盘");
22 }
23
24 public String load() { //重写父类装载USB的方法,实现多态
25 return"正在装载的是:"+getName():
26 }
27 }
28
29 public class Umouse extends USB { //定义一个USB设备USB鼠标
30 public Umouse() {
31 setName("USB接口鼠标");
32 }
33
34 public String load() { //重写父类装载USB的方法,实现多态
35 return"正在装载的是:"+getName();
36 }
37 }
38
39 public class UKeyboard extends USB { //定义一个USB设备,USB键盘
40 public UKeyboard() {
41 setName("USB接口键盘");
42 }
43
44 public String load() { //重写父类装载USB的方法,实现多态
45 return"正在装载的是:"+getName();
46 }
47 }
48
49 public class USBTest { //测试程序
50 public static void main(String[] args) {
51 USB udisk=new UDisk(); //父类USB引用子类UDisk对象
52 system.out.println(udisk.load());
53
54 USB umouse=new Umouse() //父类USB引用子类Umouse对象
55 System.out.println(umouse.load());
56
57 USB ukeyboard=new UKeyboard() //父类USB引用子类UKeyboard对象
58 System.out.println(ukeyboard.load());
59 }
60 }
代码说明: 代码行.3-17中 创建一个USB类,定义name属性以及setter和getter方法,定义方法load()。
代码行.19-27中 创建USB类的子类UDisk,重写load()方法。
代码行.29-36中 创建一个USB类的子类Umouse,重写load()方法。
代码行.38-46中 创建一个USB类的子类UKeyboard,重写load()方法。
代码行.48中 编写测试程序
代码行.50中 父类USB引用子类UDisk对象udisk。
代码行.53中 父类USB引用子类Umouse对象umouse。
代码行.56中 父类USB引用子类UKeyboard对象ukeyboard。
PS:UDisk、Umouse、UKeyboard继承USB类,并且重写了loard()方法,程序运行结果是调用子类中的方法,输出UDisk、Umouse、UKeyboard的名称,这些即是多态的表现。不同的对象可以执行相同的行为,但是它们都需要通过自己的实现方式来执行,这就要得益于向上转型了。
实例4-8
程序说明:模拟游戏中攻击的设计,要求只要发出进攻的指令,士兵就用自动步枪射出子弹,坦克立即发出炮弹,歼击机发射导弹。
1 package duke.example.ch4;
2
3 public interface IFight { //定义进攻的指令
4 public void attack(String order); //攻击指令
5 }
6
7 public class Soldier implements IFight { //士兵实现进攻的指令
8 @Override
9 public void attack(String order) { //实现攻击指令
10 System.out.println(order+"发射子弹..."):
11 }
12 }
13
14 public class Tank implement IFight { //坦克实现进攻的指令
15 @Override
16 public void attack(String order) { //实现攻击指令
17 System.out.println(order+"发射炮弹...");
18 }
19 }
20
21 public class F35 implements IFight { //战斗机实现进攻的接口
22 @Override
23 public void attack(String order) { //实现攻击指令
24 System.out.println(order+"发射导弹...");
25 }
26 }
27
28 public class FightTest { //测试类
29 public static void main(String[] args) {
30 IFight soldier=new Soldier(); //父类引用指向子类对象
31 soldier.attack("海豹突击队");
32
33 IFight tank=new Tank(); //父类引用指向子类对象
34 tank.attack("M1A1新型主战坦克");
35
36 IFight f35=new F35(); //父类引用指向子类对象
37 f35.attack("F35隐身歼击机");
38 }
39 }
代码说明: 代码行.3-5中 创建一个IFight接口,定义攻击指令attack()方法。
代码行.7-12中 创建一个实现IFight接口的士兵类Soldier,重写攻击指令attack()方法。
代码行.14-19中 创建一个实现IFight接口的坦克类Tank,重写攻击指令attack()方法。
代码行.21-26中 创建一个实现IFight接口的战斗机类F35,重写攻击指令attack()方法。
代码行.28中 编写测试程序。
代码行.30中 父类IFight引用子类Soldier对象soldier。
代码行.33中 父类IFight引用子类Tank对象tank。
代码行.36中 父类IFight引用子类F35对象f35。
PS:这种父类引用指向子类对象可以降低程序的耦合性,被调用对象对于调用者完全是透明的。父类对象向下转型,它只能调用父类已存在的接口,子类可以对接口编程,有不同的实现,而父类对象不需要关心它的实现,便于程序的维护。
总结:这一部分主要记录了有关继承和多态的应用,记住区分类-抽象类-接口的区别。