初学java时,我们就知道了java的三大特性,也是面向对象编程的三大特性
分别是:继承、封装、多态
继承不难理解,根据字面意思,就是子类继承父类,深入点理解继承就是允许将对象视为他自己本身的类型或其基类型来加以处理。这种能力极其重要,因为他允许将多种类型(从同一父类导出的)视为同一类型来处理,而同一份代码也就可以毫无差别的运行在这些不同类型之上了。
而多态方法调用允许一种类型表现出其他相似类型之间的区别,只要他们都是从同一父类导出而来。
下面我通过很多的例子,对多态描述一下自己的理解
多态性分为对象的多态性和方法的多态性
对象的多态性:
①对象的向上转型:父类 父类对象 = 子类实例。 (自动)
②对象的向下转型:子类 子类对象 =(子类)父类实例。(强制)
方法的多态性
①方法的重载:同一个方法名称可以根据参数的类型或个数不同调用不同的方法体
②方法的覆写:同一个父类的方法,可能根据实例化子类的不同也有不同的实现。
1、向上转型
package com.bittch.Day_42;
/**
* @author CHAOQIWEN
* @data 2019/7/21 11:48
*/
public enum Note{
MIDDLE_C,C_SHARP,B_FLAT;
}
package com.bittch.Day_42;
/**
* @author CHAOQIWEN
* @data 2019/7/21 11:49
*/
class Instrument{
public void play(Note n){
System.out.println("Instrumen.play()");
}
}
class Wind extends Instrument{
public void play(Note n){
System.out.println("wind.play() "+n);
}
}
public class DuoTaiTest {
public static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Instrument flute= new Wind();
tune(flute);
}
}
我们可以看出,代码中,这一句就是向上转型
这段代码中,DuoTaiTest.tune()方法接受一个Instrument的引用,同时也接受任何导出自Instrument的类。而向上转型的本质,还是看你new了哪一个类,而这个例子中,Wind从Instrument继承而来,所以Instrument中的所有接口必定存在于Wind中,从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更“窄”
2、向下转型
(这里默认是含有上述例子第一段代码的枚举,这里不重复粘贴)
class Instrument{
public void play(Note n){
System.out.println("Instrumen.play()");
}
}
class Wind extends Instrument{
public void play(Note n){
System.out.println("wind.play() "+n);
}
public void print(){
System.out.println("只有子类有");
}
}
public class DuoTaiTest {
public static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind f=(Wind)new Instrument();
f.print();
}
}
这一句的向下转型,编译时无错,在运行时发生了ClassCastException( ClassCastException 指的是两个没有关系的对象进行强转出现的异常),如果想不发生 ClassCastException,在向下转型时,需要先进行向上转型,main()函数中,修改成这样,就能运行成功
Instrument flute= new Wind();
flute.play(Note.MIDDLE_C);
Wind f=(Wind)flute();
f.print();
3、 instanceof关键字
如果向下转型存在安全隐患,就要先进行判断,而后在进行转型,那么 就需要依靠instanceof关键字实现
我们把上述例子的main()函数加以修改:
public static void main(String[] args) {
Instrument flute= new Wind();
System.out.println( flute instanceof Instrument);
System.out.println(flute instanceof Wind);
if(flute instanceof Wind){
Wind f=(Wind) flute;
f.print();
f.play(Note.MIDDLE_C);
}
}
通常在向下转型时使用instanceof关键字来判断健壮性
4、构造器和多态
我们先来看这样一个例子,他展示了组合、继承以及多态性在构建顺序上的作用
class Meal{
Meal(){
System.out.println("meal");
}
}
class Bread{
Bread(){
System.out.println("bread");
}
}
class Cheese{
Cheese(){
System.out.println("cheese");
}
}
class Lettuce{
Lettuce(){
System.out.println("lettuce");
}
}
class Lunch extends Meal{
Lunch(){
System.out.println("lunch");
}
}
class ProtableLunch extends Lunch{
ProtableLunch(){
System.out.println("pretablelunch");
}
}
public class Sandwich extends ProtableLunch{
Bread b=new Bread();
Cheese c=new Cheese();
Lettuce l=new Lettuce();
public Sandwich(){
System.out.println("sandwich");
}
public static void main(String[] args) {
new Sandwich();
}
}
我们可以通过这个例子看出对象调用构造器需要遵循的顺序:
1)调用基类构造器(这个步骤会不断反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,直到最低层的导出类)
2)按声明顺序调用成员的初始化方法
3)调用导出类构造器的主体
构造器内部的多态行为
在一般方法内部,动态绑定的调用时在运行时才决定的,因为对象无法知道它是属于方法所在的哪个类,还是属于那个类的导出类,如果要调用构造器内部的一个动态绑定的方法,就要用到哪个方法被覆盖后的定义,然而,这个调用的效果可能很难预料,因为被覆盖的方法在对象被完全构造之前就会被调用,这可能会造成一些难于发现的隐藏错误
我们先来看这样一段代码
class Glyph{
void draw(){
System.out.println("Glyph.draw()");
}
Glyph(){
System.out.println(" Glyph() before draw()");
draw();
System.out.println(" Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius=1;
RoundGlyph(int r){
radius=r;
System.out.println("RoundGlyph.RoundGlyph().radius="+radius);
}
void draw(){
System.out.println("RoundGlyph.draw().radius="+radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
上述代码中,我们设计的是Glyph.draw()将要被覆盖,这种覆盖是在RoundGlyph中发生的,但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用,但是看到输出结果,会发现当Glyph的构造器调用draw()方法时,radius并不是默认的初始值1,而是0,解决这个问题的关键,我们要先了解到初始化的实际过程(如下)
1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的0;
2)如前述调用基类构造器
3)按照声明顺序调用成员的初始化方法
4)调用导出类的构造器主体
经过总结,“多态”意味着“不同的形式”,在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法