在对Java学习的过程中,对于转型这种操作比较迷茫,特总结出了此文。例子参考了《Java编程思想》。

    目录

几个同义词

向上转型与向下转型

  例一:向上转型,调用指定的父类方法

  例二:向上转型,动态绑定

  例三:向上转型,静态绑定

  例四:向下转型

转型的误区

  1.运行信息(RTTI)

  2.数组类型

  3.Java容器

几个同义词

  首先是几组同义词。它们出现在不同的书籍上,这是造成理解混淆的原因之一。

  父类/超类/基类

  子类/导出类/继承类/派生类

  静态绑定/前期绑定

  动态绑定/后期绑定/运行时绑定

向上转型与向下转型

例一:向上转型,调用指定的父类方法

class Shape {
  static void draw(Shape s) {
System.out.println("Shape draw.");
}
} class Circle extends Shape {
  static void draw(Circle c) {
System.out.println("Circle draw.");
}
} public class CastTest {
public static void main(String args[]) {
Circle c = new Circle();
Shape.draw(c);
}
}

输出为

Shape draw.

  这表明,draw(Shape s)方法本来被设计为接受Shape引用,但这里传递的是Circle引用。实际上draw(Shape s)方法可以对所有Shape类的导出类使用,这被称为向上转型。表现的行为,和方法所属的类别一致。换句话说,由于明确指出是父类Shape的方法,那么其行为必然是这个方法对应的行为,没有任何歧义可言。

  “向上转型”的命名来自于类继承图的画法:根置于顶端,然后逐渐向下,以本例中两个类为例,如下图所示:

例二:向上转型,动态绑定

class Shape {
public void draw() {
System.out.println("Shape draw.");
}
} class Circle extends Shape {
public void draw() {
System.out.println("Circle draw.");
}
} public class CastTest {
public static void drawInTest(Shape s) {
s.draw();
}
public static void main(String args[]) {
Circle c = new Circle();
drawInTest(c);
}
}

输出为

  Circle draw.

  这样做的原因是,一个drawInTest(Shape s)就可以处理Shape所有子类,而不必为每个子类提供自己的方法。但这个方法能能调用父类和子类所共有的方法,即使二者行为不一致,也只会表现出对应的子类方法的行为。这是多态所允许的,但容易产生迷惑。

例三:向上转型,静态绑定

class Shape {
public static void draw() {
System.out.println("Shape draw.");
}
} class Circle extends Shape {
public static void draw() {
System.out.println("Circle draw.");
}
} public class CastTest {
public static void drawInTest(Shape s) {
s.draw();
}
public static void main(String args[]) {
Circle c = new Circle();
drawInTest(c);
}
}

输出为

  Shape draw.

  例三与例二有什么区别?细看之下才会发现,例三里调用的方法被static修饰了,得到了完全不同的结果。

  这两例行为差别的原因是:Java中除了static方法和final方法(包括private方法),其他方法都是动态绑定的。对于一个传入的基类引用,后期绑定能够正确的识别其所属的导出类。加了static,自然得不到这个效果了。

  了解了这一点之后,就可以明白为什么要把例一写出来了。例一中的代码明确指出调用父类方法,而例三调用哪个方法是静态绑定的,不是直接指明的,稍微绕了一下。

例四:向下转型

  出自《Java编程思想》8.5.2节,稍作了修改,展示如何通过类型转换获得子类独有方法的访问方式。

  这相当于告诉了编译器额外的信息,编译器将据此作出检查。

class Useful {
public void f() {System.out.println("f() in Useful");}
public void g() {System.out.println("g() in Useful");}
} class MoreUseful extends Useful {
public void f() {System.out.println("f() in MoreUseful");}
public void g() {System.out.println("g() in MoreUseful");}
public void u() {System.out.println("u() in MoreUseful");} } public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile-time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
}

输出

Exception in thread "main" java.lang.ClassCastException: Useful cannot be cast to MoreUseful
at RTTI.main(RTTI.java:44)
f() in Useful
g() in MoreUseful
u() in MoreUseful

  虽然父类Useful类型的x[1]接收了一个子类MoreUseful对象的引用,但仍然不能直接调用其子类中的u()方法。如果需要调用,需要做向下转型。这种用法很常见,比如一个通用的方法,处理的入参是一个父类,处理时根据入参的类型信息转化成对应的子类使用不同的逻辑处理。

  此外,父类对象不能向下转换成子类对象

  向下转型的好处,在学习接口时会明显地体会出来(如果把实现接口看作多重继承)。可以参考9.4节的例子,这里不做详述:

interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
} class ActionCharacter {
public void fight() {}
} class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
} public class Adventure {
static void t(CanFight x) { x.fight(); }
static void u(CanSwim x) { x.swim(); }
static void v(CanFly x) { x.fly(); }
static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args) {
Hero i = new Hero();
t(i); // Treat it as a CanFight
u(i); // Treat it as a CanSwim
v(i); // Treat it as a CanFly
w(i); // Treat it as an ActionCharacter
}
}

转型的误区

  转型很方便,利用转型可以写出灵活的代码。不过,如果用得随心所欲而忘乎所以的话,难免要跌跟头。下面是几种看似可以转型,实际会导致错误的情形。

1.运行信息(RTTI)

/* 本例代码节选自《Java编程思想》14.2.2节 */

Class<Number> genericNumberClass = int.class

  这段代码是无效的,编译不能通过,即使把int换为Integer也同样不通过。虽然int的包装类Integer是Number的子类,但Integer Class对象并不是Number Class对象的子类。

2.数组类型

/* 代码节改写《Java编程思想》15.8.2节,本例与泛型与否无关。 */

class Generic<T> {}

public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
//! gia = (Generic<Integer>[]) new Object[SIZE];
gia = (Generic<Integer>[]) new Generic[SIZE];
}
}

  注释部分在去掉注释后运行会提示java.lang.ClassCastException。这里令人迷惑的地方在于,子类数组类型不是父类数组类型的子类。在异常提示的后面可以看到

[Ljava.lang.Object; cannot be cast to [LGeneric;

  除了通过控制台输出的异常信息,可以使用下面的代码来看看gia究竟是什么类型:

        Object[] obj = new Object[SIZE];
gia = (Generic<Integer>[]) new Generic[SIZE];
System.out.println(obj.getClass().getName());
System.out.println(gia.getClass().getName());
System.out.println(obj.getClass().getClass().getName());
System.out.println(gia.getClass().getSuperclass().getName());

控制台输出为:

[Ljava.lang.Object;
[LGeneric;
java.lang.Object
java.lang.Object

  可见,由Generic<Integer>[] gia和Object[] obj定义出的gia和obj根本没有任何继承关系,自然不能类型转换,不管这个数组里是否放的是子类的对象。(子类对象是可以通过向上转型获得的,如果被转换的确实是一个子类对象,见例四)

3.Java容器

/* 代码节选自《Java编程思想》15.10节*/
class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
public class Test {
public static void main(String[] args) {
// 无法编译
List<Fruit> fruitList = new ArrayList<Apple>();
}
}

  明明Fruit的List是可以存放Apple对象的,为什么赋值失败?其实这根本不是向上转型。虽然可以通过getClass().getName()得知List<Fruit>和List<Apple>同属java.util.ArrayList类型,但是,假设这里可以编译通过,相当于允许向ArrayList<Apple>存放一个Orange对象,显然是不合理的。虽然由于泛型的擦除,ArrayList<Fruit>和ArrayList<Apple>在运行期是同一种类型,但是具体能持有的元素类型会在编译期进行检查。

Java入门记(二):向上转型与向下转型的更多相关文章

  1. Java入门记(五):容器关系的梳理(下)——Map

    注意:阅读本文及相关源码时,需要数据结构相关知识,包括:哈希表.链表.红黑树. Map是将键(key)映射到值(value)的对象.不同的映射不能包含相同的键:每个键最多只能映射到一个值.下图是常见M ...

  2. Java入门(二)——果然断更的都是要受惩罚的。。。

    断更了一个多月,阅读量立马从100+跌落至10-,虽说不是很看重这个,毕竟只是当这个是自己的学习笔记,但有人看,有人评论,有人认同和批评的感觉还是很巴适的,尤其以前有过却又被剥夺的,惨兮兮的. 好好写 ...

  3. Java中的向上转型和向下转型

    首先要明白一点向上转型和向下转型他们都是建立在继承的基础上. 一.向上转型 子类到父类的转换通常称作向上转型,通俗的说就是定义父类对象指向子类对象. 下面通过一个例子来深入理解向上转型. //定义一个 ...

  4. JavaSE(五)JAVA对象向上转型和向下转型

    今天做了一个测试的题目,发现自己还是很多问题没有静下心来做.很多问题是可以自己解决的但是自己一是没有读清题意,二是自己心里太急躁了.所以这个要自己应以为鉴! 对象的转型问题其实并不复杂,我们记住一句话 ...

  5. “全栈2019”Java第五十三章:向上转型和向下转型详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  6. Java向上转型和向下转型(附具体样例)

                                                Java向上转型和向下转型(附具体样例) 熬夜整理的关于Java向上和向下转型的样例,很的通俗易懂哦~~~~ 一 ...

  7. Java转型(向上转型和向下转型)

    在Java编程中经常碰到类型转换,对象类型转换主要包括向上转型和向下转型. 5.13.1 向上转型 我们在现实中常常这样说:这个人会唱歌.在这里,我们并不关心这个人是黑人还是白人,是成人还是小孩,也就 ...

  8. JAVA的向上转型和向下转型怎么理解呢?

    在定义中是子类向父类转型称为向上转型,父类向子类转型是向下转型(必须先向上转型过,才能向下转型), 但是在下面类定义后,我得到的结果却不同.求大佬解惑 class superclass{ public ...

  9. Java入门(二):注释和基本数据类型

    上次通过eclipse在控制台输出了hello world,是不是有点小激动啊,今天接着介绍Java基础知识. 一.Java注释 1.Java注释语句不会被编译器运行,不用担心代码因为许多注释语句显得 ...

随机推荐

  1. 实践总结 - 不可错过的Angular应用技巧

    angular的核心思想是通过数据驱动一切,其他东西都是数据的延伸. 套用Javascript一切皆对象的思想,在angular中可以说一切皆数据. 关于项目构建 (1) requirejs以及Yeo ...

  2. Eclipse调试时附加匹配版本的JAR包源码:Edit Source Loopup

  3. ERROR actor.OneForOneStrategy: org.apache.spark.SparkContext

    今天在用Spark把Kafka的数据往ES写的时候,代码一直报错,错误信息如下: 15/10/20 17:28:56 ERROR actor.OneForOneStrategy: org.apache ...

  4. Stanford NLP学习笔记1:课程介绍

    Stanford NLP课程简介 1. NLP应用例子 问答系统: IBM Watson 信息提取(information extraction) 情感分析 机器翻译 2. NLP应用当前进展 很成熟 ...

  5. XMPP 和 OpenFire

    XMPP XMPP(可扩展消息处理现场协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线现场探测.是一种数据传输协议. XMPP的前身是Jabber,一个开源形式组织产生的网络 ...

  6. Codeforces 28C [概率DP]

    /* 大连热身D题 题意: 有n个人,m个浴室每个浴室有ai个喷头,每个人等概率得选择一个浴室. 每个浴室的人都在喷头前边排队,而且每个浴室内保证大家都尽可能均匀得在喷头后边排队. 求所有浴室中最长队 ...

  7. IIS发布文件出现:未能加载文件或程序集“xxxx”或它的某一个依赖项。试图加载格式不正确的程序。

    解决方案:IIS——应用程序池—选中网站—高级设置——启用32位应用程序 :true.

  8. reverse engineering in ax

    install Visio2010 Premium(UML model template). not work in Visio 2013 and other version.

  9. .NET程序集的编译目标平台:X86 &AnyCPU &X64

    在我们测试平台上发布客户端组件,经常会碰到因为build的版本是x86还是anycpu而引起的application error的问题.借此,研究了一下X86,X64,AnyCPU的区别. 使用.ne ...

  10. 慕课网-Java入门第一季-7-1 如何定义 Java 中的方法

    来源:http://www.imooc.com/code/1577 所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块. 一般情况下,定义一个方法的语法是: 其中: 1. 访问修饰符:方法 ...