前言:在理解反射前,先打一下基础,java创建对象的4种方式
类:成员变量,成员方法,构造方法
在不new对象的情况下,如何获取该类的成员变量,成员方法,构造方法
java提供了反射机制允许对类的成员变量,成员方法和构造方法的信息进行编程式访问。
1.关于类的对象和类对象的区别
类的对象:基于某个类new出来的对象,也称作实例对象
类对象:类加载的产物,封装了一个类的所有信息(类名,父类,接口,属性,方法,构造方法)
类的对象从属于类,比如:Person person = new Person(),此时person的类型为Person
而类对象是虚拟机加载类的产物,类加载器加载所有的类,每一个类都会生成一个类对象,其类型为Class<?>。Class好比一本书,项目里所有的java类都会生成一个属于Class的对象,每一个类的类对象都包含这个类的所有信息。
在类加载的时候,我们可以通过反射动态的获取类的加载信息,在类加载的时候动态的创建类的对象并动态的调用方法。
注意:每个类加载到内存都会生成一个唯一的类对象
2.获取类对象的三种方法
(1)通过类的对象获取类对象
Student s = new Student();
Class c = s.getClass();
(2)通过类名获取类对象
Class c = 类名.class;
(3)通过Class里的静态方法forName()获取类对象
Class c=Class.forName("包名.类名");
3.反射通用操作
反射通用操作:使用反射机制获取类对象,并使用Class对象的方法获取表示类成员的各种对象(比如Constructor、Method、Field等),实现反射各种应用。
4.创建Person类
package entity;
import java.io.Serializable;
public class Person implements Serializable,Cloneable{
//姓名
private String name;
//年龄
private int age;
public Person() {
System.out.println("无参构造执行了...");
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
System.out.println("带参构造方法执行了...");
}
//吃
public void eat() {
System.out.println(name+"正在吃东西......");
}
@Override
public String toString() {
return "entity.Person [name=" + name + ", age=" + age + "]";
}
//带参的方法
public void eat(String food) {
System.out.println(name+"开始吃...."+food);
}
//私有的方法
private void privateMethod() {
System.out.println("这是一个私有方法");
}
//静态方法
public static void staticMethod() {
System.out.println("这是一个静态方法");
}
}
类说明:
所在包:entity包下含有Person类
私有参数:name和age
无参构造:TestPerson()
有参构造:TesrPerson(String name,Int age)
无参方法:eat()
有参方法:eat(String food)
私有方法:privateMethod()
静态方法:staticMethod()
5.三种方式获取Person类对象
public static void getClazz() throws ClassNotFoundException {
//1使用对象获取类对象
Person zhangsan=new Person();
Class<?> class1=zhangsan.getClass();
System.out.println(class1);
System.out.println(class1.hashCode());
//2使用类名.class属性
Class<?> class2=Person.class;
System.out.println(class2);
System.out.println(class2.hashCode());
//3使用Class的静态方法[推荐使用]
Class<?> class3=Class.forName("entity.Person");
System.out.println(class3);
System.out.println(class3.hashCode());
}
注意:类对象的类型为Class<?>;类的完全名为:包名+类名;
通过打印hashCode值发现,只有一个类对象。
第一种:new一个Person对象,之后使用 对象名.getClass() 获取类对象
第二种:直接 类名.class 获取类对象
第三种:使用Class类里的静态方法Class.forName(类的完全名)获取类对象
关于为什么推荐使用静态方法获取类对象:
1.在常用的Spring框架种对象的创建和销毁都交给了Spring框架管理,开发者不再手动创建对象,其次频繁的创建对象,代码冗余,且造成内存浪费,所以不推荐第一种。
2.在开发中,比如Mapper映射文件,一般会将类的完全名注入,再通过反射代理获取类对象然后创建类的对象。所以推荐Class.forName(类的完全名)方式获取 ,做到对象的创建和使用分离。理论上类名.class也可以。
6.使用反射获取类的名字,包名,父类,接口
public static void reflectOpe1() throws Exception {
/**
* 获取类对象,这里有三种方法可以获取,这里通过静态方法获取
*/
Class<?> class1=Class.forName("entity.Person");
//getName();
System.out.println("获取Person类的完全名称"+class1.getName());
//getPackage();
System.out.println("获取Person类的包名"+class1.getPackage().getName());
//getSuperClass();
System.out.println("获取Person类的父类"+class1.getSuperclass().getName());
//getInterfaces();
Class<?>[] classes=class1.getInterfaces();
System.out.println("获取Person类的接口"+Arrays.toString(classes));
System.out.println("获取Person类的简单名称"+class1.getSimpleName());
System.out.println("获取Person类的类型名称"+class1.getTypeName());
}
在第5步中,我们获取到了类对象,之后那么就可以尝试获取类的信息了
7.使用反射获取类的构造方法并创建对象
public static void reflectOpe2() throws Exception{
//(1)获取类的类对象
Class<?> class1=Class.forName("entity.Person");
//(2)获取类的构造方法 Constructor
Constructor<?>[] cons=class1.getConstructors();
for (Constructor<?> con : cons) {
System.out.println("构造方法:"+con.toString());
}
//(3)获取类中无参构造
Constructor<?> con=class1.getConstructor();
Person zhangsan=(Person)con.newInstance();
Person lisi=(Person)con.newInstance();
System.out.println("张三:"+zhangsan.toString());
System.out.println("李四:"+lisi.toString());
//简便方法:类对象.newInstance();
Person wangwu=(Person)class1.newInstance();
System.out.println("王五:"+wangwu.toString());
//(4)获取类中带参构造方法
Constructor<?> con2=class1.getConstructor(String.class,int.class);
Person xiaoli=(Person)con2.newInstance("晓丽",20);
System.out.println("晓丽:"+xiaoli.toString());
}
(1)获取所有的构造方法 类对象.getConstructors( )
(2)只获取无参构造 类对象.getConstructor( )
(3)只获取有参构造 类对象.getConstructors(String.class,int.class)
备注:有参构造参数为 参数类型的类对象 ;
(4)创建对象使用 构造器对象.newInstance( ), 有参构造传参数,无参构造就不传了
8.使用反射获取类中的方法
//3使用反射获取类中的方法,并调用方法
public static void reflectOpe3() throws Exception{
//(1)获取类对象
Class<?> class1=Class.forName("entity.Person");
//(2)获取方法 Method对象
//2.1getMethods() 获取公开的方法,包括从父类继承的方法
//Method[] methods=class1.getMethods();
//2.2getDeclaredMethods() 获取类中的所有方法,包括私有、默认、保护的 、不包含继承的方法
Method[] methods=class1.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.toString());
}
//(3)获取单个方法
//3.1eat
Method eatMethod=class1.getMethod("eat");
//调用方法
//正常调用方法 Person zhangsan=new Person(); zhangsan.eat();
Person zhangsan=(Person)class1.newInstance();
eatMethod.invoke(zhangsan);//zhangsan.eat();
System.out.println("------------------");
//3.2toString
Method toStringMethod=class1.getMethod("toString");
Object result=toStringMethod.invoke(zhangsan);
System.out.println(result);
System.out.println("-------------------");
//3.3带参的eat
Method eatMethod2=class1.getMethod("eat", String.class);
eatMethod2.invoke(zhangsan, "鸡腿");
//3.4获取私有方法
Method privateMethod=class1.getDeclaredMethod("privateMethod");
//设置访问权限无效
privateMethod.setAccessible(true);
privateMethod.invoke(zhangsan);
//3.4获取静态方法
Method staticMethod=class1.getMethod("staticMethod");
//正常调用 Person.staticMethod
staticMethod.invoke(null);
}
(1)获取所有方法包括从父类继承的方法:Method[] methods=class1.getMethods()
(2)获取所有方法不包括父类继承的方法:Method[] methods=class1.getDeclaredMethods();
(3)获取单个方法:Method eatMethod=class1.getMethod("方法名称");
(4)创建对象:类对象.newInstance() 区别上面的构造器创建对象:构造器对象.newInstance()
(5)有了方法和类的对象,那么使用反射调用方法的写法如下:
eatMethod.invoke(zhangsan);
注意:这里的eatMethod对象为方法对象,调用invoke方法,传入Person的实例,调用方法
(6)获取带参数的方法:不仅要传入方法名,还有传入参数类型
Method eatMethod2=class1.getMethod("eat", String.class);
eatMethod2.invoke(zhangsan, "鸡腿");
(7)获取私有方法:需要设置访问权限无效
Method privateMethod=class1.getDeclaredMethod("privateMethod");
//设置访问权限无效
privateMethod.setAccessible(true);
privateMethod.invoke(zhangsan);
(8)获取静态方法:invoke不需要传实例对象,直接传null
Method staticMethod=class1.getMethod("staticMethod");
//正常调用 Person.staticMethod
staticMethod.invoke(null);
9.使用反射获取类中的属性
public static void reflectOpe4() throws Exception{
//(1)获取类对象
Class<?> class1=Class.forName("entity.Person");
//(2)获取属性(字段) 公开的字段,父类继承的字段
//Field[] fields=class1.getFields();
//getDeclaredFields()获取所有的属性,包括私有,默认 ,包含,
Field[] fields=class1.getDeclaredFields();
System.out.println(fields.length);
for (Field field : fields) {
System.out.println(field.toString());
}
//(3)获取name属性
Field namefield=class1.getDeclaredField("name");
namefield.setAccessible(true);
//(4)赋值 正常调用 Person zhangsan=new Person(); zhangsan.name="张三";
Person zhangsan=(Person)class1.newInstance();
namefield.set(zhangsan, "张三"); //zhangsan.name="张三";
//(5) 获取值
System.out.println(namefield.get(zhangsan));// zhangsan.name
}
和获取方法异曲同工,懒的写了,注意Field[ ] 对象为类的属性对象,用来存储获取到的类的属性
10.使用反射实现一个可以调用任何对象方法的通用方法
public static Object invokeAny(Object obj,String methodName,Class<?>[] types,Object...args) throws Exception {
//1获取类对象
Class<?> class1=obj.getClass();
//2获取方法
Method method=class1.getMethod(methodName, types);
//3调用
return method.invoke(obj, args);
}
invokeAny(Object obj,methodName,Class<?>[ ] types,Object...args)
Object obj 传入类对象
methodName 传入方法名
Class<?>[] type 方法参数类型
Object...args 方法参数
总结:
刚开始学习反射搞不清楚为什么new了对象还要再getClass,还要搞什么三种方法获取类对象,又传什么包名,类的完全名之类的,我都知道了类名,方法名,还绕一圈再获取类名方法名,这不是绕来绕去的,脱裤子放屁吗?
Java反射最大的好处就是能在运行期间,获得某个类的结构、成员变量,用来实例化。
下列是具体使用场景:假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。
把前面的话再粘贴一下,相当重要:
而类对象是虚拟机加载类的产物,类加载器加载所有的类,每一个类都会生成一个类对象,其类型为Class<?>。Class好比一本书,项目里所有的java类都会生成一个属于Class的对象,每一个类的类对象都包含这个类的所有信息。
在类加载的时候,我们可以通过反射动态的获取类的加载信息,在类加载的时候动态的创建类的对象并动态的调用方法。
反射是动态的在程序加载的时候创建对象,而程序中实例化类的对象是静态的,需要经历编译解释加载到虚拟机中,是静态的。