1. Java类型系统
获取Java类型系统,主要有两个方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;另一种是反射(Reflect),它允许我们在程序运行时获取并使用类型信息。
假如有一个简单的继承体系,让我们看下在RTTI和Reflect不同情况下如何获取类型信息。
Animal为接口,定义getType以返回不同动物的类型,Cat、Dog、Elephant等为具体实现类,均实现getType接口。一般情况下,我们会创建一个具体的对象(Cat,Dog,Elephant等),把它向上转型为Animal,并在程序后面直接使用Animal引用。
具体样例代码如下:
/**
* 动物
*/
public interface Animal {
/**
* 获取动物类型
* @return
*/
String getType();
}
/**
* 动物具体子类 猫
*/
public class Cat implements Animal{
@Override
public String getType() {
return "猫";
}
}
/**
* 动物具体子类 狗
*/
public class Dog implements Animal{
@Override
public String getType() {
return "狗";
}
}
/**
* 动物具体实现 大象
*/
public class Elephant implements Animal{
@Override
public String getType() {
return "大象";
}
}
让我们看下相同的功能通过硬编码与反射两个机制如何实现。
1.1. 硬编码
RTTI假定在编译期,已经知道了所有的类型信息。在编码时,可以直接使用具体的类型信息,这是我们最常见的类型用法。
在编译期,编译器通过容器、泛型保障类型系统的完整性;在运行时,由类型转换操作来确保这一点。
硬编码样例如下:
public static void main(String... args){
List<Animal> animals = createAnimals();
for (Animal animal : animals){
System.out.println(animal.getType());
}
}
/**
* RTTI假定我们在编译时已经知道了所有的类型
* @return
*/
private static List<Animal> createAnimals() {
List<Animal> animals = new ArrayList<>();
animals.add(new Cat()); // 已知类型Cat
animals.add(new Elephant()); // 已知类型Elephant
animals.add(new Dog()); // 已知类型 Dog
return animals;
}
在这个例子中,我们把Cat、Elephant、Dog等向上转型为Animal并存放于List<Animal>中,在转型过程中丢失了具体的类型信息(只保留了接口信息Animal);当我们从List<Animal>中取出元素时,这时容器(容器内部所有的元素都被当做Object)会自动将结果转型成Animal。这是RTTI最基本的用法,在Java中所有的类型转换都是在运行时进行有效性检查。这也是RTTI的含义,在运行时,识别一个对象的类型。
1.2. Reflect
Reflect允许我们在运行时获取并使用类型信息,它主要用于在编译阶段无法获得所有的类型信息的场景,如各类框架。
反射样例如下:
private static final String[] ANIMAL_TYPES = new String[]{
"com.example.reflectdemo.base.Cat",
"com.example.reflectdemo.base.Elephant",
"com.example.reflectdemo.base.Dog"
};
public static void main(String... args){
List<Object> animals = createAnimals();
for (Object animal : animals){
System.out.println(invokeGetType(animal));
}
}
/**
* 利用反射API执行getType方法(等同于animal.getType)
* @param animal
* @return
*/
private static String invokeGetType(Object animal){
try {
Method getTypeMethod = Animal.class.getMethod("getType");
return (String) getTypeMethod.invoke(animal);
}catch (Exception e){
return null;
}
}
/**
* 反射允许我们在运行时获取类型信息
* @return
*/
private static List<Object> createAnimals() {
List<Object> animals = new ArrayList<>();
for (String cls : ANIMAL_TYPES){
animals.add(instanceByReflect(cls));
}
return animals;
}
/**
* 使用反射机制,在运行时动态的实例化对象(等同于new关键字)
* @param clsStr
* @return
*/
private static Object instanceByReflect(String clsStr) {
try {
// 通过反射获取类型信息
Class cls = Class.forName(clsStr);
// 通过Class实例化对象
Object object = cls.newInstance();
return object;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
反射,可以通过一组特殊的API,在运行时,动态执行所有Java硬编码完成的功能(如对象创建、方法调用等)。
相比硬编码,Java反射API要复杂的多,但其给我们带来了更大的灵活性。
2. Class对象
要理解RTTI在Java中的工作原理,首先需要知道类型信息在Java中是如何表示的。这个工作是由称为Class对象的特殊对象完成的,它包含了与类相关的所有信息。Java使用Class对象来执行RTTI。
类是程序的一部分,每个类都会有一个Class对象。每当编写并编译一个新类(动态代理、CGLIB、运行时编译都能创建新类),就会产生一个Class对象,为了生成这个类的对象,运行这个程序的JVM将使用称为“类加载器”的子系统。
2.1. Class Loader
类加载器子系统,是JVM体系重要的一环,主要完成将class二进制文件加载到JVM中,并将其转换为Class对象的过程。
类加载器子系统实际上是一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是可信类,包括Java API类,他们通常是从本地加载。在这条链中,通常不需要添加额外的类加载器,但是如果有特殊需求,可以挂载新的类加载器(比如Web容器)。
所有的类都是在第一次使用时,动态加载到JVM中的,当程序创建第一次对类的静态成员引用时,就会加载这个类。实际上构造函数也是类的静态方法,因此使用new关键字创建类的新对象也会被当做对类的静态引用,从而触发类加载器对类的加载。
Java程序在它开始运行之前并非被全部加载,各个部分是在需要时按需加载的。类加载器在加载类之前,首先检查这个类的Class是否已经加载,如果尚未加载,加载器会按照类名查找class文件,并对字节码进行有效性校验,一旦Class对象被载入内存,它就用来创建这个类的所有对象。
static初始化块在类加载时调用,因此可以用于观察类在什么时候进行加载,样例如下:
static class C1{
static {
System.out.println("C1");
}
}
static class C2{
static {
System.out.println("C2");
}
}
static class C3{
static {
System.out.println("C3");
}
}
public static void main(String... args) throws Exception{
System.out.println("new start");
// 构造函数为类的静态引用,触发类型加载
new C1();
new C1();
System.out.println("new end");
System.out.println();
System.out.println("Class.forName start");
// Class.forName为Class上的静态函数,用于强制加载Class
Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2");
Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2");
System.out.println("Class.forName end");
System.out.println();
System.out.println("C3.class start");
// Class引用,会触发Class加载,但是不会触发初始化
Class c1 = C3.class;
Class c2 = C3.class;
System.out.println("C3.class end");
System.out.println();
System.out.println("c1.newInstance start");
// 调用class上的方法,触发初始化逻辑
c1.newInstance();
System.out.println("c1.newInstance end");
}
输出结果为:
new start
C1
new end
Class.forName start
C2
Class.forName end
C3.class start
C3.class end
c1.newInstance start
C3
c1.newInstance end
看结果,C3.class的调用不会自动的初始化该Class对象(调用static块)。为了使用Class而做的准备工作主要包括三个步骤:
- 加载,这个是由类加载器执行。该步骤将查找字节码文件,并根据字节码创建一个Class对象。
- 链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,如果必要的话,将解析这个类创建的对其他类的引用。
- 初始化,如果该类有超类,则对其进行初始化,执行静态初始化器和静态初始化块。初始化被延时到对静态方法或非常数静态域进行首次访问时才执行。
2.2. Class 实例获取
Class对象作为Java类型体系的入口,如何获取实例成为第一个要解决的问题。
Class对象的获取主要有以下几种途径:
- ClassName.class,获取Class对象最简单最安全的方法,其在编译时会受到编译检测,但上例中已经证实,该方法不会触发初始化逻辑。
- Class.forName,这是反射机制最常用的方法之一,可以在不知具体类型时,通过一个字符串加载所对应的Class对象。
- object.getClass,这也是比较常用的方式之一,通过一个对象获取生成该对象的Class实例。
对于基本数据类型对于的包装器类,还提供了一个TYPE字段,指向对应的基本类型的Class对象。
基本类型 | TYPE类型 |
---|---|
boolean.class | Boolean.TYPE |
char.class | Char.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
2.3. Class 类型信息
Class对象存储了一个class的所有类型信息,当获取到Class对象后,便能通过API获取到所有信息。
在进入Class类型信息之前,需要简单的了解下几个反射的基类,以便更好的理解反射实现体系。
2.3.1 ClassAPI 基础
Class API基础主要是为反射API提供通用特性的接口或基类。由于其通用性,现统一介绍,在具体的API中将对其进行忽略。
2.3.1.1 AnnotatedElement
AnnotatedElement为Java1.5新增接口,该接口代表程序中可以接受注解的程序元素,并提供统一的Annotation访问方式,赋予API通过反射获取Annotation的能力,当一个Annotation类型被定义为运行时后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的下列方法来访问Annotation信息:
方法 | 含义 |
---|---|
<T extends Annotation> T getAnnotation(Class<T> annotationClass) | 返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null |
Annotation[] getAnnotations() | 返回该程序元素上存在的所有注解 |
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) | 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。 |
AnnotatedElement子类涵盖所有可以出现Annotation的地方,其中包括:
- Constructor 构造函数
- Method 方法
- Class 类型
- Field 字段
- Package 包
- Parameter 参数
- AnnotatedParameterizedType 泛型
- AnnotatedTypeVariable 变量
- AnnotatedArrayType 数组类型
- AnnotatedWildcardType
样例如下:
public class AnnotatedElementTest {
public static void main(String... args){
System.out.println("getAnnotations:");
for (Annotation annotation : A.class.getAnnotations()){
System.out.println(annotation);
}
System.out.println();
System.out.println("getAnnotation:" + A.class.getAnnotation(TestAnn1.class));
System.out.println();
System.out.println("isAnnotationPresent:" + A.class.isAnnotationPresent(TestAnn1.class));
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnn1{
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnn2{
}
@TestAnn1
@TestAnn2
public class A{
}
}
输出结果如下:
getAnnotations:
@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1()
@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn2()
getAnnotation:@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1()
isAnnotationPresent:true
2.3.1.2. Member
Member用于标记反射中简单元素。
所涉及方法如下:
方法 | 含义 |
---|---|
getDeclaringClass | 元素所在类 |
getName | 元素名称 |
getModifiers | 元素修饰 |
isSynthetic | 是否为Synthetic,synthetic是由编译器引入的字段、方法、类或其他结构,主要用于JVM内部使用。 |
其子类主要包括:
- Class 类型
- Field 字段
- Method 方法
- Constructor 构造函数
2.3.1.3. AccessibleObject
AccessibleObject可访问对象,其对元素的可见性进行统一封装。同时实现AnnotatedElement接口,提供对Annotation元素的访问。
所涉及方法如下:
方法 | 含义 |
---|---|
isAccessible | 是否可访问 |
setAccessible | 重新访问性 |
其中AccessibleObject所涉及的子类主要包括:
- Field 字段
- Constructor 构造函数
- Method 方法
AccessibleObject 对可见性提供了强大的支持,使我们能够通过反射扩展访问限制,甚至可以对private成员进行访问。
样例代码如下:
public class TestBean {
private String id;
public String getId() {
return id;
}
private void setId(String id) {
this.id = id;
}
}
public class AccessibleObjectBase {
public static void main(String... args) throws Exception{
TestBean testBean = new TestBean();
// private方法, 不能直接调用
Method setId = TestBean.class.getDeclaredMethod("setId", String.class);
System.out.println("setId:" + setId.isAccessible());
try {
setId.invoke(testBean, "111");
}catch (Exception e){
System.out.println("private不能直接调用");
}
setId.setAccessible(true);
System.out.println("设置可访问:" + setId.isAccessible());
setId.invoke(testBean, "111");
System.out.println("设置可访问后,可以绕过private限制,进行调用,结果为:" + testBean.getId());
}
}
输出结果如下:
setId:false
private不能直接调用
设置可访问:true
设置可访问后,可以绕过private限制,进行调用,结果为:111
2.3.1.4. Executable
Executable表示可执行元素的一种封装,可以获取方法签名相关信息。
所涉及方法如下:
方法 | 含义 |
---|---|
getName | 获取名称 |
getModifiers | 获取修饰符 |
getTypeParameters | 获取类型参数(泛型) |
getParameterTypes | 获取参数列表 |
getParameterCount | 获取参数数量 |
getGenericParameterTypes | 获取参数类型 |
getExceptionTypes | 获取异常列表 |
getGenericExceptionTypes | 获取异常列表 |
锁涉及的子类主要有:
- Constructor 构造函数
- Method 方法
样例代码如下:
public class TestBean {
private String id;
public <T, R>TestBean(String id) throws IllegalArgumentException, NotImplementedException {
this.id = id;
}
public String getId() {
return id;
}
private void setId(String id) {
this.id = id;
}
}
public class ExecutableTest {
public static void main(String... args) throws Exception{
for (Constructor constructor : TestBean.class.getConstructors()){
System.out.println("getName: " + constructor.getName());
System.out.println();
System.out.println("getModifiers: " + Modifier.toString(constructor.getModifiers()));
System.out.println();
System.out.println("getTypeParameters:");
for (TypeVariable<Constructor> t : constructor.getTypeParameters()){
System.out.println("type var:" + t.getName());
}
System.out.println();
System.out.println("getParameterCount:" + constructor.getParameterCount());
System.out.println();
System.out.println("getParameterTypes:");
for (Class cls : constructor.getParameterTypes()){
System.out.println(cls.getName());
}
System.out.println();
System.out.println("getExceptionTypes:");
for (Class cls : constructor.getExceptionTypes()){
System.out.println(cls.getName());
}
}
}
}
输出结果为:
getName: com.example.reflectdemo.reflectbase.TestBean
getModifiers: public
getTypeParameters:
type var:T
type var:R
getParameterCount:1
getParameterTypes:
java.lang.String
getExceptionTypes:
java.lang.IllegalArgumentException
sun.reflect.generics.reflectiveObjects.NotImplementedException
2.3.1.5. 方法命名规则
整个反射机制存在着通用的命名规则,了解这些规则,可以大大减少理解方法的阻力。
getXXX和getDeclaredXXX, 两者主要区别在于获取元素的可见性不同,一般情况下getXXX返回public类型的元素,而getDeclaredXXX获取所有的元素,其中包括private、protected、public和package。
2.3.2. 类型信息
Class自身信息包括类名、包名、父类以及实现的接口等。
Class类实现AnnotatedElement接口,以提供对注解的支持。除此以外,涉及方法如下:
方法 | 含义 |
---|---|
getName | 获取类名 |
getCanonicalName | 得到目标类的全名(包名+类名) |
getSimpleName | 等同于getCanonicalName |
getTypeParameters | 获取类型参数(泛型) |
getSuperclass | 获取父类 |
getPackage | 获取包信息 |
getInterfaces | 获取实现接口 |
getModifiers | 获取修饰符 |
isAnonymousClass | 是否匿名类 |
isLocalClass | 是否局部类 |
isMemberClass | 是否成员类 |
isEnum | 是否枚举 |
isInterface | 是否是接口 |
isArray | 是否是数组 |
getComponentType | 获取数组元素类型 |
isPrimitive | 是否是基本类型 |
isAnnotation | 是否是注解 |
getEnumConstants | 获取枚举所有类型 |
getClasses | 获取定义在该类中的public类型 |
getDeclaredClasses | 获取定义在该类中的类型 |
实例如下:
class Base<T> implements Callable<T> {
@Override
public T call() throws Exception {
return null;
}
}
public final class BaseClassInfo<T, R extends Runnable> extends Base<T> implements Runnable, Serializable {
@Override
public void run() {
}
public static void main(String... args){
Class<BaseClassInfo> cls = BaseClassInfo.class;
System.out.println("getName:" + cls.getName());
System.out.println();
System.out.println("getCanonicalName:" + cls.getCanonicalName());
System.out.println();
System.out.println("getSimpleName:" + cls.getSimpleName());
System.out.println();
System.out.println("getSuperclass:" + cls.getSuperclass());
System.out.println();
System.out.println("getPackage:" + cls.getPackage());
System.out.println();
for (Class c : cls.getInterfaces()){
System.out.println("interface : " + c.getSimpleName());
}
System.out.println();
for (TypeVariable<Class<BaseClassInfo>> typeVariable : cls.getTypeParameters()){
System.out.println("type var : " + typeVariable.getTypeName());
}
System.out.println();
System.out.println("getModifiers:" + Modifier.toString(cls.getModifiers()));
}
}
输出结果为:
getName:com.example.reflectdemo.classdetail.BaseClassInfo
getCanonicalName:com.example.reflectdemo.classdetail.BaseClassInfo
getSimpleName:BaseClassInfo
getSuperclass:class com.example.reflectdemo.classdetail.Base
getPackage:package com.example.reflectdemo.classdetail
interface : Runnable
interface : Serializable
type var : T
type var : R
getModifiers:public final
Class类型判断,实例如下:
public class ClassTypeTest {
public static void main(String... args){
Runnable runnable = new Runnable() {
@Override
public void run() {
printClassType(getClass());
}
};
System.out.println("匿名内部类");
runnable.run();
class M implements Runnable{
@Override
public void run() {
printClassType(getClass());
}
}
System.out.println("方法内部类");
new M().run();
System.out.println("内部类");
new ClassTypeTest().new T().run();
System.out.println("静态内部类");
new S().run();
System.out.println("枚举");
printClassType(EnumTest.class);
System.out.println("接口");
printClassType(Runnable.class);
System.out.println("数组");
printClassType(int[].class);
System.out.println("int");
printClassType(int.class);
System.out.println("注解");
printClassType(AnnTest.class);
}
class T implements Runnable{
@Override
public void run() {
printClassType(getClass());
}
}
static class S implements Runnable{
@Override
public void run() {
printClassType(getClass());
}
}
enum EnumTest{
A, B, C
}
@interface AnnTest{
}
private static void printClassType(Class cls){
System.out.println("Class:" + cls.getName());
System.out.println("isAnonymousClass:" + cls.isAnonymousClass());
System.out.println("isLocalClass:" + cls.isLocalClass());
System.out.println("isMemberClass:" + cls.isMemberClass());
System.out.println("isEnum:" + cls.isEnum());
System.out.println("isInterface:" + cls.isInterface());
System.out.println("isArray:" + cls.isArray());
System.out.println("isPrimitive:" + cls.isPrimitive());
System.out.println("isAnnotation:" + cls.isAnnotation());
if (cls.isEnum()){
System.out.println("getEnumConstants:");
for (Object o : cls.getEnumConstants()){
System.out.println(o);
}
}
if (cls.isArray()){
System.out.println("getComponentType:" + cls.getComponentType());
}
System.out.println();
}
}
输出结果如下:
匿名内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$1
isAnonymousClass:true
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
方法内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$1M
isAnonymousClass:false
isLocalClass:true
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$T
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
静态内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$S
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
枚举
Class:com.example.reflectdemo.classdetail.ClassTypeTest$EnumTest
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:true
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
getEnumConstants:
A
B
C
接口
Class:java.lang.Runnable
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:true
isArray:false
isPrimitive:false
isAnnotation:false
数组
Class:[I
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:true
isPrimitive:false
isAnnotation:false
getComponentType:int
int
Class:int
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:true
isAnnotation:false
注解
Class:com.example.reflectdemo.classdetail.ClassTypeTest$AnnTest
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:true
isArray:false
isPrimitive:false
isAnnotation:true
内部类型样例如下:
public class InnerClassTest {
public static void main(String... args){
System.out.println("getClasses");
for (Class cls : InnerClassTest.class.getClasses()){
System.out.println(cls.getName());
}
}
public interface I{
}
public class A implements I{
}
public class B implements I{
}
}
输出结果如下:
getClasses
com.example.reflectdemo.classdetail.InnerClassTest$B
com.example.reflectdemo.classdetail.InnerClassTest$A
com.example.reflectdemo.classdetail.InnerClassTest$I
2.3.3. 对象实例化
对象实例化,主要通过Constructor实例完成,首先通过相关方法获取Constructor对象,然后进行实例化操作。
所涉及的方法如下:
方法 | 含义 |
---|---|
newInstance | 使用默认构造函数实例化对象 |
getConstructors | 获取public构造函数 |
getConstructor(Class<?>... parameterTypes) | 获取特定public构造函数 |
getDeclaredConstructors | 获取所有的构造函数 |
getDeclaredConstructor | 获取特定构造函数 |
实例化涉及的核心类为Constructor,Constructor继承自Executable,拥有AnnotatedElement、AccessibleObject、Executable等相关功能,其核心方法如下:
方法 | 含义 |
---|---|
newInstance | 调用构造函数,实例化对象 |
样例如下:
public class TestBean {
private final Integer id;
private final String name;
public <T, R>TestBean(Integer id, String name) throws IllegalArgumentException, NotImplementedException {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "TestBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class ConstructorTest {
public static void main(String... args) throws Exception{
for (Constructor constructor : TestBean.class.getConstructors()){
TestBean bean = (TestBean) constructor.newInstance(1, "Test");
System.out.println("newInstance:" + bean);
}
}
}
输出结果为:
newInstance:TestBean{id=1, name='Test'}
2.3.4. 属性信息
对象属性是类型中最主要的信息之一,主要通过Field表示,首先通过相关方法获取Field实例,然后进行属性值操作。
所涉及的方法如下:
方法 | 含义 |
---|---|
getFields | 获取public字段 |
getField(String name) | 获取特定public字段 |
getDeclaredFields | 获取所有的的属性 |
getDeclaredField | 获取特定字段 |
Field继承自AccessibleObject实现Member接口,拥有AccessibleObject、AnnotatedElement、Member相关功能,其核心方法如下:
方法 | 含义 |
---|---|
isEnumConstant | 是否枚举常量 |
getType | 获取类型 |
get | 获取属性值 |
getBoolean | 获取boolean值 |
getByte | 获取byte值 |
getChar | 获取chat值 |
getShort | 获取short值 |
getInt | 获取int值 |
getLong | 获取long值 |
getFloat | 获取float值 |
getDouble | 获取double值 |
set | 设置属性值 |
setBoolean | 设置boolean值 |
setByte | 设置byte值 |
setChar | 设置char值 |
setShort | 设置short值 |
setInt | 设置int值 |
setLong | 设置long值 |
setFloat | 设置float值 |
setDouble | 设置double值 |
实例如下:
public enum EnumTest {
A
}
public class FieldBean {
private EnumTest aEnum;
private String aString;
private boolean aBoolean;
private byte aByte;
private char aChar;
private short aShort;
private int anInt;
private long aLong;
private float aFloat;
private double aDouble;
}
public class FieldTest {
public static void main(String... args) throws NoSuchFieldException, IllegalAccessException {
FieldBean fieldBean = new FieldBean();
Field aEnum = getByName("aEnum");
Field aString = getByName("aString");
Field aBoolean = getByName("aBoolean");
Field aByte = getByName("aByte");
Field aChar = getByName("aChar");
Field aShort = getByName("aShort");
Field anInt = getByName("anInt");
Field aLong = getByName("aLong");
Field aFloat = getByName("aFloat");
Field aDouble = getByName("aDouble");
aEnum.set(fieldBean, EnumTest.A);
System.out.println("isEnumConstant: " + aEnum.isEnumConstant());
System.out.println("set and get enum : " + aEnum.get(fieldBean));
aString.set(fieldBean, "Test");
System.out.println("set and get String : " + aString.get(fieldBean));
aBoolean.setBoolean(fieldBean, true);
System.out.println("set and get Boolean : " + aBoolean.getBoolean(fieldBean));
aByte.setByte(fieldBean, (byte) 1);
System.out.println("set and get Byte : " + aByte.getByte(fieldBean));
aChar.setChar(fieldBean, 'a');
System.out.println("set and get Char : " + aChar.getChar(fieldBean));
aShort.setShort(fieldBean, (short) 1);
System.out.println("set and get Short : " + aShort.getShort(fieldBean));
anInt.setInt(fieldBean, 1);
System.out.println("set and get Int : " + anInt.getInt(fieldBean));
aLong.setLong(fieldBean, 1L);
System.out.println("set and get Long : " + aLong.getLong(fieldBean));
aFloat.setFloat(fieldBean, 1f);
System.out.println("set and get Float : " + aLong.getFloat(fieldBean));
aDouble.setDouble(fieldBean, 1.1);
System.out.println("set and get Double : " + aLong.getDouble(fieldBean));
}
private static Field getByName(String name) throws NoSuchFieldException {
Field field = FieldBean.class.getDeclaredField(name);
field.setAccessible(true);
return field;
}
}
2.3.5. 方法信息
类型中的方法通过Method表示,首先通过相关方法获取Method实现,然后通过反射执行方法。
所涉及的方法如下:
方法 | 含义 |
---|---|
getMethods | 获取public方法 |
getMethod(String name, Class<?>... parameterTypes) | 获取特定public方法 |
getDeclaredMethods | 获取所有方法 |
getDeclaredMethod | 获取特定方法 |
Method继承自Executable,拥有AnnotatedElement、AccessibleObject、Executable等相关功能,其核心方法如下:
方法 | 含义 |
---|---|
getReturnType | 获取方法返回类型 |
invoke | 调用方法 |
isBridge | 是否为桥接方法。桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法。 |
isDefault | 是否为默认方法 |
实例如下:
public interface SayHi {
String get();
default void hi(){
System.out.println("Hi " + get());
}
}
public class MethodBean implements Function<String, String>, SayHi {
private final String name;
public MethodBean(String name) {
this.name = name;
}
@Override
public String get() {
return "Hi " + name;
}
@Override
public String apply(String s) {
return s + name;
}
}
public class MethodTest {
public static void main(String... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method strMethod = MethodBean.class.getDeclaredMethod("apply", String.class);
Method objMethod = MethodBean.class.getDeclaredMethod("apply", Object.class);
Method hiMethod = SayHi.class.getDeclaredMethod("hi");
MethodBean methodBean = new MethodBean("张三");
System.out.println("Return Type:");
System.out.println("getMethod(String):" + strMethod.getReturnType());
System.out.println("getMethod(Object):" + objMethod.getReturnType());
System.out.println("hi():" + hiMethod.getReturnType());
System.out.println();
System.out.println("isBridge:");
System.out.println("getMethod(String):" + strMethod.isBridge());
System.out.println("getMethod(Object):" + objMethod.isBridge());
System.out.println("hi():" + hiMethod.isBridge());
System.out.println();
System.out.println("isDefault:");
System.out.println("getMethod(String):" + strMethod.isDefault());
System.out.println("getMethod(Object):" + objMethod.isDefault());
System.out.println("hi():" + hiMethod.isDefault());
System.out.println();
System.out.println("invoke:");
System.out.println("invoke(String):" + strMethod.invoke(methodBean, "Test"));
System.out.println("invoke(Object):" + objMethod.invoke(methodBean, "Test"));
System.out.println("hi():" + hiMethod.invoke(methodBean));
}
}
输出结果:
Return Type:
getMethod(String):class java.lang.String
getMethod(Object):class java.lang.Object
hi():void
isBridge:
getMethod(String):false
getMethod(Object):true
hi():false
isDefault:
getMethod(String):false
getMethod(Object):false
hi():true
invoke:
invoke(String):Test张三
invoke(Object):Test张三
Hi Hi 张三
hi():null
2.3.6. 其他
除上述核心方法外,Class对象提供了一些使用方法。
所涉及方法如下:
方法 | 含义 |
---|---|
isInstance | 判断某对象是否是该类的实例 |
isAssignableFrom | 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。 |
getClassLoader | 获取加载当前类的ClassLoader |
getResourceAsStream | 根据该ClassLoader加载资源 |
getResource | 根据该ClassLoader加载资源 |
public class Task implements Runnable{
@Override
public void run() {
}
}
public class OtherTest {
public static void main(String...args){
Task task = new Task();
System.out.println("Runnable isInstance Task:" + Runnable.class.isInstance(task));
System.out.println("Task isInstance Task:" + Task.class.isInstance(task));
System.out.println("Task isAssignableFrom Task:" + Task.class.isAssignableFrom(Task.class));
System.out.println("Runnable isAssignableFrom Task :" + Runnable.class.isAssignableFrom(Task.class));
}
}
输出结果:
Runnable isInstance Task:true
Task isInstance Task:true
Task isAssignableFrom Task:true
Runnable isAssignableFrom Task :true
3. 动态代理
代理是基本的设计模式之一,它是我们为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常与“实际”对象通信,因此代理通常充当中间人的角色。
例如,我们已有一个Handler接口,和一个实现类HandlerImpl,现需要对其进行性能统计,使用代理模式,代码如下:
/**
* handler接口
*/
public interface Handler {
/**
* 数据处理
* @param data
*/
void handle(String data);
}
/**
* Handler 实现
*/
public class HandlerImpl implements Handler{
@Override
public void handle(String data) {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Handler代理<br />
* 实现Handler接口,记录耗时情况,并将请求发送给目标对象
*/
public class HandlerProxy implements Handler{
private final Handler handler;
public HandlerProxy(Handler handler) {
this.handler = handler;
}
@Override
public void handle(String data) {
long start = System.currentTimeMillis();
this.handler.handle(data);
long end = System.currentTimeMillis();
System.out.println("cost " + (end - start) + " ms");
}
}
public static void main(String... args){
Handler handler = new HandlerImpl();
Handler proxy = new HandlerProxy(handler);
proxy.handle("Test");
}
采用代理模式,比较优雅的解决了该问题,但如果Handler接口存在多个方法,并且需要对所有方法进行性能监控,那HandlerProxy的复杂性将会提高。
Java动态代理比代理更进一步,因为它可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上。
3.1. InvocationHandler
InvocationHandler 是由动态代理处理器实现的接口,对代理对象的方法调用,会路由到该处理器上进行统一处理。
其只有一个核心方法:
/**
* proxy : 代理对象
* method : 调用方法
* args : 调用方法参数
**/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
3.2. Proxy
Proxy 用于生成代理对象。
其核心方法为:
/**
* 获取代理类<br />
* loader : 类加载器
* interfaces: 类实现的接口
*
*/
Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces);
/*
* 生成代理对象<br />
* loader : 类加载器
* interfaces : 类实现的接口
* h : 动态代理回调
*/
Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
/*
* 判断是否为代理类<br />
*
* cl : 待判断类
*/
public static boolean isProxyClass(Class<?> cl);
/*
* 获取代理对象的InvocationHandler <br />
*
* proxy : 代理对象
*/
InvocationHandler getInvocationHandler(Object proxy);
3.3. demo
对于之前的性能监控,使用Java动态代理怎么实现?
/**
* 定义代理方法回调处理器
*/
public class CostInvocationHandler implements InvocationHandler {
// 目标对象
private final Object target;
public CostInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("call method " + method + " ,args " + args);
long start = System.currentTimeMillis();
try {
// 将请求转发给目标对象
return method.invoke(this.target, args);
}finally {
long end = System.currentTimeMillis();
System.out.println("cost " + (end - start) + "ms");
}
}
}
public static void main(String... args){
Handler handler = new HandlerImpl();
CostInvocationHandler invocationHandler = new CostInvocationHandler(handler);
Class cls = Proxy.getProxyClass(DHandlerMain.class.getClassLoader(), Handler.class);
Handler proxy = (Handler) Proxy.newProxyInstance(DHandlerMain.class.getClassLoader(),
new Class[]{Handler.class},
invocationHandler);
System.out.println("invoke method");
proxy.handle("Test");
System.out.println("isProxyClass: " + Proxy.isProxyClass(cls));
System.out.println("getInvocationHandler: " + (invocationHandler == Proxy.getInvocationHandler(proxy)));
}
4. 基于SPI的Plugin
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,它是一种动态替换发现的机制。
具体用法是在JAR包的"META-INF/services/"目录下建立一个文件,文件名是接口的全限定名,文件的内容可以有多行,每行都是该接口对应的具体实现类的全限定名。然后使用 ServiceLoader.load(Interface.class) 对插件进行加载。
假定,现有个场景,需要对消息进行处理,但消息处理器的实现需要放开,及可以动态的对处理器进行加载,当有新消息到达时,依次调用处理器对消息进行处理,让我们结合SPI和反射构造一个简单的Plugin系统。
首先我们需要一个插件接口和若干个实现类:
/**
* 插件接口
*/
public interface Handler {
void handle(String msg);
}
/**
* 实现1
*/
public class Handler1 implements Handler{
@Override
public void handle(String msg) {
System.out.println("Handler1:" + msg);
}
}
/**
* 实现2
*/
public class Handler2 implements Handler{
@Override
public void handle(String msg) {
System.out.println("Handler2:" + msg);
}
}
然后,我们添加SPI配置,及在META-INF/services/com.example.reflectdemo.plugin.Handler添加配置信息:
com.example.reflectdemo.plugin.Handler1
com.example.reflectdemo.plugin.Handler2
其次,我们实现DispatcherInvocationHandler类继承自InvocationHandler接口,将方法调用分发给目标对象。
/**
* 分发处理器<br />
* 将请求挨个转发给目标对象
*/
public class DispatcherInvocationHandler implements InvocationHandler {
// 目标对象集合
private final List<Object> targets;
public DispatcherInvocationHandler(List<Object> targets) {
this.targets = targets;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (Object target : targets){
// 将请求转发给目标对象
method.invoke(target, args);
}
return null;
}
}
实现主流程,通过SPI加装插件,将插件作为转发对象实例化DispatcherInvocationHandler,在通过Proxy构建动态代理对象,最后调用handle方法进行业务处理。
public static void main(String... args){
// 使用SPI加载插件
ServiceLoader<Handler> serviceLoader = ServiceLoader.load(Handler.class);
List<Object> handlers = new ArrayList<>();
Iterator<Handler> handlerIterator = serviceLoader.iterator();
while (handlerIterator.hasNext()){
Handler handler = handlerIterator.next();
handlers.add(handler);
}
// 将加载的插件组装成InvocationHandler,以进行分发处理
DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers);
// 生成代理对象
Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler);
// 调用handle方法
proxy.handle("Test");
}
运行结果如下:
Handler1:Test
Handler2:Test
5. 总结
Java类型系统、反射、动态代理,作为Java的高级应用,大量用于各大框架中。对其的掌握有助于加深对框架的理解。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。