Java 基础 - 反射

目录

一、Java反射机制是什么?

1.1 基本概念

1.2 使用主要种类

二、常用API

2.1 java.lang.Class 类

2.1.1 获取class对象的三种方式

2.2 java.lang.reflect包

2.3 访问构造方法

Modifier类的常用静态方法

获取Constructor对象

构造方法参数

利用Constructor对象实例化一个类

2.4 访问方法(获取方法)

Method类的常用方法

获取Method对象

方法参数以及返回类型

通过Method对象调用方法

2.5 访问成员变量

Field类的常用方法

获取Field对象

变量名称

变量类型

获取或设置(get/set)变量值

2.6 注解

简介

类注解

方法注解

参数注解

变量注解

2.7 泛型

运用泛型反射的经验法则

泛型方法返回类型

泛型方法参数类型

泛型变量类型

2.8 数组

java.lang.reflect.Array

高级技术

动态类的加载和重载

动态代理简介

反射调用流程小结

 


一、Java反射机制是什么?

1.1 基本概念

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。Java反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。
  • 生成动态代理。

要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是Class类中的方法。所以先要获取到每一个字节码文件(.class)对应的Class类型的对象.

Java反射机制的优缺点

优点:

  • 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
  • Java动态编译相结合,可以实现无比强大的功能。
  • 对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:

  • 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
  • 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。

1.2使用主要种类

与Java-Reflection相关种类主要分为:

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法
Annotation类 代表类的注解

二、常用API

实现java反射机制的类都位于java.lang.reflect包中,java.lang.Class类是Java反射机制API中的核心类。

2.1 java.lang.Class 类

java.lang.Class类是实现反射的关键所在,Class类的一个实例表示Java的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和voidClass没有公有的构造方法,Class实例是由JVM在类加载时自动创建的。

在程序代码中获得Class实例可以通过如下代码实现:

// 1. 通过类型class静态变量
Class clz1 = String.class;
String instance1 = clz1.newInstance();
// 2. 通过对象的getClass()方法
String str2 = "Hello";
Class clz2 = str2.getClass();
String instance2 = clz2.newInstance();
//

每一种类型包括类和接口等,都有一个class静态变量可以获得Class实例。另外,每一个对象都有getClass()方法可以获得Class实例,该方法是由Object类提供的实例方法。

2.1.1 获取class对象的三种方式

  • Object类的getClass()方法
  • 静态属性class
  • Class类中的静态方法forName()

2.2 java.lang.reflect包

java.lang.reflect包提供了反射中用到类,主要的类说明如下:

  • Constructor类:提供类的构造方法信息。
  • Field类:提供类或接口中成员变量信息。
  • Method类:提供类或接口成员方法信息。
  • Array类:提供了动态创建和访问Java数组的方法。
  • Modifier类:提供类和成员访问修饰符信息。

2.3 访问构造方法

为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个Constructor类型的对象或者数组。

  • getConstructors()
  • getConstructor(Class…parameterTypes)
  • getDeclaredConstructors()
  • getDeclaredConstructor(Class...parameterTypes)

创建的每个Constructor对象表示一个构造方法,然后利用Constructor对象的方法操作构造方法。

getConstructors()和getDeclaredConstructors()区别
getConstructors():获得某个类的所有的公共(public)的构造方法,包括父类中的构造方法。
getDeclaredConstructors():获得某个类的所有声明的构造方法,即包括public、private和proteced,但是不包括父类的申明构造方法。
同样类似的还有getMethods()和getDeclaredMethods()。

Constructor类的常用方法

方法名称 说明
isVarArgs() 查看该构造方法是否允许带可变数量的参数,如果允许,返回true,否则返回false
getParameterTypes() 按照声明顺序以Class数组的形式获取该构造方法各个参数的类型

通过java.lang.reflect.Modifier类可以解析出getMocMers()方法的返回值所表示的修饰符信息。在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。

Modifier类的常用静态方法

静态方法名称 说明
isStatic(int mod) 如果使用 static 修饰符修饰则返回 true,否则返回 false
isPublic(int mod) 如果使用 public 修饰符修饰则返回 true,否则返回 false
isProtected(int mod) 如果使用 protected 修饰符修饰则返回 true,否则返回 false
isPrivate(int mod) 如果使用 private 修饰符修饰则返回 true,否则返回 false
isFinal(int mod) 如果使用 final 修饰符修饰则返回 true,否则返回 false
toString(int mod) 以字符串形式返回所有修饰符

获取Constructor对象

我们可以通过Class对象来获取Constructor类的实例:

Class aClass = ...//获取Class对象
Constructor[] constructors = aClass.getConstructors();

返回的Constructor数组包含每一个声明为公有的(Public)构造方法。
如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为String类型:

Class aClass = ...//获取Class对象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});

如果没有指定的构造方法能满足匹配的方法参数则会抛出:NoSuchMethodException

构造方法参数

你可以通过如下方式获取指定构造方法的方法参数信息:

Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();

利用Constructor对象实例化一个类

你可以通过如下方法实例化一个类:

Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");

constructor.newInstance()方法的方法参数是一个可变参数列表,但是当你调用构造方法的时候你必须提供精确的参数,即形参与实参必须一一对应。在这个例子中构造方法需要一个String类型的参数,那我们在调用newInstance方法的时候就必须传入一个String类型的参数。

2.4 访问方法(获取方法)

动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个Method类型的对象或者数组。

  • getMethods()
  • getMethods(String name,Class …parameterTypes)
  • getDeclaredMethods()
  • getDeclaredMethods(String name,Class...parameterTypes)

Method类的常用方法

静态方法名称 说明
getName() 获取该方法的名称
getParameterType() 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型
getReturnType() 以 Class 对象的形式获得该方法的返回值类型
getExceptionTypes() 以 Class 数组的形式获得该方法可能抛出的异常类型
invoke(Object obj,Object...args) 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型
isVarArgs() 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false
getModifiers() 获得可以解析出该方法所采用修饰符的整数

获取Method对象

可以通过Class对象获取Method对象,如下例:

Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();

返回的Method对象数组包含了指定类中声明为公有的(public)的所有变量集合。
如果你知道你要调用方法的具体参数类型,你就可以直接通过参数类型来获取指定的方法,下面这个例子中返回方法对象名称是“doSomething”,他的方法参数是String类型:

Class  aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});

如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出NoSuchMethodException
如果你想要获取的方法没有参数,那么在调用getMethod()方法时第二个参数传入null即可,就像这样:

Class  aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", null);

方法参数以及返回类型

你可以获取指定方法的方法参数是哪些:

Method method = ... //获取Class对象
Class[] parameterTypes = method.getParameterTypes();

你可以获取指定方法的返回类型:

Method method = ... //获取Class对象
Class returnType = method.getReturnType();

通过Method对象调用方法

你可以通过如下方式来调用一个方法:

//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");

传入的null参数是你要调用方法的对象,如果是一个静态方法调用的话则可以用null代替指定对象作为invoke()的参数,在上面这个例子中,如果doSomething不是静态方法的话,你就要传入有效的MyObject实例而不是null
Method.invoke(Object target, Object … parameters)方法的第二个参数是一个可变参数列表,但是你必须要传入与你要调用方法的形参一一对应的实参。就像上个例子那样,方法需要String类型的参数,那我们必须要传入一个字符串。

2.5 访问成员变量

通过下列任意一个方法访问成员变量时将返回Field类型的对象或数组。

  • getFields()
  • getField(String name)
  • getDeclaredFields()
  • getDeclaredField(String name)

Field类的常用方法

方法名称 说明
getName() 获得该成员变量的名称
getType() 获取表示该成员变量的 Class 对象
get(Object obj) 获得指定对象 obj 中成员变量的值,返回值为 Object 类型
set(Object obj, Object value) 将指定对象 obj 中成员变量的值设置为 value
getlnt(0bject obj) 获得指定对象 obj 中成员类型为 int 的成员变量的值
setlnt(0bject obj, int i) 将指定对象 obj 中成员变量的值设置为 i
setFloat(Object obj, float f) 将指定对象 obj 中成员变量的值设置为 f
getBoolean(Object obj) 获得指定对象 obj 中成员类型为 boolean 的成员变量的值
setBoolean(Object obj, boolean b) 将指定对象 obj 中成员变量的值设置为 b
getFloat(Object obj) 获得指定对象 obj 中成员类型为 float 的成员变量的值
setAccessible(boolean flag) 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量
getModifiers() 获得可以解析出该方法所采用修饰符的整数

获取Field对象

可以通过Class对象获取Field对象,如下例:

Class aClass = ...//获取Class对象
Field[] methods = aClass.getFields();

返回的Field对象数组包含了指定类中声明为公有的(public)的所有变量集合。
如果你知道你要访问的变量名称,你可以通过如下的方式获取指定的变量:

Class aClass = MyObject.class
Field field = aClass.getField("someField");

上面的例子返回的Field类的实例对应的就是在MyObject类中声明的名为someField的成员变量,就是这样:

public class MyObject{
  public String someField = null;
}

在调用getField()方法时,如果根据给定的方法参数没有找到对应的变量,那么就会抛出NoSuchFieldException

变量名称

一旦你获取了Field实例,你可以通过调用Field.getName()方法获取他的变量名称,如下例:

Field field = ... //获取Field对象
String fieldName = field.getName();

变量类型

你可以通过调用Field.getType()方法来获取一个变量的类型(如String, int等等)

Field field = aClass.getField("someField");
Object fieldType = field.getType();

获取或设置(get/set)变量值

一旦你获得了一个Field的引用,你就可以通过调用Field.get()Field.set()方法,获取或者设置变量的值,如下例:

Class  aClass = MyObject.class
Field field = aClass.getField("someField");
MyObject objectInstance = new MyObject();
Object value = field.get(objectInstance);
field.set(objetInstance, value);

传入Field.get()/Field.set()方法的参数objetInstance应该是拥有指定变量的类的实例。在上述的例子中传入的参数是MyObject类的实例,是因为someFieldMyObject类的实例。
如果变量是静态变量的话(public static)那么在调用Field.get()/Field.set()方法的时候传入null做为参数而不用传递拥有该变量的类的实例。(译者注:你如果传入拥有该变量的类的实例也可以得到相同的结果)

2.6 注解

通过下列任意一个方法访问注解时将返回Annotation类型的对象或数组。

  • getAnnotations()
  • getAnnotation(Class annotationClass)
  • getDeclaredAnnotations()
  • getDeclaredAnnotation(Class annotationClass)

简介

注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理。下面是一个类注解的例子:

@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {
}

TheClass类定义的上面有一个@MyAnnotation的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
    public String name();
    public String value();
}

在interface前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中,就像之前我们的那个例子那样。
在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE),说明了这个注解该如何使用。
@Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
@Target(ElementType.TYPE)表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。

类注解

你可以在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:

Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你还可以像下面这样指定访问一个类的注解:

Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

方法注解

下面是一个方法注解的例子:

public class TheClass {
    @MyAnnotation(name="someName",  value = "Hello World")
    public void doSomething(){}
}

你可以像这样访问方法注解:

Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你可以像这样访问指定的方法注解:

Method method = ... // 获取方法对象
Annotation annotation = method.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

参数注解

方法参数也可以添加注解,就像下面这样:

public class TheClass {
    public static void doSomethingElse(@MyAnnotation(name="aName", value="aValue") String parameter){
    }
}

你可以通过Method对象来访问方法参数注解:

Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
int i=0;
for(Annotation[] annotations : parameterAnnotations){
    Class parameterType = parameterTypes[i++];
    for(Annotation annotation : annotations){
        if(annotation instanceof MyAnnotation){
            MyAnnotation myAnnotation = (MyAnnotation) annotation;
            System.out.println("param: " + parameterType.getName());
            System.out.println("name : " + myAnnotation.name());
            System.out.println("value: " + myAnnotation.value());
        }
    }
}

需要注意的是Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。

变量注解

下面是一个变量注解的例子:

public class TheClass {
    @MyAnnotation(name="someName",  value = "Hello World")
    public String myField = null;
}

你可以像这样来访问变量的注解:

Field field = ... //获取方法对象
Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你可以像这样访问指定的变量注解:

Field field = ...//获取方法对象
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

2.7 泛型

通过下列任意一个方法访问泛型时将返回Type类型的对象或数组。

  • getGenericType()
  • getGenericReturnType()
  • getGenericParameterTypes()

运用泛型反射的经验法则

下面是两个典型的使用泛型的场景:
1、声明一个需要被参数化(parameterizable)的类/接口。
2、使用一个参数化类。

当你声明一个类或者接口的时候你可以指明这个类或接口可以被参数化,java.util.List接口就是典型的例子。你可以运用泛型机制创建一个标明存储的是String类型list,这样比你创建一个Objectlist要更好。

当你想在运行期参数化类型本身,比如你想检查java.util.List类的参数化类型,你是没有办法能知道他具体的参数化类型是什么。这样一来这个类型就可以是一个应用中所有的类型。但是,当你检查一个使用了被参数化的类型的变量或者方法,你可以获得这个被参数化类型的具体参数。总之:

你不能在运行期获知一个被参数化的类型的具体参数类型是什么,但是你可以在用到这个被参数化类型的方法以及变量中找到他们,换句话说就是获知他们具体的参数化类型。
在下面的段落中会向你演示这类情况。

泛型方法返回类型

如果你获得了java.lang.reflect.Method对象,那么你就可以获取到这个方法的泛型返回类型信息。如果方法是在一个被参数化类型之中(译者注:如T fun())那么你无法获取他的具体类型,但是如果方法返回一个泛型类(译者注:如List fun())那么你就可以获得这个泛型类的具体参数化类型。

下面这个例子定义了一个类这个类中的方法返回类型是一个泛型类型:

public class MyClass {
    protected List stringList = ...;
    public List getStringList(){
        return this.stringList;
    }
}

我们可以获取getStringList()方法的泛型返回类型,换句话说,我们可以检测到getStringList()方法返回的是List而不仅仅只是一个List。如下例:

Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
    ParameterizedType type = (ParameterizedType) returnType;
    Type[] typeArguments = type.getActualTypeArguments();
    for(Type typeArgument : typeArguments){
        Class typeArgClass = (Class) typeArgument;
        System.out.println("typeArgClass = " + typeArgClass);
    }
}

这段代码会打印出“typeArgClass = java.lang.String”,Type[]数组typeArguments只有一个结果 – 一个代表java.lang.StringClass类的实例。Class类实现了Type接口。

泛型方法参数类型

你同样可以通过反射来获取方法参数的泛型类型,下面这个例子定义了一个类,这个类中的方法的参数是一个被参数化的List

public class MyClass {

  protected List stringList = ...;
  public void setStringList(List list){
    this.stringList = list;
  }
}

你可以像这样来获取方法的泛型参数:

method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
    if(genericParameterType instanceof ParameterizedType){
        ParameterizedType aType = (ParameterizedType) genericParameterType;
        Type[] parameterArgTypes = aType.getActualTypeArguments();
        for(Type parameterArgType : parameterArgTypes){
            Class parameterArgClass = (Class) parameterArgType;
            System.out.println("parameterArgClass = " + parameterArgClass);
        }
    }
}

这段代码会打印出”parameterArgType = java.lang.String”。Type[]数组parameterArgTypes只有一个结果 – 一个代表java.lang.StringClass类的实例。Class类实现了Type接口。

泛型变量类型

同样可以通过反射来访问公有(Public)变量的泛型类型,无论这个变量是一个类的静态成员变量或是实例成员变量。你可以在“Java Reflection:Fields”中阅读到有关如何获取Field对象的相关内容。这是之前的一个例子,一个定义了一个名为stringList的成员变量的类。

public class MyClass {
    public List stringList = ...;
}
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
    ParameterizedType aType = (ParameterizedType) genericFieldType;
    Type[] fieldArgTypes = aType.getActualTypeArguments();
    for(Type fieldArgType : fieldArgTypes){
        Class fieldArgClass = (Class) fieldArgType;
        System.out.println("fieldArgClass = " + fieldArgClass);
    }
}

这段代码会打印出”fieldArgClass = java.lang.String”。Type[]数组fieldArgClass只有一个结果 – 一个代表java.lang.StringClass类的实例。Class类实现了Type接口。

2.8 数组

java.lang.reflect.Array

Java反射机制通过java.lang.reflect.Array这个类来处理数组。不要把这个类与Java集合套件(Collections suite)中的java.util.Arrays混淆,java.util.Arrays是一个提供了遍历数组,将数组转化为集合等工具方法的类。

创建一个数组
Java反射机制通过java.lang.reflect.Array类来创建数组。下面是一个如何创建数组的例子:

int[] intArray = (int[]) Array.newInstance(int.class, 3);

这个例子创建一个int类型的数组。Array.newInstance()方法的第一个参数表示了我们要创建一个什么类型的数组。第二个参数表示了这个数组的空间是多大。

访问一个数组
通过Java反射机制同样可以访问数组中的元素。具体可以使用Array.get(…)Array.set(…)方法来访问。下面是一个例子:

int[] intArray = (int[]) Array.newInstance(int.class, 3);
Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);
System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));

这个例子会输出:

intArray[0] = 123
intArray[1] = 456
intArray[2] = 789

获取数组的Class对象
在我编写Butterfly DI Container的脚本语言时,当我想通过反射获取数组的Class对象时遇到了一点麻烦。如果不通过反射的话你可以这样来获取数组的Class对象:

Class stringArrayClass = String[].class;

如果使用Class.forName()方法来获取Class对象则不是那么简单。比如你可以像这样来获得一个原生数据类型(primitiveint数组的Class对象:

Class intArray = Class.forName("[I");

JVM中字母I代表int类型,左边的‘[’代表我想要的是一个int类型的数组,这个规则同样适用于其他的原生数据类型。
对于普通对象类型的数组有一点细微的不同:

Class stringArrayClass = Class.forName("[Ljava.lang.String;");

注意‘[L’的右边是类名,类名的右边是一个‘;’符号。这个的含义是一个指定类型的数组。
需要注意的是,你不能通过Class.forName()方法获取一个原生数据类型的Class对象。下面这两个例子都会报ClassNotFoundException

Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");
我通常会用下面这个方法来获取普通对象以及原生对象的Class对象:

public Class getClass(String className){
  if("int" .equals(className)) return int .class;
  if("long".equals(className)) return long.class;
  ...
  return Class.forName(className);
}

一旦你获取了类型的Class对象,你就有办法轻松的获取到它的数组的Class对象,你可以通过指定的类型创建一个空的数组,然后通过这个空的数组来获取数组的Class对象。这样做有点讨巧,不过很有效。如下例:

Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass, 0).getClass();

这是一个特别的方式来获取指定类型的指定数组的Class对象。无需使用类名或其他方式来获取这个Class对象。
为了确保Class对象是不是代表一个数组,你可以使用Class.isArray()方法来进行校验:

Class stringArrayClass = Array.newInstance(String.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());

获取数组的成员类型
一旦你获取了一个数组的Class对象,你就可以通过Class.getComponentType()方法获取这个数组的成员类型。成员类型就是数组存储的数据类型。例如,数组int[]的成员类型就是一个Class对象int.classString[]的成员类型就是java.lang.String类的Class对象。
下面是一个访问数组成员类型的例子:

String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType();
System.out.println(stringArrayComponentType);

高级技术

Java的反射不仅仅只是简单的进行类的操作和模拟,一些动态技术也是属于Java反射实现的。下面来进行讲解更高级、复杂的技术点。

动态类的加载和重载

Java允许你在JVM(JAVA虚拟机)中运行期动态加载和重载类,但是这个功能并没有像人们希望的那么简单直接。

需要一题的是:JAVA加载类ClassLoader不属于Java反射API,而Java动态类加载特性是Java反射机制的一部分而不是Java核心平台的一部分。

了解动态类的加载和重载,之前我们需要先了解一些相关内容:

前置内容

1.类加载器

所有Java应用中的类都是被java.lang.ClassLoader类的一系列子类加载的。

因此要想动态加载类的话也必须使用java.lang.ClassLoader的子类。

一个类一旦被加载时,这个类引用的所有类也同时会被加载。所以类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。

2.类加载顺序

在Java中类的加载是一个有序的顺序。当你新创建一个标准的Java类加载器时你必须提供它的父加载器。

当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果从父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。所以加载类的时候是优先从父加载器来加载,然后在考虑自己的加载器。

所以类加载器 加载类的顺序如下:
1、检查这个类是否已经被加载。
2、如果没有被加载,则首先调用父加载器加载。
3、如果父加载器不能加载这个类,则尝试加载这个类。

当然当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。

动态类的加载

动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的loadClass()方法。下面是个例子:

public class MainClass {
  public static void main(String[] args){
    ClassLoader classLoader = MainClass.class.getClassLoader();
    try {
        Class aClass = classLoader.loadClass("com.jenkov.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

这里获取了启动类的类加载器,然后调用类加载器的loadClass方法,通过包名来动态加载了一个类。

这里大伙可能会问,“使用类加载器的loadClass方法加载类,和Class类的forName方法获取类有什么不一样呢?不都是返回了一个对于类的Class对象吗?”

这里说明下两者的区别:

  • Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);
    第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。一旦初始化,就会触发目标类对象的 static块代码执行,static参数也也会被再次初始化。
  • ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
    第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行

所以Class.forName(className) 是初始化加载类,而ClassLoader.loadClass(className)方法只是加载类,不会初始化。

动态类的重载

相比于动态类加载,动态类的重载会显得复杂些。

Java内置的类加载器在加载一个类之前会检查它是否已经被加载,如果被加载将会直接去获取那个类,而不是重新加载。因此重载一个类是无法使用Java内置的类加载器的,如果想要实现重载一个类的话,你需要手动继承ClassLoader定义一个自己的加载器子类。

除此之外,所有被加载的类都需要被链接。这个过程是通过ClassLoader.resolve()方法来完成的。由于这是一个final方法,因此这个方法在ClassLoader的子类中是无法被重写的。resolve()方法是不会允许给定的ClassLoader实例链接一个类两次。所以每当你想要重载一个类的时候你都需要New一个新的ClassLoader的子类。

自定义类重载

上面说了,不能使用已经加载过类的类加载器来重载一个类。因此你需要其他的ClassLoader实例来重载这个类。

但是大伙或许知道,JAVA应用中的类都是使用类的全名(包名 + 类名)作为一个唯一标识来让ClassLoader加载的,这意味着,类1被类加载器A加载,如果类加载器B又加载了类1,那么两个加载器加载出来的类1其实是不同的,相当于new出来的。这就与重载(重新加载)的概念不同了...

所以要到达重载的效果,就需要将加载类进行继承了一个超类并且也实现了一个接口。设置一个自定义加载器,对loadClass方法进行重写,如果你想用来重载类的话你可能会设计很多加载器。

动态代理简介

了解了静态代理后,我们不能发现虽然静态代理带来扩展的优点,但是缺点不少:

  • 可维护性低。由于代理类和被代理类都实现了同一个接口,如果接口发生了更改,那么被代理类和所有的代理类都要进行修改,比如接口新增一个方法,那么所有的代理类和被代理类都要重写这个方法,这无疑增加了巨大的工作量。
  • 可重用性低。通过观察可以发现,代理类们的代码大体上其实是差不多的,但是由于个别的差异,导致我们不得不重新写一个新的代理类。

那么我们开始动态代理的讲解。

动态代理 即 利用Java反射机制在运行期动态的创建接口的实现类的行为。

创建个代理类来代替实际需要的类,利用这个代理来实现原类的功能,这一行为就叫做代理。而能够在其Java编译后在JVM(Java虚拟机)中运行时动态实现代理的,被称为动态代理。

其中 动态代理 的用途十分广泛,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等都使用到了动态代理。

在这之前,我们得了解InvocationHandler-调用处理程序 和 Proxy类-代理类

InvocationHandler

InvocationHandler是一个接口类,里面只有一个方法,invoke,专门用来实现代理类的功能。

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

它接受三个参数:代理类,对象真实方法,传递参数。这个方法的效果和Method中的invoke类似。返回的Object即方法返回内容。

其中第一个参数,大部分时间动态代理不会使用。

所以要实现一个动态代理,就需要创建一个动态代理类的调用处理程序。而每一个动态代理类的调用处理程序都必须实现这个InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的 动态代理类调用处理程序 中。

当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,从而实现动态代理类。

反射调用流程小结

最后,用几句话总结反射的实现原理:

  1. 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;

  2. 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;

  3. 反射也是考虑了线程安全的,放心使用;

  4. 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;

  5. 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;

  6. 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;

  7. 调度反射方法,最终是由jvm执行invoke0()执行;


 激动的心,颤抖的手

 借用客官你们发财的小手,给个鼓励吧

你可能感兴趣的:(JAVA,java)