java 多态 考题_程序员面试必考题(三十)--Java中多态的基本概念

术语“多态”来自于希腊语,意思是“多种形式”。多态作为一个概念,实际上在英文中常见。例如,英语教学“做你最喜爱的运动”对不同的人意味着不同的事情。对某个人它意味着是打蓝球。对另一个人它意味着是踢足球。在Java中,多态(polymorphism)允许同一条程序指令在不同的上下文中意味着不同的事情。具体来说,一个方法名当作一条指令时,依据执行这个动作的对象类型,可能导致不同的动作。

示例。定义一个Student类,从它再间接派生UndergradStudent类。仅保留类中必需的几个方法。方法display可以显示对象中的数据。但它显示的数据及显示多少,要依用来调用该方法的对象的类型而定。UndergradStudent重写了Student中定义的方法display。

//Student.java

publicclass Student

{

private Name fullName;

private String id;   // Identification number

public Student()

{

fullName = new Name();

id = "";

} // end default constructor

public Student(String studentId)

{

fullName = new Name();

id = studentId;

} // end constructor

public void display()

{

System.out.println("Student:" + this.getId());

}

public void setId(String studentId)

{

id = studentId;

} // end setId

public String getId()

{

return id;

} // end getId

public String toString()

{

return id + " " +fullName.toString();

} // end toString

public void displayAt(int numberOfLines)

{

for (int count = 0; count

System.out.println();

display();

} // end displayAt

} // end Student

//Name.java

publicclass Name

{     private String first; // First name

private String last;  // Last name

public Name()

{

} // end default constructor

public String toString()

{

return first + " " +last;

} // end toString

} // end Name

classCollegeStudent extends Student

{

public void display()

{

System.out.println("output:" + this.getId());

}

}

classUndergradStudent extends CollegeStudent

{

public UndergradStudent(StringUndergradStudentId)

{

super();

this.setId (UndergradStudentId);

} // end default constructor

public void display()

{

System.out.println("Undergrad:" + this.getId());

}

}

测试的代码在main()方法中。

publicclass Client

{

public static void main(String[] args)

{

UndergradStudent ug = newUndergradStudent("ug1");

Student s = new Student("stu1");

//第一轮调用

ug.displayAt(2);

s.displayAt(2);

//第二轮调用

s = ug;

ug.displayAt(2);

s.displayAt(2);

//第三轮调用

ug.displayAt(2);

s = (Student)ug;

s.displayAt(2);

}

}

在main()中,分别定义两个类的变量ug和s,并赋了初值。然后调用displayAt()来显示对象的值。

第一轮调用时,因为对象的类型与实际的引用类型是一样的,所以显示的内容也是我们预期的。ug.displayAt(2)显示的是“Undergrad:ug1”,s.displayAt(2)显示的是“Student:stu1”。

第二轮调用前,将s指向ug。实际上,它指向的不再是本类型的对象,而是UndergradStudent类型的实例。使用displayAt()来调用display()时,调用的将是UndergradStudent中的方法。所以显示的内容是一样的,都是“Undergrad:ug1”

方法displayAt定义在类Student中,但它调用定义在类UndergradStudent中的display方法。甚至在类UndergradStudent定义之前,就能为类Student编译displayAt的代码。换句话说,编译的这段代码可以使用displayAt被编译时甚至都还没有写的方法display的定义。

当编译displayAt的代码时,对display的调用产生一条注解,说“使用display的相应定义”。然后,当调用ug.displayAt(2)时,为displayAt编译的代码执行到这条注解,并用与ug对应的display的版本来替换这条注解。因为这种情况下ug是UndergradStudent类型的,所以display的版本是类UndergradStudent中定义的。

决定使用哪个版本的定义,依赖于继承链中接收对象的位置,而不是对象变量名的类型。

给Student类型的变量赋值类Undergradstudent的一个对象,是完全合法的。这里,变量s只是ug指向的对象的另一个名字。即,s和ug都是别名。但对象仍记着它创建为一个UndergradStudent。这种情形下,s.displayAt(2)最终会使用UndergradStudent中给出的display的定义,而不是Student中给出的display的定义。

变量的静态类型(static type)是出现在声明中的类型。例如,变量s的静态类型是Student。静态类型是在代码编译时固定且确定下来的。运行时某一时刻变量指向的对象的类型称为动态类型(dynamic type)。变量的动态类型随运行进程会改变。当执行前一段代码中的赋值语句s = ug时,s的动态类型是UndergradStudent。引用类型的变量称为多态变量(polymorphic variable),因为执行过程中,它的动态类型可以不同于静态类型,且可改变。

具体到我们的例子,Java查看是哪个构造方法创建了对象,从而确定要使用display的哪个定义。即,Java使用变量s的动态类型,来做出判断。

调用稍后可能被重写的方法的这种处理方式,称为动态绑定(dynamic binding)或后绑定(late binding),因为在程序执行之前,方法调用的含义没有与方法调用的位置进行绑定。执行前面这段代码时,如果Java不使用动态绑定,就不会看到Undergraduatestudent的数据。相反,只能看到Student类提供的方法display所显示的内容。

再看第三轮调用。

Java能分清要使用方法的哪个定义,即使类型转型也骗不过它。我们知道,可以使用类型转型将一个值的类型转为其他的类型。尽管有类型转型,s.displayAt(2)还是使用UndergradStudent中给出的display的定义,而不是Student中给出的display的定义。对象的动态类型,而不是它的名字,是选择要调用的正确方法的决定因素。

类型检查和动态绑定。必须要知道动态绑定如何与Java的类型检查互动。例如,如果UndergradStudent是Student类的一个子类,可以将UndergradStudent类型的对象赋给Student类型的变量,如

Student s = new UndergradStudent();

但这还没有完。

虽然,可以将UndergradStudent类型的对象赋给Student类型的变量s,但不能使用s来调用仅定义在UndergradStudent类内的方法。不过,如果在类UndergradStudent的定义中重写了一个方法,则会使用UndergradStudent内定义的这个版本。换句话说,变量决定使用哪个方法名,但对象决定使用方法名的哪个定义。如果想藉由Student类型的变量s命名的对象,来使用首次定义在类UndergradStudent中的方法名,则必须使用类型转型。

示例。Student实现了方法display。且UndergradStudent是Student的子类。下列语句是合法的:

Student s = new UndergradStudent(. ..);

s.setName(new Name("Jamie","Jones"));

s.display();

使用的是UndergradStudent类内给出的display的定义。记住,对象,而不是变量,决定将使用方法的哪个定义。若仅在UndergradStudent中定义了setDegree()方法,则下列语句是不合法的:

s.setDegree("B.A.");// ILLEGAL

因为setDegree不是Student类内的方法名。记住,变量决定使用哪个方法名。

变量s是Student类型的,但它指向UndergradStudent类型的一个对象。那个对象仍可以调用方法setDegree,但编译程序不知道这一点。为了让调用合法,必须进行类型转型,如下这样:

UndergradStudent ug =(UndergradStudent)s;

ug.setDegree("B.A.");// LEGAL

(文章来自微信公众账号:开点工作室(kaidiancs))

《横扫offer---程序员招聘真题详解700题》,开点工作室著,清华大学出版社出版,天猫、京东等各大网上书店及实体书店均已开始发售。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值