![Java无难事:详解Java编程核心思想与技术](https://wfqqreader-1252317822.image.myqcloud.com/cover/59/35011059/b_35011059.jpg)
6.1 抽象方法和抽象类
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_1.jpg?sign=1739897340-TELCvBHZ48hRcoi3od5qBxLtA1GBTswV-0-e11755922ddf2511154ba71bf484fa6c)
扫码看视频
在设计动物基类Animal时,考虑到不同的动物的睡觉方式不一样,比如一般动物躺着睡觉,而马站着睡觉,所以在设计sleep方法时,我们给sleep方法添加一个abstract关键字来声明该方法是一个抽象的方法,如代码6.1所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_2.jpg?sign=1739897340-C8lqvqhN04VcvC7s5nYjG9I4arsCz2Rg-0-a1fd14d5a37086cc44fe111748785629)
可以看到,抽象方法就是使用abstract关键字声明的没有方法体的方法。
编译Animal.java,编译器提示如图6-1所示的错误。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_3.jpg?sign=1739897340-BZF3ezJSfwstGTTTk84ay56Afr7Utho1-0-b5da2e602459ef35fd2b504a8140a4cd)
图6-1 具体类中包含了抽象方法编译报错
Horse类继承自Animal类,并且也覆盖了抽象方法sleep,那为什么还会报错呢?错误的真正原因是:sleep是抽象方法,因此该方法所在的类必须声明为抽象类,即用关键字abstract声明类。为什么要这样做呢?这是为了避免当实例化一个含有抽象方法的类的对象时,若类中含有抽象方法,则意味着该种行为是不确定的。如果允许你实例化该类的对象,那么当你调用对象的抽象方法时,应该给出什么样的表现行为呢?
修改代码6.1,将Animal类声明为抽象的,如代码6.2所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_4.jpg?sign=1739897340-gA62Bs8bINFvA05eXOeX5YDAqLFmg4sU-0-7784f2a06f13413fe3fa9e154e0050f2)
注意,abstract关键字,要放在class关键字之前,一般是放在public访问说明符之后。若将类声明为抽象的,则该类将无法被实例化。
再次编译Animal.java并执行Horse,可以看到一切正常。
修改代码6.2,将覆盖的方法sleep注释起来,如代码6.3所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_5.jpg?sign=1739897340-DurZsEibIMkq5nxQ7vY8hwxN6uKqDvs0-0-5a6a3ffb18086893e89e66a60fb17adb)
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_6.jpg?sign=1739897340-uBSPDTbPYHBI6G5wAgUtZFb11bW0P7MX-0-14a3d3db365b81e9c1e5dcc088f76a53)
编译Animal.java,编译器提示如图6-2所示的错误。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_7.jpg?sign=1739897340-oYWRpQZmiRh37vmD6XUfYxTg3ccotQvz-0-df46f7aacc1f6bbf42500bc4c4a40faf)
图6-2 子类未重写抽象父类中的抽象方法而报错
由于Horse类继承自Animal类,但未覆盖Animal类的抽象方法sleep,因此Horse类的实现也是不完整的,不能用于实例化对象。为了保证非完整实现类不能产生对象,Java编译器要求将类声明为抽象的,也就是说,如果一个子类没有实现抽象基类中所有的抽象方法,那么子类也成为一个抽象类。
抽象类通常都是作为基类来使用的,可以在抽象类中定义派生类的公共行为(抽象方法),并提供一些基本的方法实现。
我们看一个类设计的例子,定义一个形状基类Shape,它有一个绘制方法draw,绘制什么形状呢?不确定,所以draw方法声明为abstract,Shape声明为抽象基类。从Shape派生出具体的形状子类:点(Point)、线(Line)、矩形(Rectangle)。一条线可以由两个点来确定,一个矩形可以由左上角和右下角的两个点来确定,因此Line类和Rectangle类还会用到Point类的对象,最终代码如6.4所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_8.jpg?sign=1739897340-235oQWohtMsiCRYQLfiBlNwubNUrXo6G-0-c04cac4f2fffdb06347d22e571ab1677)
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_9.jpg?sign=1739897340-njt9oO67oNVSxzgAqCcoPDKBQhM5e1G6-0-e7e89cde74d73352b654325161947129)
希望读者能够仔细阅读上述代码,这对掌握面向对象的类设计很有帮助。
上述程序的输出结果如下:
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt006_10.jpg?sign=1739897340-fIZyGgCjjd7DE9VABmW2HR9j6iu7S2gL-0-e81d2b7201e0044a21f301ded9b73c13)
我们可以将一个没有任何抽象方法的类声明为abstract,避免由这个类产生对象。有时候,一个接口中声明了很多方法,但实现类往往只会用到其中很少的方法,为了减轻实现类的负担,可以编写一个基类对接口中声明的所有方法给出空实现(即只有代表方法体的一对花括号),由于这个类对接口中的方法都是空实现,直接使用该类的对象毫无意义,所以可以将该类声明为抽象的,以避免由该类直接产生对象。需要用到接口的类可以直接从这个基类派生,然后根据需要重写相应的方法即可。虽然基类是抽象的,但其中的方法都是有实现的(虽然是空实现),所以并不需要去覆盖所有的方法。可参看Java类库中的java.awt.event.WindowListener接口和java.awt.event.WindowAdapter类。关于接口,请参看下一节。
构造方法、静态方法、私有方法、final方法不能被声明为抽象的。这些方法有一个共同点,就是不能被覆盖,如果允许它们声明为抽象的,但是子类又不能覆盖这些方法,那不就矛盾了吗?
题外话 熟悉C++的读者可以把Java的抽象方法视为C++类中的纯虚函数。