java反射机制

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
    2
    java.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
2
3
System.out.println(int.class);//int
System.out.println(double.class);//double
System.out.println(void.class);//void

在基本类型的包装类型中都存在一个TYPE的一个常量,返回该包装类型对应基本类型的字节码对象 static Class TYPE

1
2
3
Class<Integer> clz = Integer.TYPE;
System.out.println(clz);
System.out.println(clz == int.class);//true

注意:Integer有自己的字节码对象,和int肯定不相同

2.3.数组的Class实例

数组也是数据类型,所以可以用方式一和方式二

1
2
3
int[] arr = {1,1,2,5};  //arr是数组对象
Class<int[]> clz3 = int[].class;
arr.getClass();//class [I

API中提到:所有具有相同维数的和相同数据类型的数组在JVM中只有一个字节码对象,也就是和数组中元素没有任何关系

1
2
3
4
5
6
7
8
int[] arr = {1,1,2,5};  //arr是数组对象
int[] arr2 = {1,1,2,3,4,5}; //arr2是数组对象
//方式二
System.out.println(arr.getClass());//class [I System.out.println(arr2.getClass());//class [I
//方式一
Class<int[]> clz3 = int[].class;
System.out.println(clz3);
System.out.println(clz3 == arr2.getClass());//true

3.操作构造器

3.1.获取构造器

获取所有构造器:

1
2
Constructor<?>[] getConstructors() 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
Constructor<?>[] getDeclaredConstructors() 返回所有的构造方法,没有权限限制

获取指定的一个构造器:

1
2
3
4
Constructor<T> getConstructor(Class<?>... parameterTypes) 
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
1
2
3
4
5
6
7
Class<Person> clz = Person.class;
Constructor<?>[] con = clz.getConstructors();//获取类中所有的public构造器
con = clz.getDeclaredConstructors();//获取类中所有的构造器,没有权限限制

Constructor<?> con = clz.getConstructor();//获取类中的无参数构造器
con = clz.getConstructor(String.class,int.class);//带有String和int类型的构造器
con = clz.getDeclaredConstructor(String.class);//获取带有private修饰的有参数构造器

3.2.用构造器创建对象

对于public的构造器

1
构造器对象.newInstance(实参);

对于private的构造器

1
2
构造器对象.setAccessible(true)//设置当前构造器可访问,这是Constructor的父类AccessibleObject中方法
构造器对象.newInstance(实参);
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
class People{
public People(){
System.out.println("无参数构造方法");
}
public People(String name,int age){
System.out.println(name + "," + age);
}
@SuppressWarnings("unused")
private People(String name){
System.out.println(name);
}
}
public class CreateObjectDemo {
public static void main(String[] args) throws Exception {
//获取People类的字节码对象
Class<People> clz = People.class;
Constructor<?> con = clz.getConstructor();
con.newInstance();
System.out.println("===============");

con = clz.getConstructor(String.class,int.class);
con.newInstance("XXX",17);

con = clz.getDeclaredConstructor(String.class);
con.setAccessible(true);//设置当前构造器可访问,这是Constructor的父类AccessibleObject中方法
con.newInstance("OOO");
}
}

4.操作方法

4.1.获取方法

获取类中的所有方法:

1
2
3
Method[] getDeclaredMethods() 获取自身类中所有的方法(不包括继承的,和访问权限无关)

Method[] getMethods() 获取包括自身和继承过来的所有的public方法

获取类中的某一个方法

1
2
3
4
5
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 
表示调用指定的一个本类中的方法(不包括继承的)

Method getMethod(String name, Class<?>... parameterTypes)
表示调用指定的一个公共的方法(包括继承的)
1
2
3
4
Class<Person> clz = Person.class;
Method[] me = clz.getMethods();//获取类中所有方法,包括继承过来的public方法

Method m =clz.getMethod("doWork");//获取public的doWork方法

4.2.调用方法

  • 1):获取方法所在类的字节码对象-

  • 2):获取方法对象-

  • 3):使用反射调用方法.

    public 修饰的方法:

    在Method类中有方法:public Object invoke(Object obj,Object… args):表示调用当前Method所表示的方法

    obj表示被调用方法所属的对象,args表示向方法传递的实参,返回也是方法返回的结果

    private修饰的方法

    Method对象.setAccessible(true);//设置当前方法可访问,这是Method的父类AccessibleObject中方法

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
class People{
public void doWork(){
System.out.println("我是无参数的doWork");
}
public String doWork(String name){
System.out.println("我是带参数的doWork");
return name;
}
@SuppressWarnings("unused")
private void eat(){
System.out.println("我是eat方法");
}
}
public class MethodInvokeDemo {
public static void main(String[] args) throws Exception {
//获取类的字节码对象
Class<People> clz = People.class;
Method m =clz.getMethod("doWork");
m.invoke(clz.newInstance());

System.out.println("============");

m = clz.getMethod("doWork", String.class);
Object o = m.invoke(clz.newInstance(), "xxx");
System.out.println(o);

m = clz.getDeclaredMethod("eat");
m.setAccessible(true);//设置方法可执行
People p = clz.newInstance();
m.invoke(p);
}
}

对于静态方法

对于static方法,静态方法属于类本身,此时的invoke方法的参数就是incoike(null,Object… args),也就是不需要对象

对于形参中的可变参数

对于数组类型的引用类型的参数,底层会自动解包,为了解决该问题,我们使用Object的一维数组把实际参数包装起来.

Method对象.invoke(方法所属对象,new Object[]{所有的实参});

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
class Test{
//基本类型可变参数
public void doWork(int...arr){
System.out.println("doWork方法被调用," + Arrays.toString(arr));
}
//引用类型可变参数
public static void doWork2(String...arr){
System.out.println("doWork2方法被调用," + Arrays.toString(arr));
}
}
public class MethodArrayInvokeDemo {
public static void main(String[] args) throws Exception {
//获取类的字节码对象
Class<Test> clz = Test.class;

//基本类型的数组调用,第二个实参传的可以是新建一个int类型的数组。也可以用Object数组进行包装
Method m = clz.getMethod("doWork", int[].class);
// m.invoke(clz.newInstance(), new int[]{1,2,3});
m.invoke(clz.newInstance(), new Object[]{new int[]{1,2,3,3}});

//引用类型的方法调用,第二个实参只能放在Objec类型的数组中
m = clz.getMethod("doWork2", String[].class);
m = clz.getMethod("doWork2", String[].class);
//m.invoke(null, new String[]{"A","B"});//错误的写法
m.invoke(null, new Object[]{new String[]{"A","B"}});

}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo {
public static void main(String[] args){
String name = Demo.class.getName();//getName()返回该类的权限的权限的名称
System.out.println(name);

String packagename = Demo.class.getPackage().getName();//getPackage()返回该类所在的包名
System.out.println(packagename);

int mod = Demo.class.getModifiers();//返回该类的修饰符
String type = Modifier.toString(mod);
System.out.println(type);

String supername = Demo.class.getSuperclass().getName();//返回父类的名称
System.out.println(supername);

Object arr = new int[]{1,2,2};//判断该字节码对象是否是数组类,也就是实际类型是不是数组
System.out.println(arr.getClass().isArray());
}
}

Class类中有方法getClassLoader()可以返回一个类加载器对象,用来加载资源文件。加载资源文件 db.properties文件,只能使用Properties类的load方法.

对于加载资源文件,这里有三种方式:

  • 使用绝对路径加载,可以加载到bin目录中,这种做法实际不可行

    1
    2
    3
    4
    5
    6
    public 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
    13
    public 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
    7
    public 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目录下的,而第三种加载的文件和当前程序的字节码在一个路径下面

文章目录
  1. 1. 1.什么是反射
  2. 2. 2.Class类和Classd的实例
    1. 2.1. 2.1.获取Class实例的方法
    2. 2.2. 2.2.内置的Class实例
    3. 2.3. 2.3.数组的Class实例
  3. 3. 3.操作构造器
    1. 3.1. 3.1.获取构造器
    2. 3.2. 3.2.用构造器创建对象
  4. 4. 4.操作方法
    1. 4.1. 4.1.获取方法
    2. 4.2. 4.2.调用方法
  5. 5. 5.操作字段
    1. 5.1. 5.1.获取字段
    2. 5.2. 5.2.操作字段
  6. 6. 6.其他API