首先思考一下Java有几种方式创建一个对象
Java创建对象的方式大概有一下三种:
- 使用new关键字:这是我们最常见的也是最简单的创建对象的方式
- 使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去
- 使用序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象
一、概述
Java的反射机制,是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。
自己思考一下什么是反射
简单地说,Java的反射就是:
我们需要一个对象,通过Java的反射机制我们可以获取到类的信息,将一个类的属性和方法利用反射技术映射成一个个Java对象,从而拿这些Java对象来做一些事情。也就是说,反射是反射Java类的各个组成部分。
二、反射的作用
一般来说,反射是用来做一些基础框架的,或者说写抽象都比较高的底层代码。你搞懂了反射,就可以帮助你理解框架的一些原理,有一句很经典的话:反射是框架设计的灵魂
三、通过反射获取到想要的对象
开发人员平常写的代码是.java文件,通过Java编译器编译成.class文件。Java是面向对象的语言,一切皆对象,所以java认为 这些编译后的 class文件,也可以抽象成一个类,这个类就是Class类
在Class类中有个静态方法
Class clazz = Class.forName("com.example.Person");
获取Class类对象的三种方式
- Class.forName("com.example.Person");
- 对象.getClass();
- 类名.class;
Class类的常用方法
Class类提供了如下几个常用方法
Constructor<T> getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Field getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
四、反射的使用
先创建一个Person类如下:
package com.example;
public class Person {
private String name;
private int age;
public Person() {
System.out.println("这是无参构造方法");
}
public Person(String name) {
System.out.println("姓名:" + name);
}
private Person(int age) {
System.out.println("年龄:" + age);
}
public Person(String name, int age) {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
- 反射构造方法
- 反射无参构造方法
- 反射一个参数的构造方法
- 反射多个参数的构造方法
- 反射私有的构造方法
- 注意:在反射私有的构造函数时,用普通的
clazz.getConstructor()会报错,因为它是私有的,所以提供了专门反射私有构造函数的方法getDeclaredConstructor(Class<?>... parameterTypes);//读取私有的构造函数,用这个方法读取完还需要设置一下暴力反射才可以constructor.setAccessible(true);
- 注意:在反射私有的构造函数时,用普通的
- 反射获得所有构造方法
- 反射无参构造方法
- 反射普通方法
package com.example;
import java.util.Date;
public class Person {
private String name;
private int age;
public Person() {
System.out.println("这是无参构造方法");
}
public Person(String name) {
System.out.println("姓名:" + name);
}
private Person(int age) {
System.out.println("年龄:" + age);
}
public Person(String name, int age) {
System.out.println("姓名:" + name + ",年龄:" + age);
}
public void m1() {
System.out.println("m1");
}
public void m2(String name) {
System.out.println(name);
}
public String m3(String name,int age) {
System.out.println(name+":"+age);
return "aaa";
}
private void m4(Date d) {
System.out.println(d);
}
public static void m5() {
System.out.println("m5");
}
public static void m6(String[] strs) {
System.out.println(strs.length);
}
public static void main(String[] args) {
System.out.println("main");
}
}
@org.junit.jupiter.api.Test//public void m1()
public void test1() throws Exception{
Class clazz = Class.forName("com.cj.test.Person");
Person p = (Person)clazz.newInstance();
Method m = clazz.getMethod("m1", null);
m.invoke(p, null);
}
@org.junit.jupiter.api.Test//public void m2(String name)
public void test2() throws Exception{
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getMethod("m2", String.class);
m.invoke(p, "张三");
}
@org.junit.jupiter.api.Test//public String m3(String name,int age)
public void test3() throws Exception{
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getMethod("m3", String.class,int.class);
String returnValue = (String)m.invoke(p, "张三",23);
System.out.println(returnValue);
}
@org.junit.jupiter.api.Test//private void m4(Date d)
public void test4() throws Exception{
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getDeclaredMethod("m4", Date.class);
m.setAccessible(true);
m.invoke(p,new Date());
}
@org.junit.jupiter.api.Test//public static void m5()
public void test5() throws Exception{
Class clazz = Person.class;
Method m = clazz.getMethod("m5", null);
m.invoke(null,null);
}
@org.junit.jupiter.api.Test//private static void m6(String[] strs)
public void test6() throws Exception{
Class clazz = Person.class;
Method m = clazz.getDeclaredMethod("m6",String[].class);
m.setAccessible(true);
m.invoke(null,(Object)new String[]{"a","b"});
}
@org.junit.jupiter.api.Test
public void test7() throws Exception{
Class clazz = Person.class;
Method m = clazz.getMethod("main",String[].class);
m.invoke(null,new Object[]{new String[]{"a","b"}});
}
注意:看下上边代码里test6和test7的invoke方法里传的参数和其他的有点不一样
这是因为 jdk1.4和jdk1.5处理invoke方法有区别
- 1.5:public Object invoke(Object obj,Object…args)
- 1.4:public Object invoke(Object obj,Object[] args)
由于JDK1.4和1.5对invoke方法的处理有区别, 所以在反射类似于main(String[] args) 这种参数是数组的方法时需要特殊处理
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数个数不对的问题。
上述问题的解决方法:
- mainMethod.invoke(null,new Object[]{new String[]{"xxx"}}); - 这种方式,由于你传的是一个数组的参数,所以为了向下兼容1.4的语法,javac遇到数组会给你拆开成多个参数,但是由于咱们这个Object[ ] 数组里只有一个元素值,所以就算它拆也没关系
- mainMethod.invoke(null,(Object)new String[]{"xxx"}); - 这种方式相当于你传的参数是一个对象,而不是数组,所以就算是按照1.4的语法它也不会拆,所以问题搞定 - 编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
对上边的描述进行一下总结:在反射方法时,如果方法的参数是一个数组,考虑到向下兼容问题,会按照JDK1.4的语法来对待(JVM会把传递的数组参数拆开,拆开就会报参数的个数不匹配的错误) 解决办法:防止JVM拆开你的数组
- 方式一:把数组看做是一个Object对象
- 方式二:重新构建一个Object数组,那个参数数组作为唯一的元素存在。
- 反射属性字段
package com.example;
import java.util.Date;
public class Person {
public String name = "李四";
private int age = 18;
public static Date time;
public int getAge() {
return age;
}
public Person() {
System.out.println("默认的无参构造方法执行了");
}
public Person(String name) {
System.out.println("姓名:" + name);
}
public Person(String name, int age) {
System.out.println(name + "=" + age);
}
private Person(int age) {
System.out.println("年龄:" + age);
}
public void m1() {
System.out.println("m1");
}
public void m2(String name) {
System.out.println(name);
}
public String m3(String name, int age) {
System.out.println(name + ":" + age);
return "aaa";
}
private void m4(Date d) {
System.out.println(d);
}
public static void m5() {
System.out.println("m5");
}
public static void m6(String[] strs) {
System.out.println(strs.length);
}
public static void main(String[] args) {
System.out.println("main");
}
}
//public String name="李四";
@org.junit.jupiter.api.Test
public void test8() throws Exception{
Class clazz = Person.class;
Person p = (Person)clazz.newInstance();
Field f = clazz.getField("name");
String s = (String)f.get(p);
System.out.println(s);
//更改name的值
f.set(p, "王六");
System.out.println(p.name);
}
@org.junit.jupiter.api.Test//private int age = 18;
public void test9() throws Exception{
Class clazz = Person.class;
Person p = (Person)clazz.newInstance();
Field f = clazz.getDeclaredField("age");
f.setAccessible(true);
int age = (Integer)f.get(p);
System.out.println(age);
f.set(p, 28);
age = (Integer)f.get(p);
System.out.println(age);
}
@org.junit.jupiter.api.Test//public static Date time;
public void test10() throws Exception{
Class clazz = Person.class;
Field f = clazz.getField("time");
f.set(null, new Date());
System.out.println(Person.time);
}
看完上边有关反射的东西, 对常用框架里的配置文件是不是有点思路了
除了这个,常用的框架里还有很多地方都用到了反射,反射是框架的灵魂,具备反射知识和思想,是看懂框架的基础
平常用到的框架,除了配置文件的形式,现在很多都使用了注解的形式,其实注解也和反射息息相关,使用反射也能轻而易举的拿到类、字段、方法上的注解,然后编写注解解析器对这些注解进行解析,做一些相关的处理,所以说不管是配置文件还是注解的形式,它们都和反射有关