[关闭]
@Tean 2016-04-18T00:48:22.000000Z 字数 5553 阅读 1253

Day09_面向对象(四)

Java


学习目标

  • 重点掌握多态
  • 重点掌握对象转型
  • 重点掌握抽象类和抽象方法
  • 重点掌握接口

一、多态

1.1 什么是多态

在Java中一个引用的类型会有两种类型:
1. 编译时类型 :由声明该变量时使用的类型决定(赋值运算符左边
2. 运行时类型 :实际赋给该变量的对象类型(赋值运算符右边
如果编译时类型和运行时类型不一样,就会出现所谓的多态。

  1. /*
  2. * Animal => 编译时类型
  3. * Dog => 运行时类型
  4. */
  5. Animal animal = new Dag(); // 多态应用

可以这么理解:定义一个引用变量,编译时是否能够通过要看赋值运算符的左边。运行时的实际表现要看赋值运算符的右边。

  1. 多态是Java面向对象的三大特征(继承、封装、多态)之一,是提供程序可拓展性的有力保障之一。

多态:同一个引用类型,使用不同的实例执行不同的操作。

  1. public class Pet {
  2. public void speak() {
  3. System.out.println("宠物在叫");
  4. }
  5. }
  6. class Dog extends Pet {
  7. public void speak() {
  8. System.out.println("汪~");
  9. }
  10. }
  11. class Cat extends Pet {
  12. public void speak() {
  13. System.out.println("瞄~");
  14. }
  15. }
  16. class Test {
  17. public static void main(String[] args) {
  18. Pet pet = new Dog();
  19. pet.speak(); // 汪~
  20. pet = new Cat();
  21. pet.speak(); // 瞄~
  22. }
  23. }

1.2 对象转型

  • Java里对象的转型有两种:向上转型向下转型
  • 向上转型:
    由于子类可以看成一个特殊的父类,所以一个父类对象的引用可以直接指向子类对象,即把子类转换为父类,我们称之为向上转型(自动类型转换)。
  • 向下转型:
    由于引用数据类型的变量只能调用他的编译时类型的方法,而不能调用他运行时类型的方法,尽管它实际运行的对象包括该方法。如果想要该引用变量调用他运行时的方法,则需要把该变量强制转换成他运行时的类型。即把父类转换为子类,我们称之为向下转型(强制类型转换)。
  1. class Animal {
  2. public void speak() {
  3. System.out.println("我是一个动物");
  4. }
  5. }
  6. class Cat extends Animal {
  7. public void speak() {
  8. System.out.println("我是一只猫");
  9. }
  10. // Cat类新增的捉老鼠的方法
  11. public void catchMouse() {
  12. System.out.println("我是猫,所以我会捉老鼠");
  13. }
  14. }
  15. public class Test {
  16. public static void main(String[] args) {
  17. // Animal:编译时类型 Cat:运行时类型
  18. Animal animal = new Cat(); // 向上转型
  19. animal.speak(); // 因为运行时类型是Cat,所有实际输出的是Cat类中的speak方法
  20. animal.catchMouse(); // 编译出错,因为编译时类型为Animal,而Animal类中没有catchMouse方法
  21. Cat cat = (Cat)animal; // 把animal向下转型为Cat
  22. cat.catchMouse();
  23. }
  24. }

1.3 instanceof运算符

向下转型(强制类型转换)时,如果操作不当就回出现类型转换异常(ClassCastException)。例如:

  1. Animal animal = new Dog();
  2. Penguin penguin = (Penguin)animal; // 运行时产生ClassCastException异常

上述代码因为animal实际是Dog类型,此时把Dog类型强制转换为Penguin时就回产生异常,因此为了增强代码的健壮性,Java中引入instanceof运算符。该运算符判断一个对象是否为某个类型的实例。

  1. Animal animal = new Dog();
  2. if(animal instanceof Penguin) {
  3. Penguin penguin = (Penguin)animal;
  4. }

1.4 多态应用案例

需求

  • 宠物系统中:PetDogPenguin
  • 宠物生病了,需要主人给宠物看病;不同的宠物,看病过程不一样,恢复的健康值也不一样:
    • 狗狗:打针、吃药 (健康值 + 20)
    • 企鹅:吃药、疗养 (健康值 + 30)

分析:

  • 编写主人类
    • 编写给狗狗看病的方法
    • 编写给Q仔看病的方法
  • 编写测试方法
    • 调用主人类给狗狗看病的方法
    • 调用主人类给Q仔看病的方法

问题:

  • 如果又需要给XXX看病,怎么办?
    • 添加XXX类,继承Pet类
    • 修改Master类,添加给XXX看病的方法
  1. 频繁修改代码,代码可扩展性、可维护性差,我们使用多态来优化设计。

1.5 练习

练习一

  • 用多态实现打印机
    • 分为黑白打印机和彩色打印机
    • 不同类型的打印机打印效果不同

练习二

  • 需求说明:
    • 主人和狗狗玩接飞盘游戏,狗狗健康值减少10,与主人亲密度增加5
    • 主人和企鹅玩游泳游戏,企鹅健康值减少10,与主人亲密度增加5
  • 提示:
    • Dog类添加catchingFlyDisc()方法,实现接飞盘功能
    • Penguin类添加swimming()方法,实现游泳功能
    • 主人添加play(Pet pet)方法
      • 如果pet代表Dog就玩接飞盘游戏
      • 如果pet代表Penguin就玩游泳游戏

二、抽象类和抽象方法

2.1 为什么需要抽象类

有些类没有必要实例化,或者实例化之后没有实在意义,或者类中的某些方法不知道如何实现,只能等到子类进行实现,这些情况下我们应该禁止对该对象进行实例化。
如果想禁止该类在外界进行实例化,可以有两种方式:
1、所有构造方法私有。会导致该类既不能被实例化,也不能被继承,该类基本也就废了。没有多大意义。
2、可以把该类标记为抽象类。那么该类则无法在外界进行实例化,但是可以通过继承该类,来实例化该类的子类。

  1. public class Pet {
  2. public void eat() { }
  3. }
  4. public class Dog extends Pet {
  5. public void eat() {
  6. System.out.println("啃骨头");
  7. }
  8. }
  9. ...
  10. /*
  11. * 下述代码没意义,Pet只是我们抽象出来的父类,
  12. * 实例化没有任何意义,包括完成Pet中eat()方法体也是没有什么意义,
  13. * 所以我们可以把Pet定义成一个抽象类,把eat定义成一个抽象方法。
  14. */
  15. {
  16. Pet pet = new Pet();
  17. }

2.2 抽象类

  • 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
  • abstract关键字来修饰一个类时,这个类叫做抽象类。

语法:

  1. 访问修饰符 abstract class 类名 { // 访问修饰符和abstract的位置可以互换
  2. // TODO ...
  3. }

2.3 抽象方法

  • abstract来修饰一个方法时,该方法叫做抽象方法。
  • 抽象方法:只有方法的声明,没有方法的实现。以分号结束。

语法:

  1. 修饰符 abstract 返回值类型 方法名(参数列表);
  2. // demo
  3. public abstract void eat(); // 没有方法体

注意:

  • 含有抽象方法的类必须被声明为抽象类。
  • 抽象类中的抽象方法必须在子类中实现。
  • 抽象类不能实例化。
  • 不能用abstract修饰私有方法、构造方法、静态方法。
  • 抽象类中可以不包括抽象方法。

思考:

  • 构造方法,静态方法,public方法,protected方法,default方法,private方法,final方法能不能被声明为抽象的方法?
  • 属性能否用abstract来修饰?
  • 抽象类中可以有普通方法吗?
  • 抽象类中可以有构造方法吗?
  • 抽象类中可以有static方法吗?
  • 抽象类中可以继承抽象类吗?
  • 抽象类中可以继承普通类吗?

三、接口

3.1 接口的概念

  • 抽象类可以理解为是从多个类中抽象出来的模板,不但允许有抽象方法,还可以有变量和普通方法等。如果抽象的更彻底,只留下常量和抽象方法,则此时我们就可以用接口取代这样的抽象类。
  • 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的声明,而没有变量和方法的实现。
  • 使用接口的好处是:
    • 可以被多继承
    • 设计和实现完全分离
    • 更自然的使用多态
    • 更容易搭建程序框架
    • 更容易更换实现
    • ...

3.2 接口的声明

语法:

  1. [访问修饰符] interface 接口名 [extends 父接口1, 父接口2,...] {
  2. /* 常量定义 和 抽象方法 */
  3. double PI = 3.1415926; // 必须赋值,系统会自动添加public static final
  4. public static final double PI = 3.1415926; // 与上面定义的效果一样
  5. void say(); // 可以省略 public abstract
  6. public abstact void say(); // 和上述声明一样
  7. String getInfo(String name); // 可以传参数和有返回值
  8. }

说明:

  • 访问修饰符,和class的访问修饰符相同,只有publicdefault两种。如果是default,则对同一包内其他类或接口可见。
  • interface 标示接口的关键字。
  • extends 后面跟接口列表,表示该接口继承的父接口。接口只能继承接口,不能继承类。多个父接口之间用英文的”,”(逗号)隔开。
  • 声明常量时可以像声明变量一样声明,但是系统会自动把该变量转成常量。
  • 声明抽象方法。修饰符可以完全省略,系统会默认接口中的所有方法均是public abstract的,如果一定要添加修饰符,只能添加publicabstract

阅读代码,找出错误:

  1. public interface MyInterface {
  2. public MyInterface();
  3. public void method1();
  4. public void method2() { }
  5. private void method3();
  6. void method4();
  7. int method5();
  8. int TYPE = 1;
  9. double HEIGHT;
  10. }

3.3 接口的实现

  • 由于接口只有抽象方法,所以接口与抽象类一样不能直接创建对象。我们一般说抽象类的子类,说接口的实现类。
  • 一个类实现接口使用关键字implements。语法格式:
  1. 修饰符 class 类名 implements 接口1, 接口2, {
  2. // 实现接口中的所有方法
  3. }

说明:

  1. 实现接口必须使用关键字implements
  2. 一个类可以实现多个接口,多个接口之间使用英文逗号隔开。
  3. 类实现接口支持多实现。实现类必须实现所有接口的所有抽象方法,如果不能完全实现,则该类必须标记为抽象类。
  4. 一个类实现多个接口的同时,还可以来继承一个类。继承类的时候写法是:必须先写继承,再写实现。

案例:

  1. // 接口
  2. public interface Runner {
  3. void start();
  4. void run();
  5. void stop();
  6. }
  7. // 实现类
  8. public class implements Runnber {
  9. public void start() {
  10. System.out.println("弯腰、蹬腿、咬牙、瞪眼、开跑");
  11. }
  12. public void run() {
  13. System.out.println("摇摆手臂、维持直线方向");
  14. }
  15. public void stop() {
  16. System.out.println("减速直至停止、瘫倒、吐血、送医院");
  17. }
  18. }

3.4 接口的用途

  • 通过接口实现不相关类的相同行为,而无需考虑这些类之间的关系.
  • 通过接口指明多个类需要实现的方法
  • 通过接口了解对象的交互界面,而无需了解对象所对应的类
  • 使用接口,具体代码可以不依赖具体实现类,当具体实现类有变化时,代码不需调整。使用接口使软件更容易扩展。
  1. 面向对象的最高境界,是面向接口编程!!!

3.5 接口和抽象类的区别

No.比较点抽象类接口
1定义用abstract修饰的类静态常量和抽象方法的集合
2组成抽象方法,普通方法,构造方法,成员变量抽象方法,静态常量
3使用子类继承(extends)实现类实现(implements)
子接口继承(extends)父接口
4关系抽象类可以实现接口接口不能继承抽象类
5对象都是通过对象的多态性来实现的
6局限不能多继承,不能实例化一个接口可以有多个实现类,一个类可以实现多个接口,一个接口可以继承多个接口,接口不能实例化
7选择建议选择接口,避免单继承
8实际应用模板,有些方法无法在定义时实现或故意留给子类实现制定规范,标准

四、作业

  1. 西游记中,3个徒弟,共同的方法(吃斋,念佛,取经),孙悟空自己的方法(除妖),猪八戒自己的方法(牵马),沙和尚自己的方法(挑行李)。
  2. 构造两个接口,A和B,分别包含有两个不同的函数,使用B接口继承A接口。创建一个具体类,实现B接口,在类中只实现B接口中所定义的方法,尝试编译,并观察编译信息。
  3. 创建一个接口(接口中包含fun1()和fun2()两个函数)和实现这个接口的类。在类中添加一个fun3()方法,使用new关键字创建一个类的对象,并把这个对象赋给接口类型的引用,使用这个引用调用fun3(),观察编译结果。
  4. 将3题中的接口换成抽象类,重复3题的试验。
  5. 定义一个抽象类,在类中定义一个抽象方法,创建一个继承此抽象类的子类,在此类中除了类的定义以外不写任何代码,尝试编译子类,观察编译器所显示的错误信息。
  6. 在第5题的子类中复写抽象方法,再次编译子类。
  7. 定义接口Shape,用来表示一般二维图像。Shape具有抽象方法area和perimeter,分别用来计算形状的面积和周长。定义一些二维形状类(矩形<Square>、三角形<Triangle>、圆形<Circle>)这些类均为Shape接口的实现类。并测试验证。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注