本文是根据自己最近学习反射所做的总结,因为本身学的不深入,所以就做一些基础的总结,尽量详细。
Java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法。对任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
简单的来说,反射机制指的是程序在运行时能够获取自身信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。包括访问修饰符、父类、实现的接口、属性和方法的所有信息,并可在运行时创建对象、修改属性(包括私有的)、调用方法(包括私有的)。
那么为什么要用反射机制?直接创建对象不就可以了吗?这就涉及到了动态与静态的概念。
在这里假设一下场景,等一下再说动态静态的概念。
比如,你和同学正在开发一个项目,你们做不同的部分,但是你要用到的学生类里面的方法,是你同学负责的部分,你同学还没把这部分代码写好,这就耽误了你的正常使用,但是你又不能停止工作,这个时候你就可以用反射来获取学生类的一切信息。利用反射创建学生对象,调用学生类里面的方法等等。 (这里是我自己假想的,不是很确定正确与否,如果有人看到,这里有问题麻烦在下面留言告知。谢谢。)
继续说动态与静态。
静态编译:在编译的时候确定类型,绑定对象,即通过。就是如果是static修饰的东西,在编译的时候就分配了内存。
动态编译:运行时候确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,用以降低类之间的耦合性。
如static int a=10;在编译的时候,就为a这个变量分配了内存。
Student stu=new Student(“zhangsan0”,30);这句在运行的时候才会new出来stu这个实例,才会分配内存。
一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发当中。
它的缺点是对性能有影响,使用反射基本上是一种解释操作,这类操作总是慢于直接执行的相同操作。反射功能强大,但是耗时,多用来做工具和框架。
下面开始结合代码来说解释。
1 利用反射机制获取一个类的Class对象的方法 有三种 如下
// ---获取Employee这个类所对应的Class对象
//第一种方法通过 对象名点getClass()
Employee emp=new Employee("zhangsna", 18);
Class<T> classtype=emp.getClass();
System.out.println(classtype.getName());
//第二种方法 类名点class
Class<T> classtype=Employee.class;
System.out.println(classtype.getName());
//第三种方法
//使用Class.Forname() 来获取一个类所对应的Class对象
Class<T> classtype=Class.forName("com.classtype.Employee");//这里括号里面填的是 报名点类名 (也叫全限定名)
System.out.println(classtype.getName());
这里我们可以看出来,第一种方法,是new 出来emp对象了,既然能new出来,就可以直接用对象访问类中的任何方法了。第一种方法也没有什么必要了。
我们都知道,要创建对象,就要有构造方法,下面我们利用反射机制获取一个类的构造方法
获取构造方法有四种手段,调用Class类中的下面四个方法:
getConstructor()//可以传参数,返回指定public修饰的构造方法
getConstructors()//返回一个列表, 列表里面是所有的public修饰的构造方法
getDeclaredConstructor()//可以传参数,返回指定构造方法,与权限无关
getDeclaredConstructors()//返回一个列表,列表里面是所有的构造方法,与权限无关
package com.constructor;
import java.lang.reflect.Constructor;
//使用反射获取Person111中所有的构造器
public class ConstructorDemo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException {
getAllConstructor();
getOneConstructor();
}
//获取指定的一个构造方法
private static void getOneConstructor() throws NoSuchMethodException, SecurityException{
//第一步先获取一个类的字节码文件
Class clz=Person111.class;
//调取指定构造器的方法即可
//如:想获取public Person111(String name){};
//应该告诉程序,我想获取的是带有一个String类型的参数的构造器
Constructor cs = clz.getConstructor(String.class);
System.out.println(cs);
//如果获取private Person111(String name,int age){}
//并不是Constructor cs = clz.getConstructor(String.class,int.class);
//方法不带Declared永远只能获取公共的构造器
Constructor cs1 = clz.getDeclaredConstructor(String.class,int.class);
System.out.println(cs1);
}
//获取所有构造方法
private static void getAllConstructor(){
//找到Person111这个类的这份字节码
Class<Person111> clz = Person111.class;
//获取构造器
//Class中public Constructor<?>[] getConstrutors()
Constructor[] cs=clz.getConstructors();//这只能获取public修饰的构造器
for (Constructor c : cs) {
System.out.println(c+"我是public修饰的构造方法");//输出两个结果。就是两个public修饰的
}
System.out.println("=========================");
cs=clz.getDeclaredConstructors();//这个方法可以获取所有构造器 与权限无关
for (Constructor c : cs) {
System.out.println(c+"我是构造方法,与权限无关");
}
}
}
class Person111{
public Person111(){};
public Person111(String name){};
private Person111(String name,int age){};
}
有了构造方法,我们就可以创建实例了。我们都知道,new一个实例的时候,会调用构造方法。
那么我们通过调用构造方法就可以创建实例。
package com.constructor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/*通过调用构造方法创建实例
*
* */
public class ConstructorInvokeDemo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
//传统方法
//new Person111();
//使用反射来调用
//疑惑: Person111.class和Class.forName();??
//这里Person111.class其实就是Class.forName("全限定名");
// System.out.println(clz);
// System.out.println(Class.forName("com.constructor.Person111"));
//1.先找到被调用的构造方法所在类的字节码
Class<Person111> clz = Person111.class;
//2.找到被调用的指定的构造器
Constructor<Person111> cs = clz.getConstructor();
//3.执行该构造器
cs.newInstance();//无参的
ClassLoader c = clz.getClassLoader();
//如果一个类中的构造器可以直接访问,同时没有参数,可以直接调用Class中的newInstance方法
//不用第二步了
clz.newInstance();
//调用带参数的构造器
Constructor<Person111> cs1 = clz.getConstructor(String.class);
//调用私有的构造器
cs1 = clz.getDeclaredConstructor(String.class,int.class);
System.out.println(cs1);
//cs1.newInstance("lisi",78);//这里会报错,因为这个构造方法是私有的
//此时就要告诉程序在运行的时候忽略安全检查。或者设置该私有的构造器可访问
cs1.setAccessible(true);//设置可访问 OK了
cs1.newInstance("lisi",45);
}
}
class Person111{
public Person111(){
System.out.println("无参");
}
public User(String name){
System.out.println("1个参");
}
private User(String name,int age){
System.out.println("2个参");
}
}
我们也可以获取其他的方法(非构造方法)
也是有四种手段,调用Class类中的下面四个方法:
getMethod()//可以传参数,获取指定的方法
getMethods()//获取所有public修饰的方法 包括自身的和继承过来的 返回的是一个列表
getDeclaredMethod()//可以传参数,获取指定的方法,与权限无关
getDeclaredMethods()//获取所有的方法,不包括继承过来的方法,与权限无关。 返回的是一个列表
package com.method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
/*获取Person111类中的方法
*
* */
public class MethodDemo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
//传统方法
//Person111 Person111=new Person111();
//Person111.Person111name("zhangsan");
//使用反射来获取
//1.先找到被调用的构造器所在类的字节码
Class<Person111> clz = Person111.class;
//2.找到被调用的方法
Method[] cs = clz.getMethods();
for (Method method : cs) {
System.out.println(method);//获取所有公共方法
//包括自身和继承过来的所有的public方法。
}
//只获取本类的方法 要用getDeclaredMethods() 和访问权限无关
//不包括继承来的方法
cs=clz.getDeclaredMethods();
for (Method method : cs) {
System.out.println(method);//获取本类所有方法,与访问权限无关
}
}
//获取指定的一个方法
private static void getOneMethod() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
//第一步 获取字节码文件
Class<Person111> clz = Person111.class;
//第二步 找到指定的方法
//只有通过方法签名才能找到唯一的方法
//方法签名=法名+参数列表(参数类型,参数个数,参数顺序)
Method cs = clz.getMethod("Person111name", String.class);
System.out.println(cs);//第一个参数就是方法的名字,后面是参数的类型
//这里说一下,获取方法与获取构造方法不同的是,获取构造器不用传方法名字,因为构造方法名字与类名字一样。
//没有必要传入构造方法。
//获取私有方法
cs=clz.getDeclaredMethod("Person111nameage", String.class,int.class);
System.out.println(cs);
}
}
class Person111{
public void Person111(){
System.out.println("无参");
}
public void Person111name(String name){
System.out.println("1个参");
}
private void Person111nameage(String name,int age){
System.out.println("2个参");
}
}
获取完方法,下面我们利用反射机制来调用类中的方法。
package com.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
* 利用反射动态调用方法
* */
public class MethodInvoke {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
//调用Personfangfa0()方法
//第一步 获取方法所在类的字节码文件
Class<Person> clz = Person.class;
//第二步获取要调用的方法
Method cs = clz.getMethod("Personfangfa1",String.class);
//在method类中有方法
//invoke(obj, args)表示调用当前method所表示的方法)
/*obj:表示被调用方法底层所属对象 重点
*args:表示调用方法时传递的实际参数
*返回 底层方法的返回结果
* */
//第三步 利用反射创建对象实例
Object obj = clz.newInstance();
Object s = cs.invoke(obj,"zhagsan");//s为接受该方法的返回值
//这里的参数 上面已经说明了。
System.out.println(s);
//调用私有方法
System.out.println("***************************");
cs=clz.getDeclaredMethod("Personfangfa2", String.class,int.class);
//调用之前设置可访问权限
cs.setAccessible(true);
cs.invoke(clz.newInstance(), "lisi",12);
}
}
class Person{
public void Personfangfa0(){
System.out.println("无参");
}
public String Personfangfa1(String name){
System.out.println("1个参");
return name;
}
private void Personfangfa2(String name,int age){
System.out.println("2个参");
}
}
利用反射调用类中的静态方法与上面方法不一样。
package com.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*使用反射调用静态方法
* */
public class StaticMethodInvokeDemo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
//第一步 获取静态方法所属类的字节码文件
Class clz=StaticMethodInvokeDemo.class;
//第二步 获取要调用的静态方法
Method cs = clz.getMethod("statimethod");//没有参数,不用写
//cs.invoke(clz.newInstance());//通用方法调用,同样,无参数。
//静态方法不属于某个对象
//如果底层方法是静态的,可以忽略obj参数,设置为null.
cs.invoke(null);//无参数。不写
}
public static void statimethod(){
System.out.println("adaads");
}
}
利用反射调用可变参数的方法。这里有一点要注意的是传参数的时候,其底层会自动解包,所以如果我们传参数的时候,直接传入的是正确的参数,代码到了底层经过解包,就变成不正确的了。所以,我们传参数的时候,把正确的包装一下,最好用Object包装,因为一切类都是它的子类,所以用它包装不会出错。代码到了底层,通过自动解包,就是被包装的正确的参数。这样就不会出错。具体实现看代码。
package com.method;
/*利用反射调用可变参数的方法
*
* */
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class VarArgsMethodInvolkeDemo {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//使用反射调用sum
//第一步 获取方法所属类的字节码文件
Class<VarArgsMethodInvolkeDemo> clz = VarArgsMethodInvolkeDemo.class;
//第二步 获取要调用发方法
Method cs = clz.getMethod("sum", int[].class);//可变参数底层其实就是数组
//cs.invoke(null, 1,2,3,4,5);这中写法是错误的
//调用
cs.invoke(null, new int[]{1,2,3,4,5});
System.out.println("*******************");
//第二步 获取要调用的方法
cs=clz.getMethod("show2", String[].class);
//cs.invoke(null,new String[]{"A","B","C"});//这样写错误
/*对于数组类型的引用类型的参数,底层会自动解包,为了解决
该问题,我们使用Object的一维数组把实际参数包装起来。
查看源码发现,invoke的参数本来就是一个object类型。所以传入
object包装的类型之后,解包刚好是正确的类型。*/
cs.invoke(null,new Object[]{new String[]{"A","B","C"}});
/*以后使用反射调用invoke方法,传递实际参数的时候,无论是基本数据类型还是引用数据类型
也无论是可变参数还是不可变参数。反正就是 一切实际参数都包装在Object[]{}中,就没有问题*/
//只有一个参数也没问题,装进Object数组
}
//基本类型
public static void show1(int ...args) {
System.out.println(Arrays.toString(args));
}
//引用类型
public static void show2(String ...args) {
System.out.println(Arrays.toString(args));
}
}
下面我们看利用反射来获取类中的属性。
也是有四种手段,调用Class类中的下面四个方法:
getField()//可以传参数,获取指定属性
getFields()//获取所有public修饰的属性,包括继承过来的,返回一个列表
getDeclaredField()//可以传参数,获取指定属性,无访问权限无关
getDeclaredFields()/获取所有属性,不包括继承过来的,返回一个列表
package com.field;
import java.lang.reflect.Field;
/*利用反射获取字段
* */
public class FieldDemo {
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
//第一步 获取字节码文件
Class<Student> clz = Student.class;
//第二步 获取的包括继承的字段 公共的 所有的
Field[] cs = clz.getFields();
for (Field field : cs) {
System.out.println(field);
}
cs=clz.getDeclaredFields();//不包括继承的 和访问权限无关 本类所有的
for (Field field : cs) {
System.out.println(field);
}
System.out.println("****************");
getOneField();
}
public static void getOneField() throws NoSuchFieldException, SecurityException{
Class<Student> clz = Student.class;
Field cs = clz.getField("score");//获取一个指定的,只能是公共的
System.out.println(cs);
cs=clz.getDeclaredField("money");//获取一个指定字段 可以是私有,与权限无关
System.out.println(cs);
}
}
class Student extends User{
public int score;
private int money;
}
class User{
public String study;
}
下面利用反射来对类中的属性进行操作。
package com.field;
import java.lang.reflect.Field;
/*利用反射操作字段
*
* */
public class FieldInvoke {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
setValue();
getValue();
}
//获取值
private static void getValue() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException{
//第一步 获取字节所在类的字节码文件
Class<User1> clz = User1.class;
//第二步 获取字段
Field cs = clz.getDeclaredField("schoolname");
/*正常是
* User user=new User;
* user.getSchoolName(18);
* */
//反射
//先创建对象实例
Object obj = clz.newInstance();
cs.setAccessible(true);//调用之前设置访问权限 以为上面获取的是私有的
Object returnvalue = cs.get(obj);//这里的参数是指 属性底层所属对象
//利用returnvalue来接收cs.get(obj)返回的obj对象的schoolname的值
System.out.println(returnvalue);
}
//设置值
private static void setValue() throws NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException{
//1 获取字节码文件
Class<User1> clz = User1.class;
//2 获取字段对象 获取age字段
Field cs = clz.getDeclaredField("age");
//3 设置值
cs.setAccessible(true);//设置值之前设置可访问的权限
/*正常设置是
* User user=new User;
* user.setAge(18);
* */
//cs.set(obj, value); obj:字段底层是所属对象,所字段是static的,该值设置为null
//这里要说一下,如果字段是静态的,那么第一个参数要设置为null,因为正常访问静态属性的时候,直接类名点属性名。
//所以这里也没有必要把类自己的名字再传进去
// value:为要设置的值
Object obj = clz.newInstance();
cs.set(obj, 18);
//获取name字段
cs=clz.getDeclaredField("name");
cs.setAccessible(true);//设置可访问权限
cs.set(obj, "zhansan");//给name字段设置值
System.out.println(obj);
}
}
class User1{
private String name;
private int age;
private String schoolname="清华大学";
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
那么通过以上总结。利用反射获取构造方法,调用构造方法;
获取方法,调用方法;获取静态方法,调用静态方法;获取属性,操作属性。
我们可以发现,其实就是那几步。
第一步 获取字节码文件
第二步 获取要获取的方法(构造方法 属性)
第三步 调用
中间要注意的就是 带不带Declare。 带Declare就是与权限无关的。 不带就是只能获取public修饰的,包括继承过来的。
调用Declare获取的方法 (属性)的时候 要设置一下访问权限。setAccessible(true)。