Java反射
反射技术
-
其实就是动态加载一个指定的类,并获取该类中的所有的内容。
-
而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,它允许程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。
-
简单说:反射技术可以对一个类进行解剖。
反射的好处:大大的增强了程序的扩展性。
反射的基本步骤:
-
获得Class对象,就是获取到指定的名称的字节码文件对象。
-
实例化对象,获得类的属性、方法或构造函数。
-
访问属性、调用方法、调用构造函数创建对象。
类类型 class Class
用于描述程序中的各个类属于同一类事物的Java类,它封装了类的很多信息。
查看JDK中的源码:
public final class Class<T> implements java.io.Serializable, java.lang.reflect.GenericDeclaration, java.lang.reflect.Type, java.lang.reflect.AnnotatedElement { private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000; private static native void registerNatives(); static { registerNatives(); } /* * Constructor. Only the Java Virtual Machine creates Class * objects. */ private Class() {}
发现:Class类有构造器,并且它的构造方法是private的(可能是为了禁止开发者去自己创建Class类的实例)。
看到注释我们知道,这个类是JVM来创建的。如果我们拿到一个类的类型信息,就可以利用反射获取其各种成员以及方法了。
那么我们怎么拿到一个类的信息呢?【获取Class对象可以有3种方式】
如果没有对象实例的时候,主要有两种办法可以获取类类型:
Class cls1 = Show.class;//每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。 Class cls2 = Class.forName("Show");//【推荐这种方法】这种方式的扩展性最强,只要将类名的字符串传入即可。如果类是在某个包中,要带上包名。
public static void main(String[] args) throws Exception { Class<Show> cls1 = Show.class; Object obj1 = cls1.newInstance(); System.out.println(obj1); Class<?> cls2 = Class.forName("Show"); Object obj2 = cls2.newInstance(); System.out.println(obj2); }
这样就创建了一个对象,缺点是:
第一,我们只能利用默认构造函数,因为Class的newInstance是不接受参数的;
第二,如果类的构造函数是private的,比如Class,我们仍旧不能实例化其对象。
如果有对象实例的话,除了上面的两种方法来获取类的信息外,还有第三种方法:对象.getClass()。弊端:必须要创建该类对象,才可以调用getClass方法。
反射就是把Java类中的各种成分映射成相应的Java类:
例如,一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示【就像汽车是一个类,汽车中的发动机,变速箱也是一个个的类】。表示Java类的Class类中提供了一系列的方法来获取其中的变量(Field),方法(Method),构造方法(Contructor),修饰符(Modifiers),包(Package)等信息。
反射的用法
1.需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
- Class.forName(classname) 用于做类加载
- obj.getClass() 用于获得对象的类型
- 类名.class 用于获得指定的类型,传参用
2.反射类的成员方法:
- public Method getMethod(String name,Class<?>... parameterTypes)//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
- public Method[] getMethods()//返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口的公共 member 方法。
3.反射类的构造函数:
- public Constructor<?>[] getConstructors() 返回类中所有的public构造器集合,默认构造器的下标为0
- public Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定public构造器,参数为构造器参数类型集合
- public Constructor<?>[] getDeclaredConstructors() 返回类中所有的构造器,包括私有
- public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回任意指定的构造器
发现带上Declared的都是获得所有的构造方法,包括私有,这下我们就可以调用原本不允许调用的私有构造器了^_^
eg: Class cls1 = Show.class; // 获取所有的构造方法集合 Constructor[] con1 = cls1.getDeclaredConstructors(); con1[1].setAccessible(true);// 设置可访问的权限 Object obj1 = con1[1].newInstance(new Object[] { "adanac" }); System.out.println(obj1); // 指定参数列表获取特定的方法 Constructor con = cls1.getDeclaredConstructor(new Class[] { String.class }); con.setAccessible(true); Object obj2 = con.newInstance(new Object[] { "lfz" }); System.out.println(obj2);
4.获取类的成员变量:
- public Field getDeclaredField(String name) 获取任意指定名字的成员
- public Field[] getDeclaredFields() 获取所有的成员变量
- public Field getField(String name) 获取任意public成员变量
- public Field[] getFields() 获取所有的public成员变量
Class cls1 = Show.class; // 获取所有的构造方法集合 Constructor[] con1 = cls1.getDeclaredConstructors(); con1[1].setAccessible(true);// 设置可访问的权限 Object obj1 = con1[1].newInstance(new Object[] { "adanac" }); Field nameField = cls1.getDeclaredField("name"); nameField.setAccessible(true); System.out.println(nameField.get(obj1));// 打印结果:adanac
现在私有变量也可以访问到了哈~~
获取了字节码文件对象后,最终都需要创建指定类的对象,创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):
- 调用空参数的构造函数:使用了Class类中的newInstance()方法。
- 调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
注意:第二种方式必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。
当指定类中没有空参数构造函数时,该如何创建该类对象呢?
Class<?> clazz = Class.forName("heimablog.Person"); // 既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。 // 获取一个带参数的构造器。 Constructor<?> constructor = clazz.getConstructor(String.class, int.class); // 想要对对象进行初始化,使用构造器的方法newInstance(); Object obj = constructor.newInstance("zhagnsan", 30); // 获取所有构造器。 Constructor[] constructors = clazz.getConstructors();// 只包含公共的 constructors = clazz.getDeclaredConstructors();// 包含私有的 for (Constructor<?> con : constructors) { System.out.println(con); }
【案例】
通过class取得一个类的全部框架:
package heimablog; import java.lang.reflect.Field; import java.lang.reflect.Modifier; class hello { public static void main(String[] args) { Class<?> demo = null; try { demo = Class.forName("heimablog.Person"); } catch (Exception e) { e.printStackTrace(); } System.out.println("===============本类属性========================"); // 取得本类的全部属性 Field[] field = demo.getDeclaredFields(); for (int i = 0; i < field.length; i++) { // 权限修饰符 int mo = field[i].getModifiers();//返回此类或接口以整数编码的 Java 语言修饰符。 String priv = Modifier.toString(mo); // 属性类型 Class<?> type = field[i].getType(); System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";"); } System.out.println("===============实现的接口或者父类的属性========================"); // 取得实现的接口或者父类的属性 Field[] filed1 = demo.getFields(); for (int j = 0; j < filed1.length; j++) { // 权限修饰符 int mo = filed1[j].getModifiers(); String priv = Modifier.toString(mo); // 属性类型 Class<?> type = filed1[j].getType(); System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";"); } } }
通过反射操作属性:
package heimablog; import java.lang.reflect.Field; class hello { public static void main(String[] args) throws Exception { Class<?> cls1 = Class.forName("heimablog.Person"); Object object = cls1.newInstance(); Field field = cls1.getDeclaredField("name"); field.setAccessible(true); field.set(object, "男"); System.out.println(field.get(object));//打印结果:男 } }
通过反射取得并修改数组的信息:
package heimablog; import java.lang.reflect.Array; class hello { public static void main(String[] args) throws Exception { int[] temp={1,2,3,4,5}; Class<?>demo=temp.getClass().getComponentType(); System.out.println("数组类型: "+demo.getName()); System.out.println("数组长度 "+Array.getLength(temp)); System.out.println("数组的第一个元素: "+Array.get(temp, 0)); Array.set(temp, 0, 100); System.out.println("修改之后数组第一个元素为: "+Array.get(temp, 0)); } } /** * 数组类型: int *数组长度 5 *数组的第一个元素: 1 *修改之后数组第一个元素为: 100 */
通过反射修改数组大小:
package heimablog; import java.lang.reflect.Array; class hello{ public static void main(String[] args) { int[] temp={1,2,3,4,5,6,7,8,9}; int[] newTemp=(int[])arrayInc(temp,12); print(newTemp); System.out.println("^^^^^"); String[] atr={"a","b","c"}; String[] str1=(String[])arrayInc(atr,5); print(str1); } /** * 修改数组大小 * */ public static Object arrayInc(Object obj,int len){ Class<?>arr=obj.getClass().getComponentType(); Object newArr=Array.newInstance(arr, len); int co=Array.getLength(obj); System.arraycopy(obj, 0, newArr, 0, co); return newArr; } /** * 打印 * */ public static void print(Object obj){ Class<?>c=obj.getClass(); if(!c.isArray()){ return; } System.out.println("数组长度为: "+Array.getLength(obj)); for (int i = 0; i < Array.getLength(obj); i++) { System.out.print(Array.get(obj, i)+" "); } } } /** *数组长度为: 10 *1 2 3 4 5 6 7 8 9 0 ^^^^^ *数组长度为: 5 *a b c null null */
获得类加载器:
package heimablog; class test{ } class hello{ public static void main(String[] args) { test t=new test(); System.out.println("类加载器 "+t.getClass().getClassLoader().getClass().getName()); } } //类加载器 sun.misc.Launcher$AppClassLoader
反射小例子:
package heimablog; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.sql.Timestamp; import java.util.Calendar; public class ClassTest { public static void main(String[] args) { Calendar birthday = Calendar.getInstance(); birthday.set(1966, 9, 11, 0, 0, 0); Student s1 = new Student("007", "Jack Brawer", true, new Timestamp( birthday.getTimeInMillis()), 1.80); ClassInfo classInfo = new ClassInfo(); System.out.println(classInfo.getClassInfo(s1)); } } // 该类可以获取任何类的元数据信息 class ClassInfo { public String getClassInfo(Object obj) { StringBuffer result = new StringBuffer(); // 得到参数类变量的Class引用变量 Class cls = obj.getClass(); // 得到参数类变量的属性信息 Field[] fields = cls.getDeclaredFields(); // 得到参数类变量的完整类名(含有包的名称) String fullName = cls.getName(); // 得到去除包名称的类名 String className = fullName.substring(fullName.lastIndexOf('.') + 1); // 如果有包的定义,可以得到包的名称 int packagePosition = fullName.lastIndexOf('.'); String packageName = null; if (packagePosition < 0) packageName = ""; else packageName = fullName.substring(0, fullName.lastIndexOf('.')); // 输出包名和类名 result.append("包的名称为:" + packageName + "\r\n"); result.append("类的名称为:" + className + "\r\n"); // 输出类中所有的属性信息 for (Field field : fields) { // 允许访问私有成员 field.setAccessible(true); try { // 输出私有属性信息 if (field.getModifiers() == Modifier.PRIVATE) result.append("私有属性" + field.getName() + ":值为" + field.get(obj) + "\n"); // 输出受保护属性信息 if (field.getModifiers() == Modifier.PROTECTED) result.append("受保护属性" + field.getName() + ":值为" + field.get(obj) + "\n"); } catch (Exception e) { System.out.println(e.getMessage()); } } return result.toString(); } } // 学生类 class Student { // 学号 private String number = null; // 姓名 private String name = null; // 性别 private boolean sex = false; // 生日 private Timestamp birthday = null; // 身高 private double height = 0; // 默认构造函数 public Student() { } // 该构造函数可以对学生的属性进行初始化 public Student(String number, String name, boolean sex, Timestamp birthday, double height) { setNumber(number); setName(name); setSex(sex); setBirthday(birthday); setHeight(height); } // 学号的读取函数 public String getNumber() { return number; } // 学号的设置函数 public void setNumber(String number) { this.number = number; } // 姓名的读取函数 public String getName() { return name; } // 姓名的设置函数 public void setName(String name) { this.name = name; } // 性别的读取函数 public boolean isSex() { return sex; } // 性别的设置函数 public void setSex(boolean sex) { this.sex = sex; } // 生日的读取函数 public Timestamp getBirthday() { return birthday; } // 生日的设置函数 public void setBirthday(Timestamp birthday) { this.birthday = birthday; } // 身高的读取函数 public double getHeight() { return height; } // 身高的设置函数 public void setHeight(double height) { this.height = height; } // 格式化字符信息输出 public String toString() { return "学号:" + number + "/n姓名:" + name + "/n是否为男生:" + sex + "/n生日:" + birthday + "/n身高:" + height; } } /* 包的名称为:heimablog 类的名称为:Student 私有属性number:值为007 私有属性name:值为Jack Brawer 私有属性sex:值为true 私有属性birthday:值为1966-10-11 00:00:00.267 私有属性height:值为1.8 */
在java中有三种类类加载器:
-
Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。
-
Extension ClassLoader 用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类
-
AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。