java对象类型转换和多态性(实例讲解)

对象类型转换

分为向上转型和向下转型(强制对象转型)。 向上转型是子对象向父对象转型的过程,例如猫类转换为动物类;向下转型是强制转型实现的,是父对象强制转换为子对象。 这和基础数据类型的转换是类似的,byte在需要时会自动转换为int(向上转型),int可以强制转型为byte(向下转型)。

对于对象转型来说, 向上转型后子对象独有的成员将不可访问 。 意思是,在需要一只动物时,可以把猫当作一只动物传递,因为猫继承自动物,猫具有动物的所有属性。但向上转型后,猫不再是猫,而是被当作动物看待,它自己独有的属性和方法就不可见了。换句话说,向上转型后,只能识别父对象中的内容。

可以通过"引用变量 instanceof 类名"的方式来判断引用变量 所指向的对象 是否属于某个类,也就是说"对象是不是某类",例如声明一个猫类对象的引用"Cat c",然后"c instanceof Animal"表述的意思是"对象c是一种动物吗?"对于instanceof返回true的对象,都可以转换为类对象,只不过有些可能需要强制转换。

向上转型可以自动进行,这本就是符合逻辑的,狗类继承自动物类,它本身就是一只动物,因此在需要动物类的时候,丢一只狗过去就会自动向上转型成动物类。但这时狗已经不是狗,而是动物,所以狗独有的成员不再可见。

强制转换的方式和基础数据类型强制转换一样,都是在待转换对象前加上目标类型,例如将动物a强制转换为狗d: Dog d = (Dog)a 。

下面是一个对象类型转换的示例,很好地分析了能否转型、转型后能否访问某些成员等等。

class Animal {
 String name;
 Animal(String name) {this.name = name;}
}

class Cat extends Animal {
 String eyecolor;
 Cat(String name,String color) {super(name); this.eyecolor = color;}
}

class Dog extends Animal {
 String furcolor;
 Dog(String name,String color) {super(name); this.furcolor = color;}
}

public class OCast {
 public static void main(String [] args) {
 Animal a = new Animal("animal");
 Cat c = new Cat("cat","blue");
 Dog d = new Dog("dog","black");

 System.out.println( a instanceof Animal);//return true
 System.out.println( c instanceof Animal);//return true
 System.out.println( d instanceof Animal);//return true
 System.out.println( a instanceof Cat); //return false

 System.out.println(a.name); //return animal
 a = new Dog("yellowdog","yellow"); //object Dog upcast to Animal
 System.out.println(a.name);  //return yellowdog
 System.out.println(a instanceof Animal); //return true
 System.out.println(a instanceof Dog); //return true
 //System.out.println(a.furcolor); //error! because a was regarded as Animal
 Dog d1 = (Dog)a; // because "a instanceof Dog" is true,so force cast Animal a to Dog
 System.out.println(d1.furcolor);  //return yellow
 }
}

对于上面的 a = new Dog("yellowdog",yellow) ,a是Animal类型,但此时 它指向的是Dog对象。也就是说它是Dog,所以也是Animal类 ,所以 a instanceof Animal); 和 a instanceof Dog; 都是true,这是它的"指针"决定的。 但因为它的类型是Animal类型,类型决定了能存储什么样的数据,对于已经存在的但不符合类型的数据都是不可见的,所以Animal类型决定了它只能看到Dog对象中的Animal部分 。

如下图:

既然可以向上转型,配合instanceof的逻辑判断,就能实现很好的扩展性。例如,动物类的sing(Animal a)方法需要的是一个动物类,可以给它一只狗d,这时会向上转型(就像需要double类型却给了一个int数据一样),虽然转型了,但狗d的实际引用仍然是Dog对象,于是 if (a instanceof Dog) 判断为真,则调用能体现狗sing()方法特殊性的语句。如果传递一只猫,if判断一下并调用能体现猫sing()方法特殊性的语句。这样,任何时候想添加一只动物,都只需要增加一条if语句就可以了。

见下面的示例:

class Animal {
 String name;
 Animal(String name) {
 this.name = name;
 }
}

class Cat extends Animal {Cat(String name) {super(name);}}
class Dog extends Animal {Dog(String name) {super(name);}}

public class TestCast {
 public static void main(String [] args) {
 TestCast t = new TestCast();
 Animal a = new Animal("animal");
 Animal c = new Cat("cat");
 Animal d = new Dog("dog");
 t.sing(a);t.sing(c);t.sing(d);
 }

 void sing(Animal a) {
 if ( a instanceof Cat) {
  Cat cat = (Cat)a;
  System.out.println("cat is singing");
 } else if(a instanceof Dog) {
  Dog dog = (Dog)a;
  System.out.println("dog is singing");
 } else {
  System.out.println("not an instance of animal");
 }
 }
}

如果没有对象转型,那么Dog里要定义一次sing(),Cat里也要定义一次sing()。要增加一个动物类,动物类里也还要定义一次sing()。现在就方便多了,直接在sing()方法内部修改if语句就可以了。

注意,上面的sing()方法不属于Animal或其他子类的方法,而是独立定义在其他类里进行调用的。

多态

向上转型虽然在一定程度上提高了可扩展性,但提高的程度并不太高。以向上转型为基础,java的多态实现的扩展性更好更方便。

多态也叫动态绑定或后期绑定,它是执行期间进行的绑定,而非编译期间的绑定(这是静态绑定或称为前期绑定)。

多态的原理是:当向上转型后,调用一个被重写的方法时,本该调用的是父类方法,但实际上却会动态地调用子类重写后的方法。实际上,编译期间绑定的确实是父类方法,只不过在执行期间动态转调子类对应方法。

例如,Animal类的sing()方法,Cat和Dog类都重写了sing()方法。当需要一个Animal对象时,传递了一个Cat类,那么将调用Cat的sing()方法。动态绑定的逻辑正如下面的代码类似:

void sing(Animal a) {
 if ( a instanceof Cat) {
 Cat cat = (Cat)a;
 System.out.println("cat is singing");
 } else if(a instanceof Dog) {
 Dog dog = (Dog)a;
 System.out.println("dog is singing");
 } else {
 System.out.println("not an instance of animal");
 }
}

以下是一个多态的例子

class Animal {
 private String name;
 Animal(String name) {this.name = name;}

 public void sing(){System.out.println("animal sing...");}
}

class Cat extends Animal {
 private String eyeColor;
 Cat(String n,String c) {super(n); eyeColor = c;}

 public void sing() {System.out.println("cat sing...");}
}

class Dog extends Animal {
 private String furColor;
 Dog(String n,String c) {super(n); furColor = c;}

 public void sing() {System.out.println("dog sing...");}
}

class Lady {
 private String name;
 private Animal pet;
 Lady(String name,Animal pet) {this.name = name; this.pet = pet;}
 public void myPetSing(){pet.sing();}
}

public class DuoTai {
 public static void main(String args[]){
 Cat c = new Cat("catname","blue");
 Dog d = new Dog("dogname","black");
 Lady l1 = new Lady("l1",c);
 Lady l2 = new Lady("l2",d);
 l1.myPetSing();
 l2.myPetSing();
 }
}

编译后的执行结果为:

cat sing...
dog sing...

在上面的示例中,Lady类的构造方法和她调用sing()方法的代码为:

Lady(String name,Animal pet) {this.name = name; this.pet = pet;}
public void myPetSing(){pet.sing();}

如果构造出Lady对象的pet是Cat对象c,这个c首先会向上转型为Animal类,也就是说Lady的pet属性虽然指向的是"Cat c"对象,但它只能看见其中的父对象Animal部分。那么 myPetSing(pet.sing();) 方法自然会调用Animal类的sing()方法。 以上过程是编译器所认为的过程,也是静态绑定或前期绑定的过程。

但编译完成后,虽然pet属性只能看见Animal部分,但实际在执行时pet.sing()却换转换为执行c.sing()。就相当于做了一次对象类型强制转换 Cat petx = (Cat)pet 。 这是动态绑定或后期绑定的过程,也称为多态。

实际上,对象在被new出来后,它所涉及到的方法都放在code segment内存区中的一个方法列表中,这个列表中包含了子类、父类的方法,只不过有些时候不可见的方法无法去调用。当执行程序时,内部的机制可以从方法列表中搜索出最符合环境的方法并执行它。

实现多态的技术的关键点在于:

(1). 定义一个父类引用f,并将其指向子类对象,即进行向上转型 ;

(2). 重写父类的方法,并使用父类引用f去引用这个方法。这样就可以面向父类进行编程 。

正如上面的示例中,将pet定义为Animal类而非具体的子类,并在方法中调用pet.sing()。如此依赖,就无需考虑pet到底是Cat/Dog,在进行功能扩展添加Bird类时,完全不用再修改Lady类的这段代码。

再例如,父类Animal,子类Dog,方法sing()。

class Animal {public void sing(A);}
class Dog extends Animal {public void sing(B);}

public class Test {
 Animal a = new Dog(); //父类引用变量a指向子对象Dog,此时将向上转型
 a.sing();  //使用父类引用变量a引用被重写的方法sing(),执行时将动态绑定到Dog的sing()
}

以上这篇java对象类型转换和多态性(实例讲解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java基础教程之类型转换与多态

    我们之前使用类创造新的类型(type),并使用继承来便利我们创建类的过程.我将在这一讲中深入类型,并介绍多态(polymorphism)的概念.  类型检查 Java的任意变量和引用经过类型声明(type declaration),才能使用.我们之前见过对象数据.类数据.方法参数.方法返回值以及方法内部的自动变量,它们都需要声明其类型.Java是一种强类型(strongly typing)语言,它会对类型进行检查.如果我们错误的使用类型,将造成错误. 类型不符,卖萌无效 比如在下面的Test类中

  • 谈谈对Java多态性的一点理解

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底

  • java对象类型转换和多态性(实例讲解)

    对象类型转换 分为向上转型和向下转型(强制对象转型). 向上转型是子对象向父对象转型的过程,例如猫类转换为动物类:向下转型是强制转型实现的,是父对象强制转换为子对象. 这和基础数据类型的转换是类似的,byte在需要时会自动转换为int(向上转型),int可以强制转型为byte(向下转型). 对于对象转型来说, 向上转型后子对象独有的成员将不可访问 . 意思是,在需要一只动物时,可以把猫当作一只动物传递,因为猫继承自动物,猫具有动物的所有属性.但向上转型后,猫不再是猫,而是被当作动物看待,它自己独

  • 基于Java中对域和静态方法的访问不具有多态性(实例讲解)

    1.将方法调用同方法主体关联起来被称为 2.编译期绑定(静态)是在程序编译阶段就确定了引用对象的类型 3.运行期绑定(动态绑定)是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法 4.除了static方法和final方法(private方法属于final方法),其他所有方法都是后期绑定,Java中所有的方法都是通过动态绑定来实现多态 5.访问某个域的行为不具有多态性 package polymorphism; class SuperField { public int fi

  • java发送email一般步骤(实例讲解)

    java发送email一般步骤 一.引入javamail的jar包: 二.创建一个测试类,实现将要发送的邮件内容写入到计算机本地,查看是否能够将内容写入: public static void main(String[] args) throws Exception { // 1. 创建一封邮件 Properties props = new Properties(); // 用于连接邮件服务器的参数配置(发送邮件时才需要用到) Session session= Session.getDefaul

  • java 动态生成SQL的实例讲解

    代码如下: /** * 动态生成SQ及SQL参数L * @param ve 接收到的消息的CHGLIST * @param paramList MQ消息中的SQL参数 * @param t 泛型对象 * @param table 数据表 * @param list 可执行SQL语句集合 * @return */ public <T> String updateSqlAndParamList(Vector<String> ve,List<String> paramList

  • JAVA超级简单的爬虫实例讲解

    爬取整个页面的数据,并进行有效的提取信息,注释都有就不废话了: public class Reptile { public static void main(String[] args) { String url1=""; //传入你所要爬取的页面地址 InputStream is=null; //创建输入流用于读取流 BufferedReader br=null; //包装流,加快读取速度 StringBuffer html=new StringBuffer(); //用来保存读取页

  • Java分页查询--分页显示(实例讲解)

    当数据库中数据条数过多时,一个页面就不能显示,这是要设置分页查询,首先要使用的是数据库sql语句的limit条件实现分组查询 sql语句大概形式为: select * from table limit 开始索引,显示条数 用该语句就会实现分块查询,并且每页显示固定条数. 首先要实现后台分页,我们需要知道它有多少页,每页有多少行,这就需要知道一共多少行,调用sql语句时还需要知道每一页的开始索引,开始索引是根据当前页数算出来的,所以还需要知道当前页数,查询后会返回一个列表存储当前页数据.将这些属性

  • java RMI详细介绍及实例讲解

    java本身提供了一种RPC框架--RMI(即RemoteMethodInvoke远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote接口用于标识其方法可以从非本地虚拟机上调用的接口,只有在"远程接口"(扩展java.rmi.Remote的接口)中指定的这些方法才可远程使用,下面通过一个简单的示例,来讲解RMI原理以及开发流程: 为了真正实现远程调用,首先创建服务端工程rmi-server,结构如下: 代码说明: 1.User.java:用于远程调用

  • java构造器的重载实现实例讲解

    说到重载的问题,已经提过很多次了.我们可以把名称一致,但是内在参数不同的对象看成重载,可以说这些类的名称相同是很有迷惑性的.同时,构造器中装有许多的方法,那么构造器也是可以实现重载的功能.下面我们就构造方法格式.注意事项进行简单介绍,然后带来构造起的重载实例. 1.构造方法格式 public class 类名(){ 类名(String name); 类名 对象=new 类名(String name): } 2.注意事项 构造器必须与主类同名 构造器可以有参数 构造器可以重载 没有返回值 不添加构

  • java虚拟机jvm方法区实例讲解

    和java堆一样,方法区是一块所有线程共享的内存区域,用于保存系统的类信息,类的信息有哪些呢.字段.方法.常量池.方法区也有一块内存区域所以方法区的内存大小,决定了系统可以包含多少个类,如果系统类太多,方法区内存不够肯定会导致方法区溢出,虚拟机同样会抛出内存溢出信息.(内存溢出后面相关文章给大家总结) jdk6和jdk7中,方法区可以理解为永久区(Perm).永久区可以使用参数-XX:PermSize和-XX:MaxPermSize制定.默认情况下-XX:MaxPermSize为64MB.如果你

  • Java编写超时工具类实例讲解

    我们在开发过程中,在进行时间操作时,如果在规定的时间内完成处理的话,有可能会回到正确的结果.否则,就会被视为超时任务.此时,我们不再等待(不再执行)的时间操作,直接向调用者传达这个任务需要时间,被取消了. 1.说明 java已经为我们提供了解决办法.jdk1.5带来的并发库Future类可以满足这一需求.Future类中重要的方法有get()和cancel().get()获取数据对象,如果数据没有加载,则在获取数据之前堵塞,cancel()取消数据加载.另一个get(timeout)操作表明,如

随机推荐