再谈抽象类(感觉理解的更深了)
抽象类其实一直都是一个比较困扰我的问题,自己上网看资料看书,最后总是感觉理解的不是很深,直到今天听了王克晶老师讲抽象类,感觉豁然开朗,不得不承认讲的真的太清楚了。
首先,我们需要知道为啥要有抽象类,学了这10几天Java,我已经明显的感觉到,Java中的各种语法、结构很多都是为了简化代码的重复性,而抽象类真的大大大大大地提升了这种复用性,废话不多说,赶紧把我今天听的东西记录一下。
下面我循序渐进地把抽象类引出来。
首先,要先说抽象方法,抽象方法是用abstrat修饰的、没有方法体的方法,抽象方法存在的意义一定是要把它写在父类里,一定要用子类去实现,这个不仅仅是语法规定(子类不去实现父类的抽象方法会报错的,除非子类也是抽象类),更重要的抽象类的意义!!!抽象类最重要的作用就是:为所有派生类提供统一的入口,派生类的实现可以不同,但是其入口是一致的!!关于这个加粗红色的部分,后面会慢慢解释。
1 class FlyingObject{ 2 int x;//飞行物横坐标 3 int y;//飞行物纵坐标 4 } 5 class Airplane extends FlyingObject{ 6 void step() { 7 y++;//小飞机纵向走 8 } 9 } 10 class BigPlane extends FlyingObject{ 11 void step() { 12 x++;//大飞机横向走 13 } 14 } 15 class Bee extends FlyingObject{ 16 void step() { 17 x++;//蜜蜂横向纵向都走 18 y++; 19 } 20 }
看上面这个代码,是飞机大战的几个类,第一个是飞行物,是超类,是整个天空所有飞行物的父类,后面三个一次是小飞机、大飞机和小蜜蜂,都是飞行物的子类。在这三个子类里都有step()这个方法,三个方法都是使得对象在天空的坐标发生变化的方法,但是三个方法的方法体不同,这样我们想要三个对象调用step()方法,我们就必须在主方法里这样写。
1 public class AbstractDemo { 2 public static void main(String[] args) { 3 Airplane airplane=new Airplane(); 4 airplane.step(); 5 BigPlane bigPlane=new BigPlane(); 6 bigPlane.step(); 7 Bee bee=new Bee(); 8 bee.step(); 9 } 10 }
要先去一个一个new三个类的对象,然后再去调用。那么问题来了,对于我们的主角英雄机而言,小飞机、大飞机和小蜜蜂都是敌人,而且这三个类都是飞行物的子类,我们为什么不可以通过向上造型创建一个FlyingObject的数组去把这些敌人都装进去呢,这样之后对敌人都可以进行统一操作了!答案当然是可以的,我们可以这样做。
1 public class AbstractDemo { 2 public static void main(String[] args) { 3 FlyingObject[] enemy=new FlyingObject[3]; 4 enemy[0]=new Airplane(); 5 enemy[1]=new BigPlane(); 6 enemy[2]=new Bee(); 7 } 8 }
这样一写,通过向上造型,创建了一个飞行物的数组,里面有小飞机、大飞机、小蜜蜂各种敌人,最关键的,放进数组后可以进行统一操作了。
那么问题来了,这个时候,想让他们动起来怎么办呢?
如果写:
1 for(int i=0;i<enemy.length;i++) 2 enemy[i].step();
这样是会报错的!提示step()这个方法未定义!
对啊!因为enemy是超类的引用数组,而step()这个方法是子类的,当然不能通过enemy[i]去调用step()方法了。
那怎么办呢?
这个时候,抽象类就应运而生了!!
在超类FlyingObject里这样写:
1 abstract class FlyingObject{ 2 int x;//飞行物横坐标 3 int y;//飞行物纵坐标 4 abstract void step(); 5 }
在这个类里,加入step()方法,但是并没有方法体,因为很显然啊,我们这个时候并不想在父类里实现step()方法,step()方法对每个子类都不同啊,真正要去调用它的时候我们都是根据不同的子类去调用各自不同的step()方法啊,所以我们在超类里不能也不需要去写它的方法体,我们仅仅是想在超类里为各个子类提供一个step()方法的接口,这样就可以通过enemy[i]这个超类对象去调用step()方法了。
因为没有方法体,所以这个方法不能属于普通成员方法,Java为我们提供了abstract关键字,将它视为抽象方法,就可以了。但是抽象方法所在的类必须为抽象类,因此在类名前也需要加入abstract关键字。
那么问题来了,
1 public class AbstractDemo { 2 public static void main(String[] args) { 3 FlyingObject[] enemy=new FlyingObject[3]; 4 enemy[0]=new Airplane(); 5 enemy[1]=new BigPlane(); 6 enemy[2]=new Bee(); 7 for(int i=0;i<enemy.length;i++) 8 enemy[i].step(); 9 } 10 }
第8行的这个enemy[i].step()如何判断它调用哪个子类的step()呢?
这个问题问的非常好!
很简单!当然是看enemy[i]里装着的具体是哪个对象,这是向上造型的知识。向上造型,引用能不能.出来方法,要看这个父类里有没有这个方法,.出来这个方法,具体去调用哪个方法,要看这个引用具体指向哪个对象,而我们用抽象类,都是用子类去实现父类的方法,其实也相当于把父类的抽象方法重写(覆盖)了,我们要去调用的应该是重写后的方法!
总结一点,向上造型,能不能.出来方法是看引用的类型,.出来之后具体调用哪个类的方法要看对象!
这样我们通过抽象类和抽象方法,就将所有的敌人都装进了enemy这个数组中,并且可以直接调用子类的方法,还可以对enemy进行统一操作!代码简化很多!复用性的魅力体现的淋漓尽致有木有!!
再回来看开头抽象类的意义:为所有派生类提供统一的入口,派生类的实现可以不同,但是其入口是一致的!!
现在应该就很明白了!
这下大家就明白抽象类的作用了吧,这种知其然还知其所以然的感觉真的很好!