根据参数的不同来分辨你调用哪个方法,通俗的解释多态就是,假如有个方法名叫:“动物的叫声”那么,当你传递的参数是动物狗,则调用狗叫声,是猫,则调用猫叫声,这就是多态。
在这之前,我们再回顾下向上转型:
enum Note{//枚举型
MIDDLE_C,C_SHARP,B_FLAT;//变量
}
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
class Wind extends Instrument {
public void play(Note n) {
System.out.println("Wind.play()"+n);
}
}
public class Music {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute =new Wind();
turn(flute);//Upcasting向上转型
}
结果:Wind.play()MIDDLE_C
Music.tune()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。在main方法中,当一个Wind引用传递到tune()方法时,就会出现这种情况,而不需要任何类型转换。这样做是允许的——因为Wind从Instrument继承而来,所有Instrument的接口必定存在于Wind中。从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更窄。
enum Note{//枚举型
MIDDLE_C,C_SHARP,B_FLAT;//变量
}
class Instrument {
public void play(Note n) {
System.out.println("Instrument3.play()");
}
public String what() {
return "Instrument3";
}
public void adjust() {}
}
class Wind extends Instrument {
public void play(Note n) {
System.out.println("Wind3.play()"+n);
}
public String what() { return "Wind3"; }
public void adjust() {}
}
class Percussion extends Instrument {
public void play(Note n) {
System.out.println("Percussion3.play()"+n);
}
public String what() { return "Percussion3"; }
public void adjust() {}
}
class Stringed extends Instrument {
public void play(Note n) {
System.out.println("Stringed3.play()"+n);
}
public String what() { return "Stringed3"; }
public void adjust() {}
}
class Brass extends Wind {
public void play(Note n) {
System.out.println("Brass3.play()"+n);
}
public void adjust() {
System.out.println("Brass3.adjust()");
}
}
class Woodwind extends Wind {
public void play(Note n) {
System.out.println("Woodwind3.play()"+n);
}
public String what() { return "Woodwind3"; }
}
public class b {
// Doesn't care about type, so new types
// added to the system still work right:
static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument[] e) {
for(int i = 0; i < e.length; i++)
tune(e[i]);
/*也可以这么写利用foreach语句
for(Instrument i : e)
tune(i);
**/
}
public static void main(String[] args) {
Instrument[] orchestra = new Instrument[5];
int i = 0;
// Upcasting during addition to the array:
orchestra[i++] = new Wind();
orchestra[i++] = new Percussion();
orchestra[i++] = new Stringed();
orchestra[i++] = new Brass();
orchestra[i++] = new Woodwind();
tuneAll(orchestra);
/*也可以这么写
Instrument3[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
**/
}
}
/*
* 我们新添加了what()和adjust()方法,我们将引用至于orchestra数组中,就会自动向上转型到
Instrument。可以看到tune()方法完全可以忽略它周围代码所发生的全部变化,依旧正常运行。
* 这正式我们期望多台所具有的特性。我们所做的代码修改,不会对程序中其他不应受到影响的部分产生破坏
。换句话说,多态是一项让程序员“将改变的事物和未变的事物分离开来”的重要技术。
*/
结果:
Wind3.play()MIDDLE_C
Percussion3.play()MIDDLE_C
Stringed3.play()MIDDLE_C
Brass3.play()MIDDLE_C
Woodwind3.play()MIDDLE_C
public class a {
public static void ride(Cycle c) {
System.out.println("Num. of wheels: " + c.wheels());
}
public static void main(String[] args) {
ride(new Cycle()); // No upcasting
ride(new Unicycle()); // Upcast
ride(new Bicycle()); // Upcast
ride(new Tricycle()); // Upcast
}
}
class Cycle {
public int wheels() { return 0; }
}
class Unicycle extends Cycle {
public int wheels() { return 1; }
}
class Bicycle extends Cycle {
public int wheels() { return 2; }
}
class Tricycle extends Cycle {
public int wheels() { return 3; }
}
Num. of wheels: 0
Num. of wheels: 1
Num. of wheels: 2
Num. of wheels: 3
多态的结果
“覆盖”只有在某些方法是基类的接口的一部分时才会出现。即,必须将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法是private(private方法属于final方法),它就不是基类接口的一部分。它仅是一些隐藏于类中的程序代码,只不过是具有相同的名称而已。如果此时,子类有个方法名与基类的一样,其实子类的方法并没有覆盖基类的方法,它现在是个新的方法罢了
缺陷:“覆盖”私有方法
class Checket {
private void hop() { System.out.println("Rodent hopping"); }
public static void main(String[] args) {
Checket rodent = new Hamste();
rodent.hop();
}
}
class Hamste extends Checket {
void hop() {System.out.println("Hamste hopping"); }
}
结果:Rodent hopping
我们所期望的的输出是Hamste hopping,但是由于private方法被自动认为是final方法,而且对导出类时屏蔽的。因此这种情况下Hamste类中的hop方法是个新方法,既然基类中hop方法在子类中不可见,所以也不能被重载。(所以,在子类中,对于基类中的private方法,最好采用不同的名字。)
我的一个疑问:
class Roden {
private void hop() { System.out.println("Rodent hopping"); } //private final void hop()也是出错
}
class Hamste extends Roden {
void hop() {System.out.println("Hamste hopping"); }
}
public class c {
public static void main(String[] args) {
Roden rodent = new Hamste();
rodent.hop();//会提示出错
}
}
这和上个例子唯一不同就是现在的main方法在c中,不是在Roden中,不知道这时为什么?哪个大侠给解释下,先谢谢了。
缺陷:域与静态方法
一旦你了解了多态的机制(可以看看什么是后期绑定),可能就会开始认为所有事物都可以多态的发生。然而,只有普通的方法(除了static方法和final方法)调用可以是多态发生。如果你直接访问某个域,这个访问就将在编译期进行解析:
class Super{
public int field=0;
public int getField(){return field;}
}
class Sub extends Super{
public int field =1;
public int getField(){return field;}
public int getSuperField(){return super.field;}
}
public class Checket{
public static void main(String[] args){
Super sup=new Sub();//Upcast
System.out.println("sup.field="+sup.field+",sup.getField()="+sup.getField());
Sub sub=new Sub();
System.out.println("sup.field="+sup.field+",sup.getField()="+sup.getField()+",sub.getSuperField()="+sub.getSuperField());
}
}
结果:sup.field=0,sup.getField()=1
sup.field=0,sup.getField()=1,sub.getSuperField()=0
当Sub对象转型为Super引用时,任何域访问操作都将由编译器解析,因此不是多态的。
前面说了static方法和final方法不具有多态:
class Super{
public static String staticget(){return "Base staticget() ";}
public String get(){return "Base get()";}
}
class Sub extends Super{
public static String staticget(){return "Sub staticget() ";}
public String get(){return "Sub get()";}
}
public class Checket{
public static void main(String[] args){
Super sub=new Sub();//Upcast
System.out.println(sub.staticget());
System.out.println(sub.get());
}
}
//结果:Base staticget()
// Sub get()
静态方法是类,而并非与单个对象相关联。
构造器和多态
构造器不具有多态性,它实际上是static方法,只不过该static声明是静态的。
class a{
int i=print("我是a中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
a(){System.out.println("构造器a");
}
}
class b{
int i=print("我是b中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
b(){System.out.println("构造器b");
}
}
class n{
int i=print("我是n中的变量i");
n(){System.out.println("构造器n");
}
int print(String n){
System.out.println(n);
return 1;
}
}
class m extends n{
int i=print("我是m中的变量i");
m(){System.out.println("构造器m");}
int print(String n){
System.out.println(n);
return 1;
}
}
public class c extends m{
int i=print("我是c中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
private a aa=new a();
private b bb=new b();
c(){System.out.println("构造器c");}
public static void main(String []args){
c cc=new c();//生成c对象
}
}
/*
我是n中的变量i
构造器n
我是m中的变量i
构造器m
我是c中的变量i
我是a中的变量i
构造器a
我是b中的变量i
构造器b
构造器c
**/
c cc=new c();先调用基类的构造器(将基类按初始化顺序初始化一遍),然后才按初始化顺序将导出类初始化一遍。
再看个特殊的情况
class a{
int i=print("我是a中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
a(){System.out.println("构造器a");
}
}
class b{
int i=print("我是b中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
b(){System.out.println("构造器b");
}
}
class n{
int i=print("我是n中的变量i");
n(){System.out.println("构造器n");
}
int print(String n){
System.out.println(n);
return 1;
}
}
class m extends n{
int i=print("我是m中的变量i");
m(){System.out.println("构造器m");}
int print(String n){
System.out.println(n);
return 1;
}
}
public class c extends m{
int i=print("我是c中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
private a aa=new a();
private b bb=new b();
c(){System.out.println("构造器c");}
public static void main(String []args){
//c cc=new c();不生成c对象
}
}
/*
结果是什么也没有
**/
因为这时没有生成c对象,也就不存在调用基类和导出类的构造器,也不会初始化基类,再初始化导出类
如果基类或者导出类中有static变量又会怎样呢?
class a{
int i=print("我是a中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
a(){System.out.println("构造器a");
}
}
class b{
int i=print("我是b中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
b(){System.out.println("构造器b");
}
}
class n{
static int i=print("我是n中的变量i");
n(){System.out.println("构造器n");
}
static int print(String n){
System.out.println(n);
return 1;
}
}
class m extends n{
static int i=print("我是m中的变量i");
m(){System.out.println("构造器m");}
static int print(String n){
System.out.println(n);
return 1;
}
}
public class c extends m{
static int i=print("我是c中的变量i");
static int print(String n){
System.out.println(n);
return 1;
}
private a aa=new a();
private b bb=new b();
c(){System.out.println("构造器c");}
public static void main(String []args){
//c cc=new c();不生成c对象
}
}
/*
我是n中的变量i
我是m中的变量i
我是c中的变量i
**/
呵呵。以前说过。static变量不论什么时候总被初始化的。
在调试程序的时候发现个现象:
class m {
static int i=print("我是m中的变量i");
m(){System.out.println("构造器m");}
static int print(String n){
System.out.println(n);
return 1;
}
}
public class a extends m{
static int i=print("我是c中的变量i");
int print(String n){
System.out.println(n);
return 1;
}
a(){System.out.println("构造器c");}
public static void main(String []args){
//c cc=new c();不生成c对象
}
}
编译时候出现:

也就说:1.对于static变量来说,给它赋予值的方法必须也是static的。
2.父类中的static方法是无法覆盖的,方法同名不允许。
现在来说说个大家意想不到,也是要注意的现象:
class a{
void draw(){System.out.println("a.draw()");
}
a() {
System.out.println("before draw()");
draw();
System.out.println("after draw()");
}
}
class b extends a{
private int m=1;
b(int i){
m=i;
System.out.println("b+"+m);
}
void draw(){System.out.println("b.draw()+"+m);
}
}
public class c {
public static void main(String[] args) {
b bb=new b(2);
}
}
/*
before draw()
b.draw()+0
after draw()
b+2
**/
a的draw方法在b覆盖了。但是,a的构造器会调用这个方法,结果导致了对b的draw方法的调用。而且大家会发现m的默认初始值竟然不是1,而是0。
因为初始化的实际过程是这样的:
1.在其他任何事情发生之前,将分配给对象的存储空间初始化为二进制的0.
2.调用基类的构造器。此时,调用被覆盖后的draw()方法(要在调用b构造器之前条用),由于步骤1的缘故,此时m=0.
3.按照声明顺序调用成员的初始化方法。
4.调用导出类的构造器主体。
对于上面的结果,我们还是相当震惊的,这类错误容易被人忽略,而且要花费很多的时间才能发现,所以我们在编写构造器的时候有一条有效的准则:
“尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其它方法,在构造器中唯一能够调用的那些放方法是基类的final和private方法,因为这些方法无法被覆盖,所以就无法出现上述的现象。”
用继承进行设计
当我们使用现成的类来建立新类时,我们首选的是利用“组合”(组合就是:在新的类中添加已有类的对象),尤其是不确定使用哪种方式的时候,其实才考虑选用“继承'。
class AlertStatus {
public String getStatus() { return "None"; }
}
class RedAlertStatus extends AlertStatus {
public String getStatus() { return "Red"; };
}
class YellowAlertStatus extends AlertStatus {
public String getStatus() { return "Yellow"; };
}
class GreenAlertStatus extends AlertStatus {
public String getStatus() { return "Green"; };
}
class Starship {
private AlertStatus status = new GreenAlertStatus();
public void setStatus(AlertStatus istatus) {
this.status = istatus;
}
public String toString() { return status.getStatus(); }
}
public class c{
public static void main(String args[]) {
Starship eprise = new Starship();
System.out.println(eprise);
eprise.setStatus(new YellowAlertStatus());
System.out.println(eprise);
eprise.setStatus(new RedAlertStatus());
System.out.println(eprise);
}
} /* Output:
Green
Yellow
Red
*///
一条通用的准则是:”用继承表达行为的差异,并用字段表达状态上的变化“。在上述的例子中,两者都用到了:通过继承得到三个不同的类,用于表达getStatus()方法的差异;而Starship通过运用组合是自己的状态发生变化。在这种情况下,这种状态的改变也就产生了行为的改变。
向下转型与运行时类型识别
我们知道向上转型是安全的,因为基类不会大于导出类的接口。但是对于向下转型,例如,我们无法知道一个几何形状,它确实就是一个圆,也可是是个三角形、正方形或其它类型。所以我们必须有某种方法来确保向下转型的正确性。
在java中所有转型都会进行检查,所以即使我们只是进行一次简单的加括号形式的类型转换,在运行时期仍然会得到检查,以便保证它的确是我们希望的那种类型。如果不是,就会返回一个ClassCastException。这种在运行期间对类型进行检查的行为称作”运行时类别识别“(RTTI)。
class Useful{
public void f(){System.out.println("useful.f");}
public void g(){System.out.println("useful.g");}
}
class Moreuseful extends Useful{
public void f(){System.out.println("moreuseful.f");}
public void g(){System.out.println("moreuseful.g");}
public void h(){System.out.println("moreuseful.h");}
}
public class c{
public static void main(String[] args){
Useful[] x={
new Useful(),
new Moreuseful()//upcast
} ;
x[0].f();
x[1].g();
// x[1].h();Compile time:method not found in Useful Useful中没有h()方法
((Moreuseful)x[1]).h();//downcast
((Moreuseful)x[0]).h();//exception throw
}
}
结果:useful.f
moreuseful.g
moreuseful.h
Exception in thread "main" java.lang.ClassCastException: Useful cannot be cast to Moreuseful
at c.main(c.java:20)
public class Checket {
public static void main(String[] args) {
Cycle[] cycles = new Cycle[]{ new Unicycle(),
new Bicycle(), new Tricycle() };
// Compile time: method not found in Cycle: Cycle类中没有balance()方法,这点很重要,不然即使向上转型后也无法调用这个方法
//cycles[0].balance(); error
// cycles[1].balance(); error
((Unicycle)cycles[0]).balance(); // Downcast/RTTI
((Bicycle)cycles[1]).balance(); // Downcast/RTTI
((Unicycle)cycles[2]).balance(); // Exception thrown
}
}
class Unicycle extends Cycle {
public void balance() {System.out.println("U.b");}
}
class Bicycle extends Cycle {
public void balance() {System.out.println("B.b");}
}
class Tricycle extends Cycle {}
class Cycle { }
结果:U.b
B.b
Exception in thread "main" java.lang.ClassCastException: Tricycle cannot be cast to Unicycleat Checket.main(Checket.java:10)