1.什么是反射
Everything is object!
这在java中可以说是一个公理,对象都可以抽象为类,那么类在java中应该也是一种对象,他其实是属于一个叫做Class类的字节码对象,它应该描述的是所有的类,具有所有的类的相同方法等。
JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
简单理解:把类中的每一种成员,都描述成一个新的类.Class: 表示所有的类.Constructor: 表示所有的构造器.Method: 表示所有的方法.Field: 表示所有的字段.
2.Class类和Classd的实例
因为Class是对很多类的抽象,为了区分这些类的字节码对象,必须用泛型规范Class
2.1.获取Class实例的方法
方式一:使用class属性(所有的数据类型都具有)
1
Class<java.util.Date> clz1 = java.util.Date.class;
方式二:使用对象的getClass() 该方法是Object类中的,说明是任意类型的对象都可调
1
2java.util.Date data = new java.util.Date();
Class<?> clz2 = data.getClass();方式三:Class类中的静态方法forName(String className),className是类的权限定名称
1
Class<?> clz3 = Class.forName("java.util.Date");
注意:在同一个类中,JVM中只存在一个字节码对象,也就是上述三种方式所创建的字节码对象是一样的
2.2.内置的Class实例
基本数据类型没有类的概念,也没有对象,因此不能使用方式二和方式三。只能用第一种,也就是
1 | Class clz = 数据类型.class |
八大基本数据类型加上一个void,有九大内置class实例.比如:
1 | System.out.println(int.class);//int |
在基本类型的包装类型中都存在一个TYPE的一个常量,返回该包装类型对应基本类型的字节码对象 static Class
TYPE
1 | Class<Integer> clz = Integer.TYPE; |
注意:Integer有自己的字节码对象,和int肯定不相同
2.3.数组的Class实例
数组也是数据类型,所以可以用方式一和方式二
1 | int[] arr = {1,1,2,5}; //arr是数组对象 |
API中提到:所有具有相同维数的和相同数据类型的数组在JVM中只有一个字节码对象,也就是和数组中元素没有任何关系
1 | int[] arr = {1,1,2,5}; //arr是数组对象 |
3.操作构造器
3.1.获取构造器
获取所有构造器:
1 | Constructor<?>[] getConstructors() 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 |
获取指定的一个构造器:
1 | Constructor<T> getConstructor(Class<?>... parameterTypes) |
1 | Class<Person> clz = Person.class; |
3.2.用构造器创建对象
对于public的构造器
1 | 构造器对象.newInstance(实参); |
对于private的构造器
1 | 构造器对象.setAccessible(true)//设置当前构造器可访问,这是Constructor的父类AccessibleObject中方法 |
1 | class People{ |
4.操作方法
4.1.获取方法
获取类中的所有方法:
1 | Method[] getDeclaredMethods() 获取自身类中所有的方法(不包括继承的,和访问权限无关) |
获取类中的某一个方法
1 | Method getDeclaredMethod(String name, Class<?>... parameterTypes) |
1 | Class<Person> clz = Person.class; |
4.2.调用方法
1):获取方法所在类的字节码对象-
2):获取方法对象-
3):使用反射调用方法.
public 修饰的方法:
在Method类中有方法:public Object invoke(Object obj,Object… args):表示调用当前Method所表示的方法
obj表示被调用方法所属的对象,args表示向方法传递的实参,返回也是方法返回的结果
private修饰的方法
Method对象.setAccessible(true);//设置当前方法可访问,这是Method的父类AccessibleObject中方法
1 | class People{ |
对于静态方法
对于static方法,静态方法属于类本身,此时的invoke方法的参数就是incoike(null,Object… args),也就是不需要对象
对于形参中的可变参数
对于数组类型的引用类型的参数,底层会自动解包,为了解决该问题,我们使用Object的一维数组把实际参数包装起来.
Method对象.invoke(方法所属对象,new Object[]{所有的实参});
1 | class Test{ |
5.操作字段
5.1.获取字段
使用反射获取字段:
- 找到字段所在类的字节码
- 获取字段
public Field[] getFields():获取当前Class所表示类中所有的public的字段,包括继承的字段.
public Field getField(String fieldName):获取当前Class所表示类中该fieldName名字的public字段,包括继承的字段.
public Field[] getDeclaredFields():获取当前Class所表示类中所有的字段,不包括继承的字段.
public Field[] getDeclaredField(String name):获取当前Class所表示类中该fieldName名字的字段,不包括继承的字段.
5.2.操作字段
给某个类中的字段设置值和获取值:
- 找到被操作字段所在类的字节码
- 获取到该被操作的字段对象
设置值/获取值
void setXX(Object obj, XX value) :为基本类型字段设置值,XX表示基本数据类型
void set(Object obj, Object value) :表示为引用类型字段设置值XX getXX(Object obj) :获取基本类型字段的值,XX表示基本数据类型
Object get(Object obj) :表示获取引用类型字段的值
6.其他API
1 | public class Demo { |
Class类中有方法getClassLoader()可以返回一个类加载器对象,用来加载资源文件。加载资源文件 db.properties文件,只能使用Properties类的load方法.
对于加载资源文件,这里有三种方式:
使用绝对路径加载,可以加载到bin目录中,这种做法实际不可行
1
2
3
4
5
6public static void test1() throws Exception{
Properties p = new Properties();
InputStream inStream = new FileInputStream("resource/db.properties"); p.load(inStream);
System.out.println(p);
test1();
}使用相对于classpath根路径加载,用到ClassLoader类加载器加载
1
2
3
4
5
6
7
8
9
10
11
12
13public static void test2() throws Exception{
/**
* 这里有两种方式获得类加载器对象
* 1.Class类中有方法getClassLoader()可以返回一个类加载器对象
* 2.Thread类中有方法getContextLoader()也可以返回一个类加载器对象
*/
Properties p = new Properties();
//ClassLoader loader = LoadeResourceDemo.class.getClassLoader();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream inStream = loader.getResourceAsStream("db.properties");//返回读取指定资源的输入流
p.load(inStream);//记载资源文件
System.out.println(p);
}相对于当前文件的字节码路径加载
1
2
3
4
5
6
7public static void test3() throws Exception{
Properties p = new Properties();
InputStream inStream =
LoadeResourceDemo.class.getResourceAsStream("db.properties");
p.load(inStream);
System.out.println(p);
}注意第二种和第三种是不一样的,区别是第二种加载的是classpath根路径的也或者是bin目录下的,而第三种加载的文件和当前程序的字节码在一个路径下面