RTTI

Java中的RTTI

RTTI可以在程序运行时发现和使用类型信息;传统的RTTI,在编译期和运行期已经知道了所有的类型。举个栗子,在数组中存放类A的实例,在A对象放入数组中时会进行向上转型为Object,取出时会自动将结果转型为A.在Java中所有的类型转换都是在运行时进行正确性检查的,即在运行时识别一个对象的类型。

Class对象

Class对象是RTTI中的核心,Class对象是一个特殊对象,其包含了与类有关的信息,可创建类的所有的“常规”对象(PS:即通过new出来的对象)。Java通过Class对象执行RTTI。

每个类都有一个Class对象,每经编译一个新类时都会产生一个Class对象。JVM通过类加载器子系统来生成该对象。所有的类都是在对其第一次使用时,动态地加载到JVM中。当程序创建第一个对类的静态引用时,就会加载这个类。即使构造方法没有使用static关键字。第一次使用new创建类的新对象也会被当作对类的静态成员的引用。

类加载器会首先检查类的Class对象是否已经加载。若未加载,默认的类加载器就会根据类名查找.class文件。一旦类的Class对象被载入内存,它就会被用来创建这个类的所有对象。

1
2
3
4
5
try {
Class.forName("springapp.domain.Product");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

public static Class<?> forName(String className)是Class类的一个static成员。其返回是一个Class对象的引用,该引用对应着传入的className。如果该Product还未被加载就加载它,在加载类Product时,Product的static{}会被执行。如果找不到要加载的类,则会抛出异常ClassNotFoundException。

1
2
3
4
5
6
7
8
9
10
11
12

try {
Class c=Class.forName("springapp.domain.Product");
System.out.println("ClassName: " + c.getName() + "\nSimpleName: " + c.getSimpleName() + "\nCanonical name: " + c.getCanonicalName());
Product p= (Product) c.newInstance();//创建实例
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}

输出结果

其中 class.newInstance()创建的类必须带有默认的构造方法。

类字面常量

类字面变量:用于生成对Class对象的引用。使用方法:类名.class。使用.class创建Class引用时,不会自动地初始化Class对象,其初始化被延迟到对静态方法(构造方法隐式是静态的)或者非常数静态域(即静态变量)进行首次引用时才执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

class CI {
public static Random sRandom = new Random(47);
public static void main(String[] args) throws ClassNotFoundException {
Class initable = I1.class;
System.out.println("After creating initble ref ");
System.out.println(I1.sfinal);
System.out.println(I1.sfinal2);
Class initable3 = Class.forName("springapp.domain.I3");
System.out.println("After creating Initable3 ref");
System.out.println(I3.sNonfinal);
}
}

class I1 {
static final int sfinal = 47;
static final int sfinal2 = CI.sRandom.nextInt(1000);
static{
System.out.println("I1");
}
}
class I3 {
static final int sNonfinal = 74;
static{
System.out.println("I3");
}
}

输出结果

从输出可以看出,仅使用.class是不会初始化类的引用,而Class.forName立即就对类的引用进行了初始化。如果一个static变量被声明为final常量,那么这个读取这个值也并不需要初始化Class。static fianl 是编译期常量,如果一个static不是final的变量,对其进行访问时,要先进行链接(为静态域分配空间)和初始化(初始化该存储空间)。

泛化的Class引用

泛化的Class引用:使用泛型限定Class对象的类型。

1
2
3
4
5
6
7
Class<Integer> integerClass = int.class;
// integerClass = double.class;非法转换
Class<?> intClass = int.class;
intClass = double.class;
Class<I1> i=I1.class;
Class<? extends I1> i1 = I1.class;
Class<? super I1> i2 = i.getSuperclass();

其中Class<?>的?表示通配符,意味着一个非具体的类引用。<? extends A>限定是A的子类,<? super A>限定是A的父类。

类型转换前先检查

(1)强转,通过RTTI保证正确性,如果转换出错抛出ClassCastException

(2)转换前先通过A instanceof B/B.isInstance(A)询问 A对象是否从属于B的实例,如果是,再进行转换。

1
2
3
4
5
6
7
8
9
Class<? extends I1> i1 = I1.class;
I1 f = new I1();
if (f instanceof List) {
System.out.println("true");
}

if (i1.isInstance(f)) {
System.out.println("true");
}

反射

反射:用来在运行时分析类。

反射可用来:

  1. 在运行时分析类
  2. 在运行时查看对象
  3. 实现通用的数组操作代码
  4. 使用Method对象(类比于C++中的函数指针)

利用反射分析类

在reflect包中有三个类Field、Method、Constructor可用来获取类的对应成员变量、方法、构造方法。具体操作如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package reflection;

import java.util.*;
import java.lang.reflect.*;


public class ReflectionTest
{
public static void main(String[] args)
{
// read class name from command line args or user input
String name;
if (args.length > 0) name = args[0];
else
{
Scanner in = new Scanner(System.in);
System.out.println("Enter class name (e.g. java.util.Date): ");
name = in.next();
}

try
{
// print class name and superclass name (if != Object)
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print("class " + name);
if (supercl != null && supercl != Object.class) System.out.print(" extends "
+ supercl.getName());

System.out.print("\n{\n");
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.println();
printFields(cl);
System.out.println("}");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
System.exit(0);
}

/**
* Prints all constructors of a class
* @param cl a class
*/
public static void printConstructors(Class cl)
{
Constructor[] constructors = cl.getDeclaredConstructors();//获取所有构造方法,如果方法中没有Declared,则不能获取私有构造方法

for (Constructor c : constructors)
{
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(name + "(");

// print parameter types
Class[] paramTypes = c.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}

/**
* Prints all methods of a class
* @param cl a class
*/
public static void printMethods(Class cl)
{
Method[] methods = cl.getDeclaredMethods();//获取这个类/接口的所有方法

for (Method m : methods)
{
Class retType = m.getReturnType();//获取返回值
String name = m.getName();//获取方法名

System.out.print(" ");
// print modifiers, return type and method name
String modifiers = Modifier.toString(m.getModifiers());//获取方法修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print(retType.getName() + " " + name + "(");

// print parameter types
Class[] paramTypes = m.getParameterTypes();//获取方法参数类型
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}

/**
* Prints all fields of a class
* @param cl a class
*/
public static void printFields(Class cl)
{
Field[] fields = cl.getDeclaredFields();//获取该类所有成员变量

for (Field f : fields)
{
Class type = f.getType();
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.println(type.getName() + " " + name + ";");
}
}
}

```

值得注意的是Field、Method、Constructor在使用.get/.set访问时只能获取设置可访问的成员变量,如public等,如果想要覆盖访问控制可以调用AccessibleObject.setAccessible(AccessibleObject[],boolean);

#### 反射编写泛型数组代码

```java
package arrays;

import java.lang.reflect.*;
import java.util.*;


public class CopyOfTest
{
public static void main(String[] args)
{
int[] a = { 1, 2, 3 };
a = (int[]) goodCopyOf(a, 10);
System.out.println(Arrays.toString(a));

String[] b = { "Tom", "Dick", "Harry" };
b = (String[]) goodCopyOf(b, 10);
System.out.println(Arrays.toString(b));

System.out.println("The following call will generate an exception.");
b = (String[]) badCopyOf(b, 10);
}

/**
* This method attempts to grow an array by allocating a new array and copying all elements.
* @param a the array to grow
* @param newLength the new length
* @return a larger array that contains all elements of a. However, the returned array has
* type Object[], not the same type as a
*/
public static Object[] badCopyOf(Object[] a, int newLength) // not useful
//一个从一开始就是Object[]的数组是不能够向下转型为其它类型数组的,故需要在一开始创建与原数组类型相同的数组
{
Object[] newArray = new Object[newLength];
System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
return newArray;
}

/**
* This method grows an array by allocating a new array of the same type and
* copying all elements.
* @param a the array to grow. This can be an object array or a primitive
* type array
* @return a larger array that contains all elements of a.
*/
public static Object goodCopyOf(Object a, int newLength)
{
Class cl = a.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();//获取数据类型
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
}

调用任意方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package methods;

import java.lang.reflect.*;


public class MethodTableTest
{
public static void main(String[] args) throws Exception
{
// get method pointers to the square and sqrt methods
Method square = MethodTableTest.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);//第一个参数为方法名,后面若干变量为方法参数类型

// print tables of x- and y-values

printTable(1, 10, 10, square);
printTable(1, 10, 10, sqrt);
}

/**
* Returns the square of a number
* @param x a number
* @return x squared
*/
public static double square(double x)
{
return x * x;
}

/**
* Prints a table with x- and y-values for a method
* @param from the lower bound for the x-values
* @param to the upper bound for the x-values
* @param n the number of rows in the table
* @param f a method with a double parameter and double return value
*/
public static void printTable(double from, double to, int n, Method f)
{
// print out the method as table header
System.out.println(f);

double dx = (to - from) / (n - 1);

for (double x = from; x <= to; x += dx)
{
try
{
double y = (Double) f.invoke(null, x);//调用当前封装在Method中的方法,如果是静态方法,第一个参数隐式参数传null,后面若干变量为方法参数。
System.out.printf("%10.4f | %10.4f%n", x, y);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
文章目录
  1. 1. Java中的RTTI
  2. 2. Class对象
    1. 2.1. 类字面常量
    2. 2.2. 泛化的Class引用
    3. 2.3. 类型转换前先检查
    4. 2.4. 反射
      1. 2.4.1. 利用反射分析类
      2. 2.4.2. 调用任意方法
|