java之类型信息(Type Information)

Runtime type information (RTTI) allows you to discover and use type information while a program is running.
运行时类型信息使得你在程序运行时可以发现和使用类型信息。

我们需要注意几个关键字:程序运行时、发现、使用、类型信息。

那么在java中,RTTI机制的实现方式是什么样的呢?
有两种方式:

  • 一种是“传统的”RTTI,在编译期就已经知道了所有的类型,也就是所有的类都是在编译期被编译器编译过的,生成了字节码(.class)文件;
  • 另一种是“反射”机制,它允许我们在运行时发现和使用类的信息,也就是说,在编译期是不知道此类型(类),此类型是在编译期之后,运行时才出现的,比如从磁盘或者网络获取的一串字节,这些字节代表一个类,是字节码。

我们用大白话描述一下,只有经过编译的类,程序员在写代码的时候,才能通过new的方式实例化类(类型转换、instanceof类型判断),因为编译器知道这个类的位置和名字。如果是从网络上获取的类,首先需要明确的是,从网络上获取类肯定是在运行期,编译期程序还没运行;也就是说,我们程序员在写代码时,根本就不知道这个类的存在,更谈不上通过new的方式实例化一个这个类的对象了,所以,只能通过反射的机制使用这个类。

有时语言很难描述一个概念的含义,比如RTTI这个概念,我们需要用心体会实际的内涵,并辅以大量的语言描述才能表述清楚。
从上面的描述,我们知道RTTI是在运行期才起作用的,无论是传统的RTTI还是反射都是在运行期才有效的,比如,传统RTTI,到运行期才能发现类,使用类信息实例化一个对象。只是在编译(编码)的时候有区别,对于传统RTTI,在编码的时候,已经知道类名的,所以可以采用类名的方式编码,比如实例化对象、类型转换和判断对象类型等都可以直接用类名。对于反射,在编码时(现在都是实时编译的,编码和编译期可以认为是一个时期),类名是不知道的,当然类中的方法名和字段名也是不知道,所以java引入了一套反射机制编码,目的也是在运行期能发现和使用这些在编译期不知道的类。

上面反复提到的一句话是运行期发现和使用类型信息。那么运行期的类型信息是啥?其实就是Class类的对象,每个定义的类,到运行期被装载进内存后都会生成一个Class对象,这个就是运行期的类型信息了。

传统的RTTI的表现形式

在我们编码过程中,传统的RTTI体现形式有三种:

  • 传统的类型转换,如“(Shape)” ,由RTTI确保类型转换的正确性,如果执行一个错误的类型转换,就会抛出一个ClassCastException异常。
    我理解传统的类型转换,不仅包括向下类型转换也包括向上类型转换,在转换过程中都会用到RTTI,具体怎么用的,这个是由jvm来实现的。
    举一个实际的例子,Circle、Square和Triangle是Shape的子类,有一个List存放Shape的子类对象。
    在这个例子中,当把Shape对象放入List的数组时会向上转型,但在向上转型为Shape的时候也丢失了Shape对象的具体类型。对数组而言,它们只是Shape类的对象。
    当从数组中取出元素时,这种容器(实际上它将所有的事物都当作Object持有)会自动将结果转型回Shape。这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。

“在java中所有的类型转换都是运行时进行正确性检查的”,我不认同这句话,只有父类引用强制转换成子类引用的情况,才会在运行期检查,没有继承关系的类型转换在编译期就能进行检查。

在运行期对类型转换进行检查,其实就是发现和使用类型信息的过程。以向下类型转换举例,其实就是比较被转换对象的类型和括号中的类的类型是否相同。
疑问:向上类型转型,在运行期有进行类型检查吗?具体原理是什么样的呢?

  • 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。我理解是通过类字面量.class的方式去获取Class对象
  • RTTI在Java中的第三种形式,就是关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。

需要说明的是,RTTI和多态是两个概念。在实际开发过程中尽量使用多态,只有必要的时候才使用RTTI。也就是说,开发人员在一般情况下不需要了解具体的对象,只有确实需要特殊对象时才用RTTI进行类型转换。

Class对象

反射:运行时的类信息

The class Class supports the concept of reflection, along with the java.lang.reflect library which contains the classes Field, Method, and Constructor (each of which implements the Member interface). Objects of these types are created by the JVM at run time to represent the corresponding member in the unknown class. You can then use the Constructors to create new objects, the get( ) and set( ) methods to read and modify the fields associated with Field objects, and the invoke( ) method to call a method associated with a Method object. In addition, you can call the convenience methods getFields( ), getMethods( ), getConstructors( ), etc., to return arrays of the objects representing the fields, methods, and constructors. (You can find out more by looking up the class Class in the JDK documentation.) Thus, the class information for anonymous objects can be completely determined at run time, and nothing need be known at compile time.

It’s important to realize that there’s nothing magic about reflection. When you’re using reflection to interact with an object of an unknown type, the JVM will simply look at the object and see that it belongs to a particular class (just like ordinary RTTI). Before anything can be done with it, the Class object must be loaded. Thus, the .class file for that particular type must still be available to the JVM, either on the local machine or across the network. So the true difference between RTTI and reflection is that with RTTI, the compiler opens and examines the .class file at compile time. Put another way, you can call all the methods of an object in the “normal” way. With reflection, the .class file is unavailable at compile time; it is opened and examined by the runtime environment.

为啥java中需要有反射机制呢?

我们想想,平时在开发的过程中,我们是怎么使用一个类的呢?我们要调用一个类中定义的方法的时候,都是先new一个类的实例,A a = new A(),然后a.method()就可以了;我们能用new和点语法这样写代码是因为编译器做了很多工作,首先编译器是对A类编译过的,也就能能找到代表A类的字节码文件,这样你才能知道A这个名字,才能new A;也就是因为编译器检查过,我们在写代码的时候,才能知道A中有个方法名就method,才能用点语法调用知道名字的方法。也就是说,通过编译器的帮助,发现类的信息,程序员用new和点语法使用类的信息。
那么,我们再想一个问题,难道我们java程序在运行起来后,要使用从磁盘或者网络下载的一个新类(一个类的字节码文件)还能使用new和点语言吗?肯定不能呀,首先明确一点,一个软件的所有的代码都是程序员一开始写好的,不会程序运行起来还会自己写代码(除了IDE工具);程序运行起来会从网络上下载各种各样的类,程序员也一开始写代码的时候是不知道这些类的,他只知道将来运行起来会从网络上下载类来使用,即便他在编码时候知道,将来从网络上下载的类的名字和内部细节,在编码的时候也不能用new和点语法,因为编译期找到这个类,编译都通不过(写到这里,我有一个想法,能不能在编码的时候提前知道要下载的类的接口来解决呢?《Effective Java》中第65条给出了方案);要使用从网络上下载下来的类,就必须用到反射机制了,在运行时,jvm加载从网络上下载的类,用Class.forName类方法来加载,jvm就知道这个类和类内部的细节了,然后会给类中的每个构造函数、字段和方法都生成对应的对象,对应的是Constructor、Field和Method对象(经过编译期加载进来的类,也是有这些对象的),通过这些对象就可以创建实例,调用方法和字段了。在运行时,是没有程序员参与的,程序发现和使用类的信息,只能通过反射机制。

传统RTTI是通过编译器发现类,和程序员的配合来使用类,具体表现就是new和点语法;反射机制是在运行时,程序自己发现类和使用类。

也就是说反射提供了一种让我们的程序可以在运行期使用没有经过编译期编译过的类的方案。

《Effective Java》中第65条:接口优先于反射机制

核心反射机制(core reflection facility),java.lang.reflect包,提供了“通过程序来访问任意类”的能力。
如果你编写的程序必须要与编译时未知的类一起工作,如有可能,就应该仅仅使用反射机制来实例化对象,而访问对象时则使用编译时已知的某个接口或者超类。

动态代理

Java’s dynamic proxy takes the idea of a proxy one step further, by both creating the proxy object dynamically and handling calls to the proxied methods dynamically. All calls made on a dynamic proxy are redirected to a single invocation handler, which has the job of discovering what the call is and deciding what to do about it.

参考

JAVA 反射用法
JAVA 笔记(四) RTTI - 运行时类型检查

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值