1.引入
首先强化一下“多态”的概念
菜鸟教程上给出例子——
超棒的一个例子!!!看它!!
多态:同一个接口 不同的子类引用了这个接口(例如打印机)后
可以执行不同的操作(子类(例如彩色打印机和黑白打印机)进行不同的覆盖)
另外 多态的实现方式有下面这几种 我们这一章重点来看一下方法二:接口
之后来看看Head First Java对本章的引入~
继承只是个开始
-
要使用多态 我们还需要“接口”(不是GUI中的I——interface所代表的接口哦!)!
-
我们需要超越简单的继承并前进到更高的适应性和扩展性!——这需要我们设计和编写接口规格
-
很多Java功能如果没有接口就无法工作 因此就算我们不会去设计接口 也还是会使用到接口~
-
那么接口是什么呢?
——是一种100%纯抽象的类 -
那什么是抽象类呢?
——就是无法初始化的类 -
那抽象类又有什么作用啊?
——诶问题真多 下面慢慢讲解给你
上一章让Vet(兽医)的方法能运用Animal的所有子类只是多态的基本招式~接口才是多态和Java的重点!
2.设计继承
先来回顾一下最开始的Animal类
通过这个类 我们可以这样初始化类——
- 初始化引用变量的类型和新建对象的类型相同
Wolf aWolf = new Wolf();
- 初始化引用变量的类型和新建对象的类型不同
Animal aHippo = new Hippo();
好了 上面两种指令都是可以的 但是这样呢?——
Animal anim = new Animal();
如何处理这个问题?
我们要有Animal这个类来继承和产生多态 但是要限制只有Animal的子类才能被初始化——我们要的是Lion、Hippo对象 不是Animal对象!
当然有方法来解决这个问题了!
这个方法可以防止类被初始化!(防止类被new出来)
标记类为抽象类即可!!
这样编译器就知道不管在哪里 这个被抽象的类都是不能创建任何类型的实例了!
综上所述 我们设计好继承结构之后 必须要决定哪些类是抽象的 哪些是具体的!
具体的类是实际可以被初始化为对象的!(例如Hippo Lion这些)
如何设计抽象的类?
很简单~
在类的声明前面加上抽象类的关键词abstract
即可
abstract class Canine extends Animal {
public void main()
}
好!抽象类设计完成!!
从现在开始 编译器就不会让你初始化抽象类了!!
(当然了 我们还是可以使用抽象类来 声明为引用类型给多态使用 只不过不用担心这个抽象类被初始化了~)
c = new Canine()
直接报错hhh
3.抽象类与具体类
还是先来举个例子
我们创建出来的Animal Feline Canine就是抽象类~
其他的就是具体类
查阅Java API我们就会发现其中有很多的抽象类 特别是GUI的函数库中更多
举个例子 GUI的组件类是 按钮、滚动条 等与GUI有关的类的父类
而我们只会对组件类下的具体子类做初始化动作!
抽象的方法
不光有抽象的类 我们还可以有抽象的方法!
做个对比~
抽象的类代表这个类一定要继承过父类~
抽象的方法代表这个方法一定要被覆盖过
抽象的方法是没有实体的!
编写出抽象方法的程序代码是没有意义的!
如果我们声明出一个抽象的方法 就必须将类也标记为抽象的!
就算只有一个抽象的方法 这个类也必须标记为抽象的!
多态的使用
接下来举一个例子来深入了解下多态的使用!
假设我们不知道有ArrayList这种类
然而我们需要自行 编写&维护 list的类 来保存Dog对象
来看看我们一点点完善这个类的过程吧
【1】创建Dog对象专用的List
public class MyDogList {
private Dog [] dogs = new Dog[5];//实际上我们新建的是数组对象
private int nextIndex = 0;
public void add(Dog d) {
//先只写出add方法
//因为我们使用的大小为5的简单Dog数组来保存新加入的对象
//Dog对象超过五个时 我们调用add()什么都不会发生!
if (nextIndex < dogs.length) {
//可用索引nextIndex小于数组长度 就可以进行添加!
dogs[nextIndex] = d;
System.out.println("Dog added at" + nextIndex);
nextIndex ++;//递增计数
}
}
}
【2】Cat要用list咋办?
对鸭 Dog对象可以用MyDogList
那Cat用啥?
-
再写一个MyCatList类?
大可不必 -
创建一个单独的DogAndCatList类?
这样就要用addCat(Cat c)
addDog(Dog d)
分别处理两个不同的数组实例了 也不咋地!
正解是——
编写一个AnimalList
类让它处理Animal
的所有子类
public class MyAnimalList {
private Animal [] animals = new Animal[5];
//这可不是在创建Animal对象哦!只是一个保存Animal的数组对象而已!
private int nextIndex = 0;
public void add(Animal a) {
if (nextIndex < animals.length){
animals[nextIndex] = a;
System.out.println("Animal added at " + nextIndex);
nextIndex ++;
}
}
}
AnimalList类写完了!
来试着用add方法添加下Dog和Cat~
public class AnimalTestDrive {
public static void main(String[] args) {
MyAnimalList list = new MyAnimalList();
Dog d = new Dog();
Cat c = new Cat();
list.add(d);
list.add(c);
}
}
【3】如果添加进去的不是Animal呢?
对啊 写个万用类一劳永逸不好嘛?
在java中的所有类都是从Object(“对象之母”)这个类继承出来的!
可以看到ArrayList中的很多方法都用到了Object这个终极类型
因为每个类都是对象的子类,所以ArrayList可以处理任何类~
Object这个类是所有类的源头(是所有类的父类)
那你可能会问了 要这么个“对象之母”干啥子哦
如果Java中没有共同的父类 那将无法让Java的开发人员创建出可以处理自定义类型的类
eg:无法写出像ArrayList这样可以处理各种类的类
其实吧 所有的类都是从Object继承出来的
比如咱们写一个public class Dog
其实这个就等同于public class Dog extends Object()
你可能又问了 Dog类不是从Canine(犬科)类extends出来的么?
害 不成问题 编译器会知道如何改成让Canine去继承对象的!(事实上是Animal这个更大的类去进行继承)
4.宠物特性的设置
来举个很有意思的例子
我们想要在目前的Dog类 中 加入一些宠物特性
(即为修改合约)
但是这样还是有一些问题
我们想设置一个总的Pet类(宠物有太多太多啦)
但是怎么去做呢?
来看几种方法
【1】宠物方法加进Animal类中
来分别看看优缺点~
【2】把宠物方法加入到Animal类中 但是把宠物方法设定成抽象的 强迫每个动物子类覆盖它们
【3】把方法加到需要的地方
多重继承?
我们真正需要的方法是:
- 一种可以让宠物行为只应用在宠物身上的方法
- 一种确保所有宠物的类都有相同的方法定义的方法
- 一种可以运用到多态的方法
看起来我们需要搞两个上层的父类?
可恶 这个还是有个问题!
多继承的问题
多重继承 OK
多继承 不行!!
首先 Java就不支持这种方式
因为多继承会有称为“致命方块”的问题
总之就是不行就是了!!
接口是我们的救星!
Java对于我们上面说的“解决多继承”的问题
有一个解决方案——
使用接口
这个接口
- 不是GUI的接口
- 不是“沟通管道”的接口
- 不是“存取途径”的接口
我们说的这个“救星接口” 是Java的interface关键词
这个接口可以用来解决多重继承问题且不会产生致命方块这种问题!
接口解决致命方块的办法很简单:
把全部的方法设置为抽象的!
这样子类就得要实现此方法,因此Java虚拟机在执行期间就不会搞不清楚要用哪一个继承版本
5.接口
接口的定义和实现
定义:
public interface Pet {...}
接口的实现:
public class Dog extends Canine implements Pet {...}
//使用implements这个关键词
//注意到实现interface时 还是必须在某个类的继承下
【承接上个part】设计与实现Pet接口
设计一个简单的Pet接口
【1】先进行一个接口的定义
【2】再进行接口的实现
public interface Pet {
//接口的定义
public abstract void beFriendly();
public abstract void play();
//接口的方法一定是抽象的!它们是没有内容的!!!
}
public class Dog extends Canine implements Pet {
//接口的实现
public void beFriendly() {...}
public void play() {...}
//上面的两个方法是Pet的方法 一定要在大括号中实现!
//这个是合约的规定
public void roam() {...}
public void eat() {...}
//上面的两个是一般的覆盖方法
}
不同继承树的类也可以实现相同的接口
可以看到 我们如果想要定义一个动物&宠物 只需要让它继承Animal类和Pet接口就可以
同理 想要定义一个机器宠物 让它继承Robot类和Pet接口即可!
类可以实现多个接口。
更棒的是 我们的接口还可以让子类实现“多继承”
也就是让子类继承多个接口!
举个栗子
通过继承结构 Dog可以是个
Canine
可以是个Animal
也是一个(超类)Object
然而 Dog Is-A Pet 是通过接口实现的机制达成的
为啥用接口?接口可以实现多继承呐~
public class Dog extends Animal implements Pet, Saveable, paintable{...}
可以看到这里就是一个Dog子类实现了多个接口
来个小口诀
如何判断应该是设计类/子类/抽象类/接口
- 如果新的类无法对其他的类通过IS-A测试时,就设计不继承其他类的类
- 只有在需要其类的特殊化版本时,以覆盖或增加新的方法来继承现有的类
- 当你需要定义一群子类的模板 又不想让程序员初始化此模板时 设计出抽象的类给它们用
- 如果想要定义出类可以扮演的角色 使用接口
6.super的使用
调用父类的方法
来看看我们的需求——
- 创建出一个具体的子类
- 我们必须覆盖父类的某个方法A
- 我们同时需要执行父类封装的这个方法A
简单来说 我们不打算完全覆盖掉原来的方法 但是我们要加入额外的动作
使用super关键词
可以让我们在子类中调用父类的方法~
来举个栗子
先来看看父类的定义
abstract class Report {
void runReport() {
//设置报告方法~
}
void printReport() {
//输出报告的方法
}
}
之后我们的子类想要调用父类中的方法&自己加入新的方法
class BuzzwordsReport extends Report {
void runReport() {
super.runReport();//调用父类的方法
buzzwordCompliance();//新加入的方法
printReport();//调用父类的方法
//(但是与新增方法不名~所以不用加super)
}
void buzzwordCompliance() {...}
//子类新增的方法
}
仔细康康上面的代码
子类中我们指定super.runReport
这个命令 父类的方法就会被执行
从上面的关系图中我们可以看出来 子类的方法包含了父类的这些方法
虽然我们对子类的对象进行调用会执行子类覆盖过的方法
BuzzwordReport 子类对象 = new BuzzwordReport();
子类对象.runReport();//这样调用 会执行子类覆盖过的方法
但是!我们用了super.runReport()
所以在子类中可以调用父类的方法
7.本章小结!
标星号“*”的内容是有待进一步理解的!
【1】如果不想让某个类被初始化 就用abstract
这个关键词将它标记为抽象的
比如我们不想创建一个Animal类型的animal
【2】抽象的类可以带有抽象&非抽象的方法
记住一点 抽象方法所在的类一定是抽象类!
也就是说这个类必须这么标识——
abstract class Animal() {...}
Animal即为类名
【3】抽象的方法没有内容 它的声明是以分号结束滴~
abstract class Pet() {
public abstract void beFriendly();
public abstract void play();
//这些方法是木有内容滴~
}
【4】“抽象”的方法必须在“具体”的类中运行
【5】Java所有的类都是Object(java.lang.Object)直接或间接的子类
定义类的时候可以省去 但是其实是默认存在
extends Object
的~
【6】方法可以声明Object的参数或返回类型
*【7】不管实际上引用的对象是什么类型 只有在引用变量的类型是带有方法A时才可以调用这个方法A
*【8】Object引用变量在没有类型转换的情况下不能赋值给其他的类型,若堆上的对象类型与所要转换的类型不兼容 则这个转换会在执行期产生异常
这个真的理解不了哇 回头多学一些再来看这个
*【9】从ArrayList<Object>
取出的对象只能被Object引用 不然就要用类型转换来改变
取出的对象?
emmmm
这个要再去找找例子!
【10】Java不允许多重继承 因为那样会有致命方块的问题
所以需要接口嘛~
【11】接口就好像是100%纯天然的抽象类!
【12】以interface这个关键词取代class来声明接口
public interface Pet() {
public abstract beFriedly();
public abstract play();
}
【13】实现接口时要使用implements这个关键词
public class Dog extends Canine implements Pet {
public void beFriedly() {......};
public void play() {......};
//这两个方法一定要在大括号中实现
}
【14】class可以实现多个接口
public class Dog extneds Animal implements Pet, Sveable, paintable {
...
}
【15】实现某个接口的类必须实现它所有的方法 因为这些方法都是 public 与 abstract 的
【16】要在子类中调用到父类的方法(当子类中要覆盖的方法恰好与父类是同一个 但是同时又要用到父类方法的时候)时,可以使用super这个关键词来引用
//这是子类的一个方法(与父类同名~)
void runReport() {
super.runReport();//调用父类的方法
printReport();//调用父类的方法
//(但是与新增方法不名~所以不用加super)
}