运行时类型信息使得你可以在程序运行时发现和使用类型信息。——《Think in java 4th》


通常我们在面向对象的程序设计中我们经常使用多态特性使得大部分代码尽可能地少了解对象的具体类型,而是只与对象家族中的一个通用表示打交道,这样代码会更容易写,更容易读,且便于维护,设计也更容易实现、理解和改变。所以“多态”是面向对象编程的基本目标。但是,有些时候能够知道某个泛化引用对确切类型,就可以使用最简单的方式去解决它,或者我们必须去了解其确切功能和隐藏部分去完成某个功能时,使用RTTI和反射机制可以帮助我们去完成我们想要实现的功能。


1. Class对象

        类是程序的一部分,每个类都有一个Class对象。为了生成这个类的对象,jvm使用了被称为“类加载器”的子系统。类加载器子系统实际上包含一条类加载器链,但是只有一个原生类加载器,它是jvm实现的一部分(native)。如果你有特殊需求,也可以添加额外对自定义类加载器。

        所有的类都是在对其第一次使用时,动态加载到jvm中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法 (引用自java编程思想第四版P315) ,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的对象也会被当做对类的静态成员引用。

        因此,Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。这一点与许多传统语言都不同,动态加载使能对行为,在诸如c++这样的静态加载语言中是很难或者根本不可能复制的。

        一旦一个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

1)获取Class类对象引用的方法

① Class.forName("全类名");

这个方法是Class类的一个static成员,Class对象就和其他对象一样,我们可以获取并操作它的引用(这就是类加载器的工作),forName()是取得引用的一种方法。它会使用调用类的类加载器加载指定的类。如果找不到目标类则会抛出ClassNotFoundException。使用此方法,你不需要为了获得Class引用而持有该类型的对象。

② 对象.getClass();

如果你已经有了一个想要的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,这个方法属于Object类的成员方法,它将返回表示该对象实际类型的Class引用。如果你有一个Class对象,还可以使用getInterfaces()getSuperclass()等方法获取接口类Class对象和基类Class对象,isInterface()方法判断是否为接口。

③ 类字面常量 类.class

Java还提供了另一种方法来生成对Class对象的引用(例:Object.class),相比于Class.forName()不仅更简单、而且更安全,因为它在编译器就会受到检查(因此不需要置于try语句块中),根除了对forName()方法的调用,所以也更高效。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象:



Java编程思想中建议使用.class的形式,以保持与普通类的一致性。

有趣的是,使用.class来创建对Class对象的引用时,不会自动初始化该Class对象。


为了使用类而做的准备工作实际包含三个步骤:



仅使用.class语法来获得对类的引用不会引发初始化,但是使用Class.forName()立即就进行了初始化。如果一个static final值是“编译期常量”,那么这个值不需要进行初始化就可以被读取。


2)泛化的Class引用

定义一个Class对象的引用可以任意地指向任何Class对象

Class intClass = int.class;
intClass = double.class;

然而如果你操作有误(将本应指向int.class的引用指向了double.class),只有在运行时才可能发现错误的赋值,因为编译器不知道那你的意图,不会报错。Java SE5提供了Class泛型,对Class引用指向的Class对象进行了限定。

Class<Integer> intClass = int.class;
//intClass = double.class;//编译期报错

使用Class泛型的目的是让编译期进行类型检查,误操作时编译期报错,及时发现错误。

通配符?表示任何类型

Class<?> intClass = int.class;
intClass = double.class;

Class与Class使用效果是等价的,但是Class有不同的表示意义,Class<?>表示我就是选择了不具体的类型。

Integer是Number子类,但Integer的Class对象 不是 Number的Class对象 的子类

//class<Number> numberClass = int.class;//不合法

使用Class<? extends Number>表示一个范围,限定Class为Number子类的Class对象

class<? extends Number> numberClass = int.class;//OK

使用泛型的Class引用.newInstance(),返回的是确切类型的实例对象,而不是Object。

除了类似Class<? super FancyToy>的引用调用newInstance(),返回Object类型的实例。

Class<? super FancyToy>表示“某个类,是FancyToy的超类”,是含糊的,所以返回Object类型的实例。

class Toy{}

class FancyToy extends Toy{}

public class GenericClass {

	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<? super FancyToy> up = Toy.class;
Object obj = up.newInstance();//返回Object
Class<Toy> toyClass = Toy.class;
Toy toy = toyClass.newInstance();//返回Toy
} }

3)新的转型语法

Java SE5还添加了用于Class引用的转型语法,即cast()方法:

class Building {}
class House extends Building {} public class ClassCasts {
public static void main(String[] args){
Building b = new House();
Class<House> houseType = House.class;
House h = houseType.cast(b); // 等价操作
h = (House)b; // 等价操作
}
}

cast() 方法接受参数对象,并将其转型为Class引用的类型,新的转型语法对于无法使用普通转型的情况显得非常有用,在你编写泛型代码时,如果你存储了Class引用,并希望以后通过这个引用来执行转型,这种情况就可以解决。

在javaSE5中另一个新特性就是 Class.asSubclass() ,该方法允许你将一个类对象转型为更具体的类型,它将一个类转换成另外一个的实例,如果转换异常就会抛出ClassCastException异常,也就是这个类不是另外一个类的实例;所以我们可以通过它抛出异常的方式来判断一个类是否是另外一个类的实例。(java编程思想中说这是个没什么用的特性) Class.asSubclass浅谈

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; public class Teacher { public static void main(String[] args) { Class<?> clazz = B.class;
String name = clazz.getName();
try {
Class classA = clazz.asSubclass(A.class);
} catch (ClassCastException e) {
System.out.println(name+"不是类StopThread的子类");
}
}
}

4)类型转换检查

在对对象进行转型的时候需要先进行类型检查,否则可能会抛出ClassCastException

① instanceof 关键字

对 instanceof 有比较严格的限制:只可以将其与命名类型进行比较,而不能与Class对象作比较。

A a = new B();
if(a instanceof B) {
B b = (B)a;
}

需要说明的是 instanceof 关键字检查的是is-a关系,也就是可以判断继承关系,a instanceof Object总为true,而直接getClass()并进行相等(==、equals 结果一致)比较只能比较确切类型,并且不同类加载器加载的同一个类的Class是不同的。

② Class.isInstance() 方法

Class.isInstance() 方法提供了一种动态地测试对象的途径。

A a = new B();
if( B.class.isInstance(a) ) {
B b = (B)a;
} // 与 instanceof 的结果一致

能够完成一些使用 instanceof 关键字 无法完成的效果。


5)反射:运行时的类信息

        如果不知道某个对象的确切类型,RTTI可以告诉你,但有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。

        在传统的编程环境中不太可能出现这种情况。但当我们置身于更大规模的编程世界中,在许多重要情况下我们可能需要获取一个指向某个并不在你的程序空间中的对象的引用,事实上,在编译时你的程序根本没法获知这个对象所属的类,比如从磁盘的某个文件中,或者网络连接中获取了一串字节,并且被告知这些字节代表了一个类,那么怎样才能使用这样的类呢?

        Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethod以及Construtor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象相关联的字段,用invoke()方法调用与Method对象相关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象数组(详情可查找Class类的JDK文档 )。

Java高级特性——反射(详细方法): https://www.jianshu.com/p/9be58ee20dee

// 使用反射展示类的所有方法, 即使方法是在基类中定义的
package typeinfo; // Print类的print方法等价于System.Out.Println,方便减少代码量
import static xyz.util.Print.*; import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern; // {Args: typeinfo.ShowMethods}
public class ShowMethods {
private static String usage =
"usage:\n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or:\n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
// 去掉类名前面的包名
private static Pattern p = Pattern.compile("\\w+\\.");
public static void main(String[] args) {
if (args.length < 1) {
print(usage);
System.exit(0);
}
int lines = 0;
try {
Class<?> c = Class.forName(args[0]);
// 反射获得对象c所属类的方法
Method[] methods = c.getMethods();
// 反射获得对象c所属类的构造
Constructor[] ctors = c.getConstructors();
if (args.length == 1) {
for (Method method : methods)
print(p.matcher(method.toString()).replaceAll(""));
for (Constructor ctor : ctors)
print(p.matcher(ctor.toString()).replaceAll(""));
}
} catch (ClassNotFoundException e) {
print("No such class: " + e);
}
} /*
public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()
*/
}

Java 反射获取私有方法:https://www.cnblogs.com/raincute/p/9848483.html

通常我们创建一个类时,它的私有方法在类外是不可见的,但是可以通过反射机制来获取调用。

Method的setAccessible()方法可以绕过私有的安全检查,所以我们就可以调用类的私有方法。

method.setAccessible(true);//获取私有权限
field.setAccessible(true);

参考文章:https://blog.csdn.net/gd_hacker/article/details/80272159


总结: 引用 《 Think in java 4th》

参考文章:https://www.cnblogs.com/Harley-Quinn/p/5263802.html

【JavaSE】运行时类型信息(RTTI、反射)的更多相关文章

  1. 了解运行时类型信息(RTTI)

    RTTI需要引用单元TypeInfo GetPropInfo 函数用于获得属性的 RTTI 指针 PPropInfo.它有四种重载形式,后面三种重载的实现都是调用第一种形式.AKinds 参数用于限制 ...

  2. 是否含有RTTI(运行时类型信息)是动态语言与静态语言的主要区别

    运行时类型信息代表类型信息和对内存的操作能力. 运行时类型信息是运行时系统的基础. 类型信息分为编译时类型信息和运行时类型信息两种: 静态语言的类型信息只在编译时使用和保留,在可执行文件中没有类型信息 ...

  3. C++之运行时类型识别RTTI

     C++ Code  12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...

  4. c++运行时类型识别(rtti)

    一个简单运行时类型识别 namespace rtti_ex { /* * 类型信息基类 */ class i_type_info { public: // 判断是否是指定类型 bool is(cons ...

  5. C++学习之显式类型转换与运行时类型识别RTTI

    static_cast const_cast reinterpret_cast 运行时类型识别(RTTI) dynamic_cast 哪种情况下dynamic_cast和static_cast使用的情 ...

  6. 运行时类型识别RTTI

    1.RTTI的工作原理 例1. 用Class加载对象示例. package RTTI; public class Candy { static{ System.out.println("Lo ...

  7. C++——运行时类型识别RTTI

    1.实现方式 typeid运算符,返回表达式的类型 dynamic_cast运算符,基类的指针或引用安全地转换成派生类的指针或引用 2.适用于:使用基类的指针或引用执行派生类的操作,且该操作不是虚函数 ...

  8. Java基础之RTTI 运行时类型识别

    运行时类型识别(RTTI, Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息. 多态(polymorphism)是基于R ...

  9. RTTI (Run-Time Type Identification,通过运行时类型识别) 转

    参考一: RTTI(Run-Time Type Identification,通过运行时类型识别)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型.   RTTI提供了以下两个 ...

随机推荐

  1. 伪造请求头向url传递参数爬取百度默认翻译

    from urllib import request,parse import json # 翻译函数 def fanyi(msg): #参数封装 data = { "kw": c ...

  2. IDEA-Tomcat 运行报错

    我的问题是SDK版本不一致

  3. ABAP字符串处理

    字符串中包含单引号:单引号前面再加一个单引号 例:jest~stat = 'E0002' jest~stat = 'E0003' OR jest~stat = 'E0004' IF z_stat IS ...

  4. vue自定义组件添加原生事件监听

    注:全局或局部注册的组件称为子组件,其中声明的组件名称(如下demo中的child)是一个自定义组件 Demo1-直接给父组件添加事件监听 <!DOCTYPE html> <html ...

  5. HDU-4003 Find Metal Mineral 树形DP (好题)

    题意:给出n个点的一棵树,有k个机器人,机器人从根节点rt出发,问访问完整棵树(每个点至少访问一次)的最小代价(即所有机器人路程总和),机器人可以在任何点停下. 解法:这道题还是比较明显的能看出来是树 ...

  6. 运用pool进程池启动大量子进程

    # Pool进程池类 from multiprocessing import Pool import os import time import random def run(index): prin ...

  7. Java集合体系结构(List、Set、Collection、Map的区别和联系)

    Java集合体系结构(List.Set.Collection.Map的区别和联系) 1.Collection 接口存储一组不唯一,无序的对象 2.List 接口存储一组不唯一,有序(插入顺序)的对象 ...

  8. 【leetcode】654. Maximum Binary Tree

    题目如下: Given an integer array with no duplicates. A maximum tree building on this array is defined as ...

  9. table 中 当前行变量的获取

  10. jsp标签的介绍

    cankao:http://www.cnblogs.com/xdp-gacl/p/3788369.html jsp常用的标签有以下3个 1.<jsp:include>标签 2.<js ...