反射

概念

现在有一个对象,要通过对象找到一个类的名称,此时就要用到反射机制。

核心概念一切的操作都将使用Object完成,类,数组的引用都可以使用Object进行接收

通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

功能(重点:在运行时不是编译时)

反射主要提供了以下的功能

  • 1.在运行时判断任意一个对象所属的类;
  • 2.在运行时构造任意一个类的对象;
  • 3.在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 4.在运行时调用任意一个对象的方法

用途

很多框架(比如Spring)都是配置化的(比如通过XML文件配置JavaBean,controller之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射(把类写进配置文件中,然后通过运行时读配置文件来动态的加载类)——运行时动态加载需要加载的对象。(运行时传入类名,然后来创建对象,而不是去new 对象)(和基于接口的方法的区别: 当基于接口时,接口中的定义中方法名是固定好的,它无法直接引用到其他的方法,要加载某个对象必须重新去写new的代码,而使用接口,就可以在运行时动态的从配置文件中读取类名,动态的加载需要的类.)

java的反射机制就是增加程序的灵活性,避免将程序写死到代码里,

例如: 实例化一个 person()对象, 不使用反射, new person(); 如果想变成 实例化 其他类, 那么必须修改源代码,并重新编译。

使用反射: class.forName(“person”).newInstance(); 而且这个类描述可以写到配置文件中,如 .xml, 这样如果想实例化其他类,只要修改配置文件的”类描述”就可以了,不需要重新修改代码并编译

1
反射可以结合Java的字节码,使用ASM和cglib等库,还能动态生成类。

认识Class类

Class是一个java类,跟Java API中定义的诸如Thread、Integer类、我们自己定义的类是一样,也继承了Object(Class是Object的直接子类)。总之,必须明确一点,它其实只是个类,只不过名字比较特殊。更进一步说,Class是一个java中的泛型类型。

Java.lang.Class是一个比较特殊的类,它用于封装被装入到JVM中的类(包括类和接口)的信息当一个类或接口被装入的JVM时便会产生一个与之关联的java.lang.Class对象,可以通过这个Class对象对被装入类的详细信息进行访问。(Java中Class对象和类的实例对象是两个不同的概念,不能混淆!)(每一个类都有一个封装它的java.lang.Class类对象) 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。 每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。

一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象:事实上,Class对象就是用来创建类的所有的“普通”对象的。 类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(恰当地说,是被保存在一个同名的.class文件中)。在运行时,当我们想生成这个类的对象时,运行这个程序的 Java虚拟机(JVM)首先检查这个类的Class对象是否已经加载。如果尚未加载,JVM就会根据类名查找.class文件,并将其载入。 一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有(实例)对象.

在Java中Object是一切类的父类,那么所有类的对象实际上也都是Java.lang.Class类的实例,所以所有的对象都可以转变为Java.lang.Class类型表示。

如何取到Class 类对象 三种方法

1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。例如:
MyObject x;

1
Class c1 = x.getClass();

2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如:

1
Class c2=Class.forName("MyObject"),Employee必须是接口或者类的名字。

3、获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如

1
2
3
Class cl1 = Manager.class;
Class cl2 = int.class;
Class cl3 = Double[].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
//综合实例
public class test {
public static void main(String[] args)
{
//三种反射方式
/************************************/
try {
Class<?> h2 =Class.forName("Hello");
//找到了封装的Class类对象.
h2.getConstructors();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/*************************************/
Class<?> h = Hello.class;
//找到了封装的Class类对象.
h.getConstructors();
/*************************************/
Hello hello =new Hello();
Class<?> c = hello.getClass();
//找到了封装的Class类对象.
c.getConstructors();
}
}

Class类的常用方法

前面三种方法已经找到了Class类对象,下面就是Class类中的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//传入完整包.类名实例化Class对象。!!!!!!!!!!!!!!!!!!!!!! 上面的第一种方式
public static Class<?> forName(String className) //静态方法
// 得到一个类的对象.
public T newInstance()
// 得到一个类的全部构造方法
public Constructors[] getConstructors() throws SecurityException
//得到本类中单独定义的全部属性
public Field[] getDeclareFields() throws SecurityException
//得到本类继承来的全部属性
public Field[] getFields() throws SecurityException
//得到一个类中的全部方法
public Method[] getMethods() throws SecurityException
//返回一个method对象,并设置一个方法中的所有参数类型
public Method getMethod(String methodname, Class<?>... parameterTypes)
//得到一个类中实现的全部接口
public Class[] getInterfaces()
//得到一个类完整的包类名称
public String getName()
//返回表示数组类型的Class
public Class<?> getComponentType()

使用class类

要想对JVM中Class类封装的信息进行访问

通过无参构造实例化对象
1
2
3
Class<?> c =String.class;//也可以用forname得到class对象。
//得到class对象后,通过newInstance实例化对象。
Object str =c.newInstance();
调用有参构造实例化对象
1
2
3
4
5
6
7
8
9
操作时需要明确调用的构造函数,并将参数进行传递。
1.通过class类的getConstructors()取得本类中的全部构造方法
2.向构造方法传递一个对象数组进去,里面包含了构造方法所需的各个参数
3.之后通过Constructor实例化对象
Class<?> c =null;
c =Class.forNAme("org.sysu.book");
Constructor<?> cons[] =null;
cons=c.getConstructors();
Book book =(Book)cons[0].newInstance("wkn",22);

反射的基本运用

反射可以用于判断任意对象所属的类,获得Class对象,构造任意一个对象以及调用一个对象。介绍一下基本反射功能的实现.

判断是否为某个类的实例
1
2
3
4
5
public native boolean isInstance(Object obj);
Hello hello =new Hello();
Class<?> c = hello.getClass();//找到了封装的Class类对象.
System.out.println(c.isInstance(hello));
//true
创建实例

1 使用class对象的newInstance()方法创建class对象对应类的实例

1
2
Class<?> c =String.class;
Object str =c.newInstance();

2 先通过class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法创建实例,这种方法可以用指定的构造器构造类的实例。

1
2
3
4
5
6
7
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器!!!!
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("wkn");
System.out.println(obj);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//实例
Hello hello =new Hello();
Class<?> c = hello.getClass();//找到了封装的Class类对象.
try {//找到有三个参数的构造函数
Constructor c1 =c.getConstructor(String.class,Integer.class,String.class);
try {
Hello hhh = (Hello) c1.newInstance("123",1,"123");//实例化对象
System.out.println(hhh.toString());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
获取方法

获取某个class对象的方法集合

1 getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

1
public Method[] getDeclaredMethods() throws SecurityException

2 getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。

1
public Method[] getMethods() throws SecurityException

3 getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象

1
public Method getMethod(String name, Class<?>... parameterTypes)
获取构造器信息
1
public T newInstance(Object ... initargs)
获取类的成员变量信息
1
2
3
getFiled: 访问公有的成员变量
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量
getFileds和getDeclaredFields用法同上(参照Method)
但是获取私有属性的时候必须先设置Accessible为true,然后才能获取。
1
2
3
4
5
6
Hello hello3 =new Hello();
Class<?> t3 = hello.getClass();//找到了封装的Class类对象.
Field f = t3.getDeclaredField("name");
f.setAccessible(true); //要先设置这个!!!!!!!!!!!!!!!!!!!!!!!!!!!!
f.set(hello3,"123");
System.out.println(hello3.toString());
调用方法

当我们从类中获取了一个方法后,我们就可以用invoke()方法来调用这个方法。invoke方法的原型为: 调用方法,必须传入对象实例

1
2
3
4
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
//obj是指定哪个对象调用,args是传入的参数.

下面是一个实例:

1
2
3
4
5
6
7
8
9
10
11
//实例2
Hello hello1 =new Hello();
Class<?> t = hello.getClass();//找到了封装的Class类对象.
try {
Method m = t.getMethod("method1",String.class);//method1 是方法的名字
Hello hello2 = (Hello) t.newInstance();// Hello对象
Object re = m.invoke(hello2,"sss"); //反射调用方法
System.out.println(re);
} catch (Exception e) {
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class test1 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> klass = methodClass.class;
//创建methodClass的实例
Object obj = klass.newInstance();
//获取methodClass类的add方法
Method method = klass.getMethod("add",int.class,int.class);
//调用method对应的方法 => add(1,4)
Object result = method.invoke(obj,1,4);
System.out.println(result);
}
}
class methodClass {

public final int f = 3;
public int add(int a,int b) {
return a+b;
}
public int sub(int a,int b) {
return a+b;
}
}

反射的应用之注解

从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。