java多态

根据参数的不同来分辨你调用哪个方法,通俗的解释多态就是,假如有个方法名叫:“动物的叫声”那么,当你传递的参数是动物狗,则调用狗叫声,是猫,则调用猫叫声,这就是多态。

在这之前,我们再回顾下向上转型:

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)





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值