当前位置: 代码迷 >> 综合 >> Java中关于反射的基础详解。
  详细解决方案

Java中关于反射的基础详解。

热度:45   发布时间:2023-10-22 23:29:59.0

本文是根据自己最近学习反射所做的总结,因为本身学的不深入,所以就做一些基础的总结,尽量详细。

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="清华大学";@Overridepublic String toString() {return "User [name=" + name + ", age=" + age + "]";}}
那么通过以上总结。利用反射获取构造方法,调用构造方法;

获取方法,调用方法;获取静态方法,调用静态方法;获取属性,操作属性。

我们可以发现,其实就是那几步。

第一步  获取字节码文件

第二步  获取要获取的方法(构造方法  属性)

第三步 调用

中间要注意的就是  带不带Declare。  带Declare就是与权限无关的。  不带就是只能获取public修饰的,包括继承过来的。

调用Declare获取的方法 (属性)的时候  要设置一下访问权限。setAccessible(true)。