Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » Java SE 综合讨论区 » Java与OOP初步  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 [原创]Java中多态变量的讨论和总结
jimshen





发贴: 18
积分: 2
于 2006-08-08 06:04 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
一、多态的表现形式

多态的表现形式有方法重载,方法改写,多态变量和泛型。重载是一种静态的多态性,在程序编译时确定被调用的方法,称为早绑定。而多态变量和改写相结合之后,方法的调用在运行时才能确定,是动态的多态性,称为晚绑定。

二、里氏替换原则(The Liskov Principle of Substitution)

在静态类型语言中,在父类和子类之间的关系存在下面的现象:

子类的实例必须拥有父类的所有数据成员;
子类的实例必须至少通过继承(如果不是显示地改写)实现父类所定义的所有功能;
这样,在某种条件下,如果用子类实例来替换父类实例,那么将会发现子类实例可以完全模拟父类的行为,二者毫无差别;
替换原则是指如果有A和B两个类,类B是类A的子类,那么在任何情况下都可以用类B来替换类A,而外界则毫无察觉。

不是所有继承产生的子类都符合替换原则,符合替换原则的子类称为子类型。

三、静态类型和动态类型

在静态类型面向对象语言中,一个变量所存储的值的类型并不等同于这个变量所声明的类型。声明为父类类型的变量可以包含子类的实例值。

静态类型是指变量在声明时所确定的类型,并且一经声明就不会改变;动态类型是指这个变量实际存储的值的类型。在静态类型的面向对象程序设计语言中,在编译时消息传递表达式的合法性不是基于接收器的动态类型,而是基于接收器的静态类型。而对象对消息的响应取决于对象的动态类型。

这样的变量在多态中称为多态变量。

考虑下面的类图:


对应的程序如下:

class SuperClass {
  public int x;
  public void f1() {
  }
  public void f2() {
  }
}
class SubClass extends SuperClass{
  public int y;
  public void f1() {
  }
  public void f3() {
  }
}
public class Test{
  public static void main(String[] args){
    SuperClass ob=new SuperClass(); //(1)
    ob=new SubClass(); //(2)
    ob.x=1; //(3)
    ob.y=2; //(4)
    ob.f1(); //(5)
    ob.f2(); //(6)
    ob.f3(); //(7)
  }
}

程序中,子类改写了父类中的f1()方法,添加了自己的属性y和方法f3()。语句(1)中变量ob的静态类型是SuperClass,动态类型是SuperClass;语句(2)中变量ob的静态类型不变,动态类型是SubClass(子类类型),子类对象的地址可以存放在父类类型的引用变量中,即父类引用变量引用子类对象;(3)、(5)、(6)是正确的,(4)、(7)是错误的,因为对于引用的合法性依赖于变量的静态类型,属性y和方法f3()在SuperClass中不存在,故有错,还应该注意参数的匹配;(5)调用的f1()是子类的f1(),(6)调用的f2()是父类的f2(),这是因为对于消息的响应取决于变量的动态类型,因此f1()是SubClass中的f1()。
如果在父类和子类中存在属性的覆盖,则通过ob(父类对象名)访问的x是父类中被覆盖的属性。

class SuperClass {
  public int x=1;
  public void f1() {
  }
  public void f2() {
  }
}
class SubClass extends SuperClass{
public int x=2;
  public int y;
  public void f1() {
  }
  public void f3() {
  }
}
class Test{
  public static void main(String[] args){
    SuperClass ob=new SuperClass(); //(1)
    ob=new SubClass(); //(2)
System.out.println(ob.x);
    ob.f1(); //(5)
    ob.f2(); //(6)
  }
}

四、父类对象和子类对象

子类从父类继承了所有的属性和方法,因此作用在父类上的方法应用在子类对象上也是合法的。由于继承表达的是一种is a关系,即子类对象可以被视为父类的一个对象,因此可以把子类对象的引用赋给父类对象;反之,父类对象不一定是其某个特定子类的对象,因此不一定满足is a关系,因此不能把父类对象的引用直接赋给子类对象。

class Employee {
private String name;
private int salary;

Employee(String name,int salary){
  this.name=name;
  this.salary=salary;
}

public String getDetails() {
  return Name: " + name + "\nSalary: " + salary;
}
}

class Manager extends Employee {
String department;

Manager(String name,int salary,String department){
  super(name,salary);
  this.department=department;
}

public String getDetails() {
  return super.getDetails() + "\nDepartment: " + department;
}
}

public class Test{
public static void main(String args[]){
  Employee a=new Employee("Mike",800);
  Manager b=new Manager("Tom",1200,"Research");
  Manager c=a; //Error
  Employee d=b; //True
  System.out.println(A.getDetails());
  System.out.println(D.getDetails());
}
}

根据消息传递的概念,对消息的响应取决于接收器。而这又依赖于接收器的动态类型。因此,当父类对象中存放了子类对象的引用,并且父类和子类中有方法覆盖时,通过父类对象将会调用子类中的方法。

同时,在编译时消息传递表达式的合法性不是基于接收器的动态类型,而是基于接收器的静态类型。因此,当父类对象中存放了子类对象的引用时,不能通过父类对象名引用子类中新定义的成员(包括属性和方法)。例如,下面的语句是错误的:

System.out.println(d.department);

可以通过强制类型转换把父类对象转换成子类对象,然后再进行访问:

Manager e=(Manager) d;
System.out.println(e.department);

五、运行时类型识别和向下造型

替换原则将数据的类型从子类提升到了父类。有时也需要做相反的事情,例如判断父类对象所存放的值是否是某个子类的对象。

类型的强制类型转换只能用于子类唯一这种确定的情况下,如果子类不止一个,则可能发生错误,可以通过instanceof运算符来识别子类对象所属的类。

public class Manager extends Employee
public class Contractor extends Employee

假设有一个子类对象,可以通过下面的语句来判断它所述的类并执行相应的操作:

if(a instanceof Manager){
Manager b=(Manager) a;
......
}
else if(a instance of Contractor){
Contractor b=(Contractor) a;
......
}

例如在上面的操作中,可以把a这个父类对象转换成相应的子类对象,从而去访问子类中新增的属性或方法,这称为向下造型(down casting)。造型(cast)也就是通常所说的类型转换。类型的提升称为向上造型(up casting)。

六、多态和抽象类、抽象方法

抽象类不能实例化,但是可以声明抽象类的变量用以存放其子类对象的引用。另外,抽象方法为其子类规定了一个必须实现的接口,这样在多个子类中就拥有一个相同的接口方法。

在程序中可以定义一个父类的数组用于保存所有子类的对象,从而保持程序的简洁和灵活性。下面的程序说明了这个问题。


abstract class Shape{
public abstract void draw();
}
class Circle extends Shape{
private int x;
private int y;
private int radius;
public Circle(int x,int y,int radius){
this.x=x;
this.y=y;
this.radius=radius;
}
public void draw() {
System.out.println("draw circle: ("+x+","+y+"),radius="+radius);
}
}
class Line extends Shape{
  private int x1,y1,x2,y2;
  public Line(int x1,int y1,int x2,int y2){
    this.x1=x1;
    this.x2=x2;
    this.y1=y1;
    this.y2=y2;
  }
  public void draw(){
    System.out.println("draw line,("+x1+","+y1+")-("+x2+","+y2+")");
  }
}
public class TestShape{
  public static void main(String[] args){
    Shape[] s=new Shape[4];
    s[0]=new Circle(1,1,10);
    s[1]=new Line(3,4,20,12);
    s[2]=new Circle(3,10,20);
    for(int i=0;i<s.length;i++){
      if(s[i]!=null)
        s[i].draw();
    }
  }
}

如果没有多态这个特性或者不利用多态这个特性,则程序将复杂得多。

Java中所有的类的最终根类是java.lang.Object,因此可以Object类型的引用变量可以存放所有Java对象的引用,也使用一个Object数组保存Java中所有的对象。


jimshen edited on 2006-08-08 06:29


搞笑QQ图片

话题树型展开
人气 标题 作者 字数 发贴时间
12157 [原创]Java中多态变量的讨论和总结 jimshen 6287 2006-08-08 06:04
10302 Re:[原创]Java中多态变量的讨论和总结 jimshen 46 2006-08-08 06:30
10176 Re:[原创]Java中多态变量的讨论和总结 jimshen 391 2006-08-08 06:36
9956 Re:[原创]Java中多态变量的讨论和总结 jameszhang 38 2006-08-08 19:52
10104 Re:[原创]Java中多态变量的讨论和总结 jimshen 20 2006-08-08 21:50
10847 Re:[原创]Java中多态变量的讨论和总结 Java9 26 2006-08-17 13:00

flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923