Java:类型信息,
Java:类型信息,
运行时类型信息使得你可以在程序运行时发现和使用类型信息。
RTTI(Run-Time Type Identification)通过运行时类型信息程序能使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类。
为什么需要RTTI?
拿一组有继承关系的类来说把。其中Shape时基类,Circle、Square或是Triangle都继承自Shape。当我们创建一个List<Shape>对象并将几个Circle、Square和Triangle的对象存入数组中。事实上对于这种容器来讲它将所有的事物都当作Object持有。当你从数组中取出对象的时候Object被转型成Shape,而不是其他类型。这是因为我们只知道这个List<Shape>保存的都是Shape,在编译时由容器和Java的泛型保证这点;而在运行时由类型转换操作保证这点。
对于List<Shape>中的对象,假如它们都在屏幕上显示(即显示为三角形、圆形等形状),当我们想用某种方法来旋转它们但想跳过圆形(因为对圆旋转并没有意义)时,就可以使用RTTI查询某个Shape引用指向的对象的确切类型,然后选择并剔除特例。
Class对象
Java使用Class对象来执行RTTI。
- 类是程序的一部分,每个类都有一个Class对象。每当编写并编译了一个新类,就会产生一个Class对象(更恰当地说时被保存在一个同名的.class文件中)。
- 为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被成为“类加载器”的子系统。
- 所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。(这证明构造器也是类的静态方法,即使构造器没有关键字static,因此使用new操作符创建类的新对象也会被当作对类的静态成员的引用)
- Java程序在它开始运行之前并非被完全加载,其各个部分时在必须时才加载的。这是Java与许多传统语言不同的一点
- 类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有收到破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)
package typeinfo;
class Candy { static { System.out.println("Loading Candy"); } }
class Gum { static { System.out.println("Loading Gum"); } }
class Cookie { static { System.out.println("Loading Cookie"); } }
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After create Candy");
try {
Class.forName("typeinfo.Gum");
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After create Cookie");
}
}
输出:
inside main
Loading Candy
After create Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After create Cookie
- 其中Class.forName("Gum")这个方法时Class类(重点:所有的Class对象都属于这个类)的一个static成员。Class对象就和其他对象一样,我们可以获取并操作它的引用。
- forName()是取得Class对象的引用的一种方法。参数为包含目标类的文本名(有大小写区分)的String作为输出参数,返回一个Class对象的引用。
一个类的使用包含三个步骤:
使用“.class”的形式来创建对Class对象的引用时,不会自动地初始化该Class对象。即只执行到1、2步骤。初始化被延迟到了对静态方法或者非常数静态域进行首次引用时才执行。
类型转换前先做检查
迄今为止,我们已知的RTTI形式包括:
- 其实还有第三种形式,就是关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例
if (x instanceof Dog){
((Dog)x).bark();
}
但这种方法限制性过大,因为它只可将其与明明类型进行比较,而不能和Class对象做比较,拿上例来说你只能在代码中手动打出Dog,而不能用一个对象去代替Dog,因此在需要大量的比较的时候需要输入大量的instanceof表达式。
改进:动态的instanceof
使用Class对象:使用Class对象的instanceof()方法,参数为目标对象,即上例的x(Class对象可以使用泛型,可以用泛型创建一个Class对象的容器,在大量比较的时候就不需要在代码中大量使用instanceof表达式了)
instanceof与Class的等价性
- a instanceof B 等价于 B.class.isInstance(a) 当对象a的类型与B相同或者是其派生类时,返回true
- a.getClass()==B.class 等价于 a.getClass.equals(B.class) 当且仅当对象a的类型与B相同时,返回true
反射:运行时的类信息
如果不知道某个对象的确切类型,RTTI可以告诉你。但有个限制:这个类型必须在编译时必须已知。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。
但我们在运行时有时候会从外部导入一些字节,这些字节代表了一个类,这个类会在你运行时(即编译后)才被知道,此时RTTI便不能处理。此时我们就需要运用反射机制。但这并不代表反射机制与RTTI有很大的不同。当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定类(就像RTTI那样)。在使用这个类做其他事情前必须先加载这个类的Class对象(该对象的.class文件必须可获取,或是在本地机器上,或是从网络获取)。
Class类与java.lang.reflect类库一起对反射的概念进行了支持。(用法不做具体说明,另行搜索)
区别:
- 对RTTI来说,编译器在编译时打开和检查.class文件
- 对反射机制来说,.class文件在编译时是不可获取的,所以实在运行时打开和检查.class文件
接口与类型信息
public interface A{
void f();
}
public Class B implements A{
public void f(){}
public void g(){}
}
当我们在程序中用A a = new B();的时候,我们可以使用在A中有的API,这在多态的时候讲过了。通过使用RTTI,我们可以发现a时被当作B实现的,通过将其转型为B,我们可以调用不在A中的方法。
这时完全合法和可接受的,但我们也许不想让客户端程序员这么做,因为这给了它们一个机会使得他使用超出他权限的一些东西。
当我们实现使用包访问权限的时候,在包外部的客户端就不能看到它了,因此当我们试图将其向下转型为B的时候将被禁止,因为包的外部没有任何B类型可以用。
static void callHiddenMethod(Object a,String methodName)throw Eception{
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(true);
g.invoke(a);
}
但通过上述函数(反射)仍可以访问到a中属于类B的方法,甚至是private方法!只要你知道方法名,就可以调用。将接口实现为一个私有内部类,匿名类都不能阻止反射到达并调用那些非公共访问权限的方法。
遗憾的是书上并没有介绍具体如何解决这个问题的方法。
以上总结自《Java编程思想(Thinking in Java)》——YayayaHong
相关文章
- 暂无相关文章
用户点评