@Tean
2016-04-18T00:48:22.000000Z
字数 5553
阅读 1253
Java
- 重点掌握多态
- 重点掌握对象转型
- 重点掌握抽象类和抽象方法
- 重点掌握接口
在Java中一个引用的类型会有两种类型:
1. 编译时类型 :由声明该变量时使用的类型决定(赋值运算符左边)
2. 运行时类型 :实际赋给该变量的对象类型(赋值运算符右边)
如果编译时类型和运行时类型不一样,就会出现所谓的多态。
/** Animal => 编译时类型* Dog => 运行时类型*/Animal animal = new Dag(); // 多态应用
可以这么理解:定义一个引用变量,编译时是否能够通过要看赋值运算符的左边。运行时的实际表现要看赋值运算符的右边。
多态是Java面向对象的三大特征(继承、封装、多态)之一,是提供程序可拓展性的有力保障之一。
多态:同一个引用类型,使用不同的实例执行不同的操作。
public class Pet {public void speak() {System.out.println("宠物在叫");}}class Dog extends Pet {public void speak() {System.out.println("汪~");}}class Cat extends Pet {public void speak() {System.out.println("瞄~");}}class Test {public static void main(String[] args) {Pet pet = new Dog();pet.speak(); // 汪~pet = new Cat();pet.speak(); // 瞄~}}
- Java里对象的转型有两种:
向上转型和向下转型。- 向上转型:
由于子类可以看成一个特殊的父类,所以一个父类对象的引用可以直接指向子类对象,即把子类转换为父类,我们称之为向上转型(自动类型转换)。- 向下转型:
由于引用数据类型的变量只能调用他的编译时类型的方法,而不能调用他运行时类型的方法,尽管它实际运行的对象包括该方法。如果想要该引用变量调用他运行时的方法,则需要把该变量强制转换成他运行时的类型。即把父类转换为子类,我们称之为向下转型(强制类型转换)。
class Animal {public void speak() {System.out.println("我是一个动物");}}class Cat extends Animal {public void speak() {System.out.println("我是一只猫");}// Cat类新增的捉老鼠的方法public void catchMouse() {System.out.println("我是猫,所以我会捉老鼠");}}public class Test {public static void main(String[] args) {// Animal:编译时类型 Cat:运行时类型Animal animal = new Cat(); // 向上转型animal.speak(); // 因为运行时类型是Cat,所有实际输出的是Cat类中的speak方法animal.catchMouse(); // 编译出错,因为编译时类型为Animal,而Animal类中没有catchMouse方法Cat cat = (Cat)animal; // 把animal向下转型为Catcat.catchMouse();}}
向下转型(强制类型转换)时,如果操作不当就回出现
类型转换异常(ClassCastException)。例如:
Animal animal = new Dog();Penguin penguin = (Penguin)animal; // 运行时产生ClassCastException异常
上述代码因为animal实际是Dog类型,此时把Dog类型强制转换为Penguin时就回产生异常,因此为了增强代码的健壮性,Java中引入
instanceof运算符。该运算符判断一个对象是否为某个类型的实例。
Animal animal = new Dog();if(animal instanceof Penguin) {Penguin penguin = (Penguin)animal;}
需求
- 宠物系统中:Pet、Dog、Penguin
- 宠物生病了,需要主人给宠物看病;不同的宠物,看病过程不一样,恢复的健康值也不一样:
- 狗狗:打针、吃药 (健康值 + 20)
- 企鹅:吃药、疗养 (健康值 + 30)
分析:
- 编写主人类
- 编写给狗狗看病的方法
- 编写给Q仔看病的方法
- 编写测试方法
- 调用主人类给狗狗看病的方法
- 调用主人类给Q仔看病的方法
问题:
- 如果又需要给XXX看病,怎么办?
- 添加XXX类,继承Pet类
- 修改Master类,添加给XXX看病的方法
频繁修改代码,代码可扩展性、可维护性差,我们使用多态来优化设计。
练习一
- 用多态实现打印机
- 分为黑白打印机和彩色打印机
- 不同类型的打印机打印效果不同
练习二
- 需求说明:
- 主人和狗狗玩接飞盘游戏,狗狗健康值减少10,与主人亲密度增加5
- 主人和企鹅玩游泳游戏,企鹅健康值减少10,与主人亲密度增加5
- 提示:
- Dog类添加catchingFlyDisc()方法,实现接飞盘功能
- Penguin类添加swimming()方法,实现游泳功能
- 主人添加play(Pet pet)方法
- 如果pet代表Dog就玩接飞盘游戏
- 如果pet代表Penguin就玩游泳游戏
有些类没有必要实例化,或者实例化之后没有实在意义,或者类中的某些方法不知道如何实现,只能等到子类进行实现,这些情况下我们应该禁止对该对象进行实例化。
如果想禁止该类在外界进行实例化,可以有两种方式:
1、所有构造方法私有。会导致该类既不能被实例化,也不能被继承,该类基本也就废了。没有多大意义。
2、可以把该类标记为抽象类。那么该类则无法在外界进行实例化,但是可以通过继承该类,来实例化该类的子类。
public class Pet {public void eat() { }}public class Dog extends Pet {public void eat() {System.out.println("啃骨头");}}.../** 下述代码没意义,Pet只是我们抽象出来的父类,* 实例化没有任何意义,包括完成Pet中eat()方法体也是没有什么意义,* 所以我们可以把Pet定义成一个抽象类,把eat定义成一个抽象方法。*/{Pet pet = new Pet();}
- 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做
抽象类。- 用
abstract关键字来修饰一个类时,这个类叫做抽象类。
语法:
访问修饰符 abstract class 类名 { // 访问修饰符和abstract的位置可以互换// TODO ...}
- 用
abstract来修饰一个方法时,该方法叫做抽象方法。- 抽象方法:只有方法的声明,没有方法的实现。以分号结束。
语法:
修饰符 abstract 返回值类型 方法名(参数列表);// demopublic abstract void eat(); // 没有方法体
注意:
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类中的抽象方法必须在子类中实现。
- 抽象类不能实例化。
- 不能用abstract修饰私有方法、构造方法、静态方法。
- 抽象类中可以不包括抽象方法。
思考:
- 构造方法,静态方法,public方法,protected方法,default方法,private方法,final方法能不能被声明为抽象的方法?
- 属性能否用abstract来修饰?
- 抽象类中可以有普通方法吗?
- 抽象类中可以有构造方法吗?
- 抽象类中可以有static方法吗?
- 抽象类中可以继承抽象类吗?
- 抽象类中可以继承普通类吗?
- 抽象类可以理解为是从多个类中抽象出来的模板,不但允许有抽象方法,还可以有变量和普通方法等。如果抽象的更彻底,只留下常量和抽象方法,则此时我们就可以用
接口取代这样的抽象类。- 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的声明,而没有变量和方法的实现。
- 使用接口的好处是:
- 可以被多继承
- 设计和实现完全分离
- 更自然的使用多态
- 更容易搭建程序框架
- 更容易更换实现
- ...
语法:
[访问修饰符] interface 接口名 [extends 父接口1, 父接口2,...] {/* 常量定义 和 抽象方法 */double PI = 3.1415926; // 必须赋值,系统会自动添加public static finalpublic static final double PI = 3.1415926; // 与上面定义的效果一样void say(); // 可以省略 public abstractpublic abstact void say(); // 和上述声明一样String getInfo(String name); // 可以传参数和有返回值}
说明:
- 访问修饰符,和class的访问修饰符相同,只有
public和default两种。如果是default,则对同一包内其他类或接口可见。interface标示接口的关键字。- extends 后面跟接口列表,表示该接口继承的父接口。接口只能继承接口,不能继承类。多个父接口之间用英文的”
,”(逗号)隔开。- 声明常量时可以像声明变量一样声明,但是系统会自动把该变量转成常量。
- 声明抽象方法。修饰符可以完全省略,系统会默认接口中的所有方法均是
public abstract的,如果一定要添加修饰符,只能添加public和abstract。
阅读代码,找出错误:
public interface MyInterface {public MyInterface();public void method1();public void method2() { }private void method3();void method4();int method5();int TYPE = 1;double HEIGHT;}
- 由于接口只有抽象方法,所以接口与抽象类一样不能直接创建对象。我们一般说抽象类的子类,说接口的实现类。
- 一个类实现接口使用关键字
implements。语法格式:
修饰符 class 类名 implements 接口1, 接口2, … {// 实现接口中的所有方法}
说明:
- 实现接口必须使用关键字
implements。- 一个类可以实现多个接口,多个接口之间使用英文逗号隔开。
- 类实现接口支持多实现。实现类必须实现所有接口的所有抽象方法,如果不能完全实现,则该类必须标记为抽象类。
- 一个类实现多个接口的同时,还可以来继承一个类。继承类的时候写法是:必须先写继承,再写实现。
案例:
// 接口public interface Runner {void start();void run();void stop();}// 实现类public class implements Runnber {public void start() {System.out.println("弯腰、蹬腿、咬牙、瞪眼、开跑");}public void run() {System.out.println("摇摆手臂、维持直线方向");}public void stop() {System.out.println("减速直至停止、瘫倒、吐血、送医院");}}
- 通过接口实现不相关类的相同行为,而无需考虑这些类之间的关系.
- 通过接口指明多个类需要实现的方法
- 通过接口了解对象的交互界面,而无需了解对象所对应的类
- 使用接口,具体代码可以不依赖具体实现类,当具体实现类有变化时,代码不需调整。使用接口使软件更容易扩展。
面向对象的最高境界,是面向接口编程!!!
| No. | 比较点 | 抽象类 | 接口 |
|---|---|---|---|
| 1 | 定义 | 用abstract修饰的类 | 静态常量和抽象方法的集合 |
| 2 | 组成 | 抽象方法,普通方法,构造方法,成员变量 | 抽象方法,静态常量 |
| 3 | 使用 | 子类继承(extends) | 实现类实现(implements) 子接口继承(extends)父接口 |
| 4 | 关系 | 抽象类可以实现接口 | 接口不能继承抽象类 |
| 5 | 对象 | 都是通过对象的多态性来实现的 | |
| 6 | 局限 | 不能多继承,不能实例化 | 一个接口可以有多个实现类,一个类可以实现多个接口,一个接口可以继承多个接口,接口不能实例化 |
| 7 | 选择 | 建议选择接口,避免单继承 | |
| 8 | 实际应用 | 模板,有些方法无法在定义时实现或故意留给子类实现 | 制定规范,标准 |
- 西游记中,3个徒弟,共同的方法(吃斋,念佛,取经),孙悟空自己的方法(除妖),猪八戒自己的方法(牵马),沙和尚自己的方法(挑行李)。
- 构造两个接口,A和B,分别包含有两个不同的函数,使用B接口继承A接口。创建一个具体类,实现B接口,在类中只实现B接口中所定义的方法,尝试编译,并观察编译信息。
- 创建一个接口(接口中包含fun1()和fun2()两个函数)和实现这个接口的类。在类中添加一个fun3()方法,使用new关键字创建一个类的对象,并把这个对象赋给接口类型的引用,使用这个引用调用fun3(),观察编译结果。
- 将3题中的接口换成抽象类,重复3题的试验。
- 定义一个抽象类,在类中定义一个抽象方法,创建一个继承此抽象类的子类,在此类中除了类的定义以外不写任何代码,尝试编译子类,观察编译器所显示的错误信息。
- 在第5题的子类中复写抽象方法,再次编译子类。
- 定义接口Shape,用来表示一般二维图像。Shape具有抽象方法area和perimeter,分别用来计算形状的面积和周长。定义一些二维形状类(矩形<Square>、三角形<Triangle>、圆形<Circle>)这些类均为Shape接口的实现类。并测试验证。