0%

Java——类型信息

运行时类型信息可以让我们在程序运行时发现和使用类型的信息

  • Java中在运行时识别对象和类的信息主要有两种方式:
    1. 传统的RTTI,它假定我们在编译时已经知道了所有的类型
    2. 反射机制,它允许我们在运行时发现和使用类型信息

RTTI

在使用基本的向上转型时,就会使用到RTTI提供的类型转换,例如:

1
2
3
4
5
6
7
8
class Shape{}
class Circle extends Shape{}
class Square extends Shape{}
public class RTTI{
public static void main(String[] args){
List<Shape> list = Arrays.asList(new Circle(), new Square());
}
}

在这里,Circle对象和Square对象会向上转型为Shape对象,而这同时也会丢失Shape对象的具体类型。在Java中,所有的类型转换都是在运行时进行正确性检查的,而RTTI的含义就是:在运行时,识别一个对象的类型。

在编码时,就是使用容器和泛型来强制确保这个转换;在运行时,使用RTTI来确保这个转换

但是,RTTI的类型转换并不彻底:它只是将Object转型为Shape,而不是转型为Circle、Square。而后面Shape会执行什么代码就是多态机制的事情了。

Class对象

  • 要理解RTTI在Java中的工作原理,就必须知道类型信息在运行时是如何表示的,而Class对象就是用来在运行时表示类型信息的,即是用来储存类型信息的。

  • 在Java中,每编写并编译了一个新类,都会产生一个Class对象,而这些Class对象就是通过编译完后所生成的对应的.class文件来创建的,它们是一一对应的,让后JVM就可以使用Class对象来创建我们编写类的对象,在这个过程中将使用被称为”类加载器“的子系统,而将class文件加载到虚拟机的内存,这个过程称为类加载。

  • 创建一个类对象的过程:

    1. 编写一个新类;
    2. 编译生成.class文件;
    3. 加载:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象;
    4. 链接:验证类中的字节码,为静态域分配空间,解析这个类创建的对其他类的所有引用;
    5. 初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态代码块。

forName方法

  • Class.forName(“类的包路径”)可以获取指定类的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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.muchlab.classinfo;

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 creating Candy");
// new Gum();
// System.out.println("After creating Gum");
try {
//使用Class.forName("类的包路径")可以获取指定类的Class对象,使用该对象就可以获取类中包含信息
//在执行forName之前,该类还没被加载,所以,在执行forName时该类会被加载并执行static子句
final Class gum = Class.forName("com.muchlab.classinfo.Gum");

} catch (ClassNotFoundException e) {
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName('Gum')");
final Cookie cookie = new Cookie();
//当你有了一个具体类的对象之后,你可以使用getClass方法来获取该对象对应的类对象引用
final Class<? extends Cookie> aClass = cookie.getClass();
System.out.println(aClass.getSimpleName());

System.out.println("After creating Cookie");
}
}

getxxx

  • xx.getClass:通过对象来获取该对象类型的Class对象
  • getName:通过Class对象来获取全限定类名
  • getSimpleName:通过Class对象来获取不含包名类名
  • getCanonicalName:通过Class对象来获取全限定类名
  • getInterfaces:通过Class对象来获取类实现的接口列表
  • getSuperclass:通过Class对象来获取父类
  • newInstance:通过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
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
package com.muchlab.classinfo;

import java.lang.reflect.Field;

interface HasBatteries{}
interface Waterproof{}
interface Shoots{}

class Toy{
public String name;
private int number;
Toy(){}
Toy(int i){}
}
class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots{
FancyToy(){ super(1); }
}

public class ToyTest {
static void printInfo(Class cc){
System.out.println("Class name:" + cc.getName() +
", is interface? ["+cc.isInterface()+"]");
System.out.println("Simple name:" + cc.getSimpleName());
System.out.println("Canonical name:" + cc.getCanonicalName());
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("com.muchlab.classinfo.FancyToy");
} catch (ClassNotFoundException e) {
System.out.println("Can't find FancyToy");
System.exit(1);
}
printInfo(c);
for (Class anInterface : c.getInterfaces()) {
printInfo(anInterface);
}
try {
final Field field = c.getField("name");
System.out.println(field.getName());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
final Class superclass = c.getSuperclass();
Object o = null;
try {
o = superclass.newInstance();
} catch (InstantiationException e) {
System.out.println("Can't instantiate");
System.exit(1);
} catch (IllegalAccessException e) {
System.out.println("Can't access");
System.exit(1);
}
printInfo(o.getClass());
final Class<FancyToy> fancyToyClass = FancyToy.class;
//类字面常量:基本类型.class等价于其包装类.TYPE
System.out.println(int.class==Integer.TYPE);
}
}

类字面常量

  • 如果使用forName方法来获取相关类的Class对象,不仅要指定类的全限定类名,而且在编译时不会受到检查,所以可以使用类名.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.muchlab.classinfo;

import java.util.Random;

class Initable{
static final int staticFinal = 47;
static final int staticFinal2 =
ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}

class Initable2{
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3{
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}

public class ClassInitialization {
public static Random rand = new Random(47);

public static void main(String[] args) throws ClassNotFoundException {
//使用.class不会触发Initial的初始化
Class initable = Initable.class;
System.out.println("===========================");
//Initable.staticFinal为编译期常量,所以这个值不需要对类进行初始化就可以读取
System.out.println(Initable.staticFinal);
System.out.println("===========================");
//Initable.staticFinal2不是一个编译期常量,所以这个值需要对类进行初始化才可以读取
//Random随机数需要在运行后才能确定其值。
System.out.println(Initable.staticFinal2);
System.out.println("===========================");
//Initable2.staticNonFinal由于不是一个final常量,所以需要进行链接(分配空间)和初始化
System.out.println(Initable2.staticNonFinal);
//如果使用forName则会触发Initial3的初始化
final Class<?> initable3 = Class.forName("com.muchlab.classinfo.Initable3");
System.out.println(Initable3.staticNonFinal);
}
}

.class和.TYPE

对于基本数据类型的包装类,有一个标准字段TYPE,TYPE字段是一个引用,指向对应的基本数据类型的Class对象,即基本类型.class就等价于包装类.TYPE

基本类型.class 包装类.TYPE
boolean.class Boolean.TYPE
char.class Character.TYPE
int.class Integer.TYPE
…….. ……..

泛化的Class引用

Class<? extends xx>

  • 使用泛化的Class引用主要是对Class引用所指向的对象的确切类型进行限制,如下面代码所示:对Class引用所指向的对象限制为Integer
1
2
3
4
5
6
7
8
package com.muchlab.classinfo;

public class GenericClassReferences {
public static void main(String[] args) {
Class<Integer> cc = int.class;
System.out.println(cc.getName());
}
}
  • 但有时我们会觉得这种限制太过严格了。为解决这个问题,我们可以放松一些限制,这时我们就会想使用Integer的父类Number来进行泛化。
1
Class<Number> cc = int.class;
  • 但这行代码其实会通不过编译,原因是Integer的Class对象并不是Number的Class对象的子类,这时就可以使用通配符来解决这个继承的问题。在Class泛型中的指向类的继承关系是这样表示的:Class<? extends Number> cc = int.class;它代表了继承于Number的某个类的Class对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.muchlab.classinfo;

public class WildcardClassReferences {
public static void main(String[] args) {
//虽然Integer继承了Number类型,但是Integer的Class对象并不是Number Class对象的子类
// Class<Number> numberClass = int.class;

//当你需要指定一个类的声明类型时,又想放松一些限制,使用通配符能满足要求
//这里的?表示一个非具体的类,这个非具体类继承了Number,所以intclass才能被赋值为
//int.class和double.class
//指定一个类的声明类型的好处是:为了提供编译期的类型检查
Class<? extends Number> intclass = int.class;
//由于Double也是继承于Number类的,所以可以使用Double的Class对象为intClass赋值
intclass = double.class;
}
}

Class<?>

  • 在编写Class对象时,Class<?>是要优于Class的,即便它们是等价的,Class<?>的好处就是它表示你并非是由于疏忽而忘记写泛型的类型,而是使用了一个非具体的类引用。

Class<? super xxx>

  • Class<? super xxx>代表的是Class中的泛型为xxx的超类,这里写成Class<超类>并不能起到作用。
1
2
3
4
5
6
7
8
9
10
package com.muchlab.classinfo;

public class GenericToyTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<FancyToy> cc = FancyToy.class;
//? super FancyToy表示Fancy的超类,在这里不能写成Class<Toy>
Class<? super FancyToy> superClass = cc.getSuperclass();
System.out.println(superClass.getName());
}
}

反射,运行时的类型信息

  • 如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样RTTI才能识别它,而反射则没有这个限制。
  • Class类于java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用于表示未知类对应的成员。这样就可以使用Constructor来创建对象,用get()和set()来读取和修改Field对象相关联的字段,用invoke()来调用与Method相关联的方法。
  • getxxx:
    1. getConstructors():获取构造器
    2. getFields():获取成员变量
    3. getMethods():获取成员方法

JDK的动态代理

在这里插入图片描述

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
package com.muchlab.reflect;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class DynamicProxyHandler implements InvocationHandler{
private Object proxied;

public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}

/**
* 当调用被代理对象的方法时,调用将会被重定向到该方法中,可以在该方法中添加其他逻辑
* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 方法的参数
* @return 原始方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("=============================Before Invoke==================================");
System.out.println("**** proxy: "+proxy.getClass()+
", method: "+method+", arg: " + args);
if (args!=null)
for (Object arg : args) {
System.out.println(" " + arg);
}

//invoke表示对用法的调用
Object ret = method.invoke(proxied, args);
System.out.println("=============================After Invoke==================================");
return ret;
}
}

public class SimpleDynamicProxy {
public static void consumer(Interface iface){
iface.doSomething();
iface.somethingElse("bonobo");
}

public static void main(String[] args) {
RealObj realObj = new RealObj();
consumer(realObj);
//调用静态方法Proxy.newProxyInstance可以创建动态代理,这个方法需要一个类加载器
//动态代理可以将所有的调用重定向到调用处理器的invoke方法,而这需要向调用处理器的构造器传递实际的对象引用(即被代理的对象)
/**
* 参数1:类加载器
* 参数2:希望代理实现的接口列表,这希望代理Interface接口
* 参数3:调用处理器的实现类的实例
*/
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{Interface.class},
new DynamicProxyHandler(realObj));
consumer(proxy);
}
}

参考资料:Java编程思想

-------------本文结束感谢您的阅读-------------