第十章 接口、继承和多态
1.简介
继承和多态是面向对象开发中非常重要的一个环节。继承和多态使用得当, 整个程序的架构将变得非常有弹性,同时可以减少代码的冗余性。继承机制下,用户可以复用一-些定义好的类,减少重复代码的编写。多态机制下,用户可以动态调整对象的调用, 降低对象之间的依存关系。为了优化继承与多态,一些类除了可继承
2.类的继承
继承的基本思想是基于某个父类进行扩展,得到一个新的子类。子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
在Java中使用extends关键字来标识两个类的继承关系。
class TestOne{
public TestOne(){ // 构造方法
}
protected void doSomething(){ // 成员函数
System.out.println("TestOne doSomething!");
}
protected TestOne dolt(){
return new TestOne();
}
}
class TestTwo extends TestOne{ // 继承父类
public TestTwo(){ // 构造方法
super(); // 调用构造函数
super.doSomething(); // 调用父类成员方法
}
public void doSomethingNew(){ // 新增方法
System.out.println("TestOne doSomethingNew!");
}
public void doSomething(){ // 重写父类方法
System.out.println("TestTwo doSomething!");
}
protected TestTwo dolt(){
return new TestTwo();
}
}
注意
在实例化子类对象时,父类无参构造方法将被自动调用。有参构造方法不能被自动调用,用户只能使用super关键字显式地调用父类的构造方法。
技巧
如果使用finalize()
方法对对象进行清理,需要确保子类finalize()
方法的最后一个动作是调用父类的finalize()
方法,以保证当垃圾回收对象占用内存时,对象的所有部分都能被正常终止。
2.Object类
在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中所有的类都直接或间接继承了java.lang.object
类。0bject
类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。
2.1getClass()方法.
getClass( )方法是0bject类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName( )方法可以取得类的名称。
语法如下:
getClass(). getname();
可以将getClass()方法与toString()方法联合使用。
2.2toString()方法
toString( )方法的功能是将一个对象返回为字符串形式,它会返回一个String实例。在实际的应用中通常重写toString( )方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。
2.2equals()方法
第7章中曾讲解过equals()方法。当时是比较 “==” 运算符与equals()方法,两者的区别在于: 此时是用来比较的是两个对象的引用是否相等,而equals( )
方法比较的是两个对象的实际内容。来看下面的实例。
3.对象类型的转换
对象类型的转换在Java编程中经常遇到,主要包括向.上转型与向下转型操作。
3.1向上转换
因为平行四边形是特殊的四边形,也就是说平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一个四边形对象。 例如,鸡是家禽的一种,而家禽是动物中的一类,那么也可以将鸡对象看作是一个动物对象。
class Quadrangle{
public static void draw(Quadrangle q){ // 四边形类中的方法
System.out.println("this draw method is ");
}
}
public class Parallelogram extends Quadrangle{
public static void main(String[] args){
Parallelogram p = new Parallelogram();
draw(p);
}
}
就是把子类对象赋值给父类类型的变量,这种技术被称 为“向上转型”。
3.2向下转换
通过向上转型可以推理出,向下转型是将较抽象的类转换为较具体的类。这样的转型通常会出现问题,例如不能说四边形是平行四边形的一-种,所有的鸟都是鸽子,因为这非常不合乎逻辑。也就是说,子类对象总是父类的一个实例,但父类对象不一定是子类的实例。
class Quadrangle{
public static void draw(Quadrangle q){ // 四边形类中的方法
System.out.println("this draw method is ");
}
}
public class Parallelogram extends Quadrangle{
public static void main(String[] args){
draw(new Parallelogram()); // 将平行四边形类对象看作是四边形对象,称为向上转换操作
Quadrangle q = new Parallelogram();
// Parallelogram p = q;
// 将父类对象直接赋予给子类对象,这种写法是错误的
// 将父类对象赋予给子类对象,并强制转换为子类型,这种写法是正确的
Parallelogram p = (Parallelogram) q;
}
}
如果将父类对象直接赋予子类,会发生编译器错,因为父类对象不一定子类的实例,需要显示的进行转换。越是具体的对象,具有的特性越多;越是抽象的对象,具有的特性越少。在做向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题。
4.使用instanceof操作符判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一 个类实现了某个接口( 接口会在10. 6节中进行介绍) , 也可以用它来判断一个实例对象是否属于一 个类。
instanceof
的语法格式如下:
myobject instanceof ExampleClass
- myobject :某类的对象引用。
- ExampleClass :某个类。
class Quadrangle{
public static void draw(Quadrangle q){ // 四边形类中的方法
System.out.println("this class is quadrangle draw");
}
}
class Square extends Quadrangle{
public static void testQuadrangle(){
System.out.println("this class is Square");
}
}
class Anything{
public static void testAnything(){
System.out.println("this class is Anything");
}
}
public class Parallelogram extends Quadrangle{
public static void main(String[] args){
Quadrangle q = new Parallelogram(); // 实例化父类一个对象
if (q instanceof Parallelogram){ // 判断父类对象是否为Parallelogram子类的一个实例
Parallelogram p = (Parallelogram)q; // 向下转换
}
else{
System.out.println("q to Parallelogram is error!");
}
if (q instanceof Square){
Square s = (Square) q;
}
else{
System.out.println("q to Square is error!");
}
}
}
输出
Process finished with exit code 0
5.方法重载
构造方法的名称由类名决定,所以构造方法只有一个名称。如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到方法重载。虽然方法重载起源于构造方法,但它也可以应用到其他方法中。
方法的重载就是在同一个类中允许存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。为了更好地解释重载,来看下面的实例。
public class OverLoadTest {
public static int add(int a, int b){
return a + b;
}
public static double add(double a, double b){
return a + b;
}
public static int add(int a){
return a;
}
public static int add(int a, double b){
return 9;
}
public static int add(double a, int b){
return 99;
}
public static void main(String[] args){
System.out.println("execute method is add(int, int)"+add(9, 9));
System.out.println("execute method is add(double, double)"+add(9.0, 9.0));
System.out.println("execute method is add(int, double)"+add(9, 9.0));
System.out.println("execute method is add(double, int)"+add(9.0, 9));
System.out.println("execute method is add(int)"+add(9));
}
}
5.1不定长参数
public static int add(int...a){
int sum = 0;
for (int i = 0; i < a.length; i++){
sum += a[i];
}
return sum;
}
6.多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。在10. 3节中已经学习过对象可以作为父类的对象实例使用,这种将子类对象视为父类对象的做法称为“向.上转型”。假如现在需要绘制一个平行四边形,这时可以在平行四边形类中定义一个draw( )方法,具体实现代码如例10.11所示。
自我理解
如果定义一个四边形类,让它处理所有继承该类的对象,根据“向.上转型”原则可以使每个继承四边形类的对象作为draw( )方法的参数,然后在draw()方法中作一些限定就可以根据不同图形类对象绘制相应的图形,从而以更为通用的四边形类来取代具体的正方形类和平行四边形类。这样处理能够很好地解决代码冗余问题,同时也易于维护,因为可以加入任何继承父类的子类对象,而父类方法也无须修改。
public class Quadrangle {
private Quadrangle[] qtest = new Quadrangle[9]; //继承父类的子类对象的个数
private int nextIndex = 0;
public void draw(Quadrangle q){
if (nextIndex < qtest.length){
qtest[nextIndex] = q;
System.out.println(nextIndex);
nextIndex++;
}
}
public static void main(String[] args){
Quadrangle q = new Quadrangle();
q.draw(new Square());
q.draw(new parallelogram());
}
}
class Square extends Quadrangle{
public Square(){
System.out.println("this is Square!");
}
}
class parallelogram extends Quadrangle{
public parallelogram(){
System.out.println("this is parallelogram!");
}
}
7.抽象类与接口
7.1抽象类
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中, 并不需要将父类初始化对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一-种具体图形,但它的子类却可以。
语法如下
public abstract class Test{
abstract void isTest(); // 定义抽象方法
}
7.2接口
7.2.1接口的运用
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。
语法如下:
public interface drawTest{
void draw();
}
一个类实现一个借口,使用关键字implement关键字,语法如下
public class parallelogram extends Quadrangle implements drawTest{
// ...
}
注意:
在接口中,方法必须被定义为public或abstract形式,其他修饰权限不被Java编译器认可。或者说,即使不将该方法声明为public形式,它也是public。
package test;
interface drawTest{
public void draw();
}
class ParallelogramUseInterface extends QuadrangleUseInterface implements drawTest{
public void draw(){
System.out.println("this is parallelogram draw()");
}
void doAnything(){
// someSentence
}
}
class SquareUseInterface extends QuadrangleUseInterface implements drawTest{
public void draw(){
System.out.println("this is square draw()");
}
void doAnything(){
// SomeSentence
}
}
class AnyThingUseInterface extends QuadrangleUseInterface{
void doAnything(){
// SomeSentence
}
}
public class QuadrangleUseInterface {
public void doAnyThing(){
// SomeSentence
}
public static void main(String[] args){
drawTest[] drawT = {new SquareUseInterface(), new ParallelogramUseInterface()};
for (int i = 0; i < drawT.length; i++){
drawT[i].draw();
}
}
}
7.2.2接口与继承
Java中不允许出现多重继承,但使用接口就可以实现多重继承。一个类可以同时实现多个接口,因此可以将所有需要继承的接口放置在implements关键字后并使用逗号隔开。但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。