Java学习笔记之方法重载,动态方法调度和抽象类

时间:2021-02-01 07:40:35

一、方法重载

如果子类中的方法与它的超类中的方法有相同的方法名,则称子类中的方法重载超类中的方法,特别是当超类和子类中的方法名和参数类型都相同时,在子类中调用该方法时,超类中的方法会被隐藏。考虑下面程序:

 class A
{
int i, j;
A(int a, int b)
{
i = a;
j = b;
} // display i and j
void show()
{
System.out.println("i and j: " + i + " " + j);
}
} class B extends A
{
int k; B(int a, int b, int c)
{
super(a, b);
k = c;
} // display k – this overrides show() in A
void show()
{
System.out.println("k: " + k);
}
} public class myJavaTest
{
public static void main(String args[])
{
B subOb = new B(1, 2, 3); subOb.show(); // this calls show() in B
}
}

运行结果:k: 3

子类B中的show()重载了超类A中的show(),从子类中调用重载方法时(41行),它总是引用子类定义的方法(28-31行),超类中的show()方法(11-14行)被隐藏。当然如果希望访问被重载的超类方法,可以用super,如下:

 class B extends A
{
int k; B(int a, int b, int c)
{
super(a, b);
k = c;
}
void show()
{
super.show(); // this calls A's show()
System.out.println("k: " + k);
}
}

如果用这段代码替换上面的版本,运行结果会变为:

i and j: 1 2
k: 3

super.show()调用了被隐藏的超类的方法。

注意,当子类和超类中仅仅是方法名相同而参数类型不同时,利用子类调用该方法时系统会根据输入的参数类型来判断到底使用哪一个版本。

二、动态方法调度

上面的例子解释了何为方法重载。方法重载的真正意义在于它构成了Java一个最强大的概念的基础:动态方法调度。动态方法调度是一种在程序运行时而不是编译时调用重载方法的机制,它是实现运行时多态性的基础。

考虑下面程序:

 // Dynamic Method Dispatch
class A
{
void callme()
{
System.out.println("Inside A's callme method");
}
} class B extends A
{
// override callme()
void callme()
{
System.out.println("Inside B's callme method");
}
} class C extends A
{
// override callme()
void callme()
{
System.out.println("Inside C's callme method");
}
} public class myJavaTest
{
public static void main(String args[])
{
A a = new A(); // object of type A
B b = new B(); // object of type B
C c = new C(); // object of type C
A r; // 声明一个对A的引用 r r = a; // 引用r指向A的对象
r.callme(); // calls A's version of callme r = b; // 引用r指向B的对象
r.callme(); // calls B's version of callme
r = c; // 引用r指向C的对象
r.callme(); // calls C's version of callme
}
}

输出如下:

Inside A's callme method
Inside B's callme method
Inside C's callme method

程序创建了一个名为A的超类以及它的两个子类B和C。子类B和C重载A中定义的callme( )方法。main( )主函数中,声明了A、B和C类的对象。而且,一个A类型的引用r也被声明。就像输出所显示的,所执行的callme( )版本由调用时引用对象的类型决定。

由上述例子我们可以看到,重载方法允许Java支持运行时多态性,就是在程序运行的时候选择使用哪一个版本的方法,从而实现“一个接口,多个方法”。超类提供子类可以直接运用的所有元素。多态也定义了这些派生类必须自己实现的方法。这允许子类在加强一致接口的同时,灵活的定义它们自己的方法。

三、应用方法重载

下面的程序创建了一个名为Figure的超类,它存储不同二维对象的大小。它还定义了一个方法area( ),该方法计算对象的面积。程序从Figure派生了两个子类。第一个是Rectangle,第二个是Triangle。每个子类重载area( )方法,它们分别返回一个矩形和一个三角形的面积。

 class Figure
{
double dim1;
double dim2; Figure(double a, double b)
{
dim1 = a;
dim2 = b;
} double area()
{
System.out.println("Area for Figure is undefined.");
return 0;
}
} class Rectangle extends Figure
{
Rectangle(double a, double b)
{
super(a, b);
} // override area for rectangle
double area()
{
System.out.println("Inside Area for Rectangle.");
return dim1 * dim2;
}
} class Triangle extends Figure
{
Triangle(double a, double b)
{
super(a, b);
} // override area for right triangle
double area()
{
System.out.println("Inside Area for Triangle.");
return dim1 * dim2 / 2;
}
} public class myJavaTest
{
public static void main(String args[])
{
Figure f = new Figure(10, 10);
Rectangle r = new Rectangle(9, 5);
Triangle t = new Triangle(10, 8); Figure figref; figref = r;
System.out.println("Area is " + figref.area()); figref = t;
System.out.println("Area is " + figref.area()); figref = f;
System.out.println("Area is " + figref.area()); }
}

输出:

Inside Area for Rectangle.
Area is 45.0
Inside Area for Triangle.
Area is 40.0
Area for Figure is undefined.
Area is 0.0

这种情况下, 如果一个对象是从Figure派生, 那么它的面积可以由调用area( )来获得。无论用到哪种图形的类型,该操作的接口是相同的。

四、使用抽象类

回看前面的例子中,超类Figure中,area( )的定义仅是一个占位符,它不会计算和显示任何类型对象的面积。也就是说,有时候我们希望定义一个超类,但是该超类只给定一种类的结构但是不提供方法的实现,而继承超类的子类共享这种结构,具体的实现由每个子类自己填写。

还是考虑上面的例子,对于Triangle类,如果它自己不定义area(),那么它将变得毫无意义。所以这种情况下,必须确保子类真正重载了所有必须的方法。Java对于这个问题的解决使用的是抽象方法(abstract method).通过abstract 修饰符指定某些方法必须由子类实现,你不去实现就不让你运行,这样子类就必须重载它们,而不能简单使用超类中定义的版本。

考虑下面的程序:

 // A Simple demonstration of abstract.
abstract class A
{
abstract void callme();//声明抽象方法,不具体实现,交给子类实现 // 用一个抽象类去实例化一个对象是不允许的,但在类里面实现一个具体方法还是允许的
void callmetoo()
{
System.out.println("This is a concrete method.");
}
} class B extends A
{
void callme() //实现超类的抽象方法
{
System.out.println("B's implementation of callme.");
}
} public class myJavaTest
{
public static void main(String args[])
{
A a;
B b = new B();
a = b;
a.callme();
a.callmetoo();
}
}

输出:

B's implementation of callme.
This is a concrete method.

由上面程序总结出使用抽象类要注意:

  • 声明一个抽象方法的通用形式:abstract type name(parameter-list)(第4行)
  • 一个类只要含有一个或多个抽象类方法,它就变成了抽象类,就必须声明为抽象类(第二行所示)
  • 尽管抽象类不能用来实例化(即不能用来建立一个具体的对象,语句A a = new A()就是非法的),但是它们可以用来创建对象引用(如25行所示)
  • 抽象类实例化抽象类不可以,但抽象类可以自己实现任意数量的具体方法(如7-10行实现了一个具体方法)
  • 所有子类都必须具体实现超类中的抽象方法或者改子类自己声明为abstract

下面用抽象类改善前面的Figure类:

 // Using abstract methods and classes.
abstract class Figure
{
double dim1;
double dim2; Figure(double a, double b)
{
dim1 = a;
dim2 = b;
} // area is now an abstract method
abstract double area();
} class Rectangle extends Figure
{
Rectangle(double a, double b)
{
super(a, b);
} // override area for rectangle
double area()
{
System.out.println("Inside Area for Rectangle.");
return dim1 * dim2;
}
} class Triangle extends Figure
{
Triangle(double a, double b)
{
super(a, b);
} // override area for right triangle
double area()
{
System.out.println("Inside Area for Triangle.");
return dim1 * dim2 / 2;
}
} public class myJavaTest
{
public static void main(String args[])
{
// Figure f = new Figure(10, 10); // 非法的,因为抽象类是不能够创建对象的
Rectangle r = new Rectangle(9, 5);
Triangle t = new Triangle(10, 8);
Figure figref; // 仅仅声明了一个指向Figure类的引用,是不会创建具体对象的,所以语句合法 figref = r;
System.out.println("Area is " + figref.area()); figref = t;
System.out.println("Area is " + figref.area());
}
}

56行变量figref声明成Figure的一个引用,意思是说它可以用来引用任何从Figure派生的对象。