Class
反射机制
程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性;
Class对象的获取
package Reflect; class Demo{ } public static void main(String[] args) { Class<?> demo1=null; Class<?> demo2=null; Class<?> demo3=null; try{ demo1=Class.forName("Reflect.Demo"); }catch(Exception e){ e.printStackTrace(); } demo2=new Demo().getClass(); demo3=Demo.class; }
通过Class实例化类对象
通过无参构造实例化对象
package Reflect; class Person{ public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString(){ return "["+this.name+" "+this.age+"]"; } private String name; private int age; } public static void main(String[] args) { Class<?> demo=null; try{ demo=Class.forName("Reflect.Person"); }catch (Exception e) { e.printStackTrace(); } Person per=null; try { per=(Person)demo.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } per.setName("Rollen"); per.setAge(20); System.out.println(per); }
但是注意一下,当我们把Person中的默认的无参构造函数取消的时候,比如自己定义只定义一个有参数的构造函数之后,会出现错误:
java.lang.InstantiationException: Reflect.Person
所以大家以后再编写使用Class实例化其他类的对象的时候,一定要自己定义无参的构造函数
通过Class调用其他类中的构造函数 (也可以通过这种方式通过Class创建其他类的对象)
public static void main(String[] args) { Class<?> demo=null; try{ demo=Class.forName("Reflect.Person"); }catch (Exception e) { e.printStackTrace(); } Person per1=null,per2=null,per3=null,per4=null; //取得全部的构造函数 Constructor<?> cons[]=demo.getConstructors(); try{ per1=(Person)cons[0].newInstance(); per2=(Person)cons[1].newInstance("Rollen"); per3=(Person)cons[2].newInstance(20); per4=(Person)cons[3].newInstance("Rollen",20); }catch(Exception e){ e.printStackTrace(); } }
public static void main(String[] args) { Class<?> demo=null; try{ demo=Class.forName("Reflect.Person"); }catch (Exception e) { e.printStackTrace(); } //所有实现的接口 Class<?> intes[]=demo.getInterfaces(); for (int i = 0; i < intes.length; i++) { System.out.println("实现的接口 "+intes[i].getName()); } //获取父类 Class<?> temp=demo.getSuperclass(); System.out.println("继承的父类为: "+temp.getName()); }
获取类中的所有方法信息
public static void main(String[] args) { Class<?> demo=null; try{ demo=Class.forName("Reflect.Person"); }catch (Exception e) { e.printStackTrace(); } Method method[]=demo.getMethods(); for(int i=0;i<method.length;++i){ Class<?> returnType=method[i].getReturnType(); Class<?> para[]=method[i].getParameterTypes(); int temp=method[i].getModifiers(); System.out.print(Modifier.toString(temp)+" "); System.out.print(returnType.getName()+" "); System.out.print(method[i].getName()+" "); System.out.print("("); for(int j=0;j<para.length;++j){ System.out.print(para[j].getName()+" "+"arg"+j); if(j<para.length-1){ System.out.print(","); } } Class<?> exce[]=method[i].getExceptionTypes(); if(exce.length>0){ System.out.print(") throws "); for(int k=0;k<exce.length;++k){ System.out.print(exce[k].getName()+" "); if(k<exce.length-1){ System.out.print(","); } } }else{ System.out.print(")"); } System.out.println(); } }
【运行结果】:
public java.lang.String getSex ()
public void setSex (java.lang.String arg0)
public void sayHello (java.lang.String arg0,int arg1)
public final native void wait (long arg0) throws java.lang.InterruptedException
public final void wait () throws java.lang.InterruptedException
public final void wait (long arg0,int arg1) throws java.lang.InterruptedException
public boolean equals (java.lang.Object arg0)
public java.lang.String toString ()
public native int hashCode ()
public final native java.lang.Class getClass ()
public final native void notify ()
public final native void notifyAll ()
获取类的全部成员变量信息
public static void main(String[] args) { Class<?> demo = null; try { demo = Class.forName("Reflect.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(); 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() + ";"); } }
【运行结果】:
===============本类属性========================
private java.lang.String sex;
===============实现的接口或者父类的属性========================
public static final java.lang.String name;
public static final int age;
反射调用指定方法
public static void main(String[] args) { Class<?> demo = null; try { demo = Class.forName("Reflect.Person"); } catch (Exception e) { e.printStackTrace(); } try{ //调用Person类中的sayChina方法 Method method=demo.getMethod("sayChina"); method.invoke(demo.newInstance()); //调用Person的sayHello方法 method=demo.getMethod("sayHello", String.class,int.class); method.invoke(demo.newInstance(),"Rollen",20); }catch (Exception e) { e.printStackTrace(); } }
反射调用类的set和get方法
public static void main(String[] args) { Class<?> demo = null; Object obj=null; try { demo = Class.forName("Reflect.Person"); } catch (Exception e) { e.printStackTrace(); } try{ obj=demo.newInstance(); }catch (Exception e) { e.printStackTrace(); } setter(obj,"Sex","男",String.class); getter(obj,"Sex"); } public static void getter(Object obj, String att) { try { Method method = obj.getClass().getMethod("get" + att); System.out.println(method.invoke(obj)); } catch (Exception e) { e.printStackTrace(); } } public static void setter(Object obj, String att, Object value, Class<?> type) { try { Method method = obj.getClass().getMethod("set" + att, type); method.invoke(obj, value); } catch (Exception e) { e.printStackTrace(); } }
通过反射修改属性值
public static void main(String[] args) throws Exception { Class<?> demo = null; Object obj = null; demo = Class.forName("Reflect.Person"); obj = demo.newInstance(); Field field = demo.getDeclaredField("sex"); field.setAccessible(true); field.set(obj, "男"); System.out.println(field.get(obj)); }
通过反射取得并修改数组的信息:
public static void main(String[] args) { int[] temp={1,2,3,4,5,6,7,8,9}; int[] newTemp=(int[])arraySet(temp,15); print(newTemp); System.out.println("====================="); String[] atr={"a","b","c"}; String[] str1=(String[])arraySet(atr,8); print(str1); } /** * 修改数组大小 * */ public static Object arraySet(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)+" "); } }
数组长度为: 15
1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 =====================
数组长度为: 8
a b c null null null null null
类的生命周期
在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM。在程序执行中JVM通过装载,链接,初始化这3个步骤完成。
类的装载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。 但是同一个类只会被加载一次,类装载器装载以前就是把二进制数据组装为可以运行的状态。
链接分为校验,准备,解析这3个阶段:
校验:一般用来确认此二进制文件是否适合当前的JVM(版本),
准备:就是为静态成员分配内存空间,。并设置默认值
解析:指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)完成之后,类型也就完成了初始化
初始化:Java虚拟机规范严格规定了有且只有以下情况必须立即对类进行初始化:
1.遇到new、获取静态变量(final常量除外)、为静态变量赋值以及调用静态方法时,如果类没有进行过初始化,则需要先触发其初始化。
2.使用java.lang.reflect包的方法对类进行反射调用的时候(Class.forName(…)),如果类还没有初始化,需要先触发对其的初始化。
3.当初始化一个类的时候,如果发现其父类还没有初始化,则需要先触发对其父类的初始化。
初始化的过程就是一个执行类构造器<clint>方法的过程,类构造器执行的特点:
a,类构造器<clint>方法是由编译器自动收集类中所有类变量(静态非final变量)赋值动作和静态初始化块(static{……})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定。静态初始化块中只能访问到定义在它之前的类变量,定义在它之后的类变量,在前面的静态初始化中可以赋值,但是不能访问。
b.类构造器<clint>方法与实例构造器<init>方法不同,它不需要显式地调用父类构造器方法,虚拟机会保证在调用子类构造器方法之前,父类的构造器<clinit>方法已经执行完毕。
d.由于父类构造器<clint>方法先与子类构造器执行,因此父类中定义的静态初始化块要先于子类的类变量赋值操作。
e. 类构造器<clint>方法对于类和接口并不是必须的,如果一个类中没有静态初始化块,也没有类变量赋值操作,则编译器可以不为该类生成类构造器<clint>方法。
f.接口中不能使用静态初始化块,但可以有类变量赋值操作,因此接口与类一样都可以生成类构造器<clint>方法。
接口与类不同的是:
首先,执行接口的类构造器<clint>方法时不需要先执行父接口的类构造器<clint>方法,只有当父接口中定义的静态变量被使用时,父接口才会被初始化。
其次,接口的实现类在初始化时同样不会执行接口的类构造器<clint>方法。
g.java虚拟机会保证一个类的<clint>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的<clint>方法,其他线程都需要阻塞等待,直到活动线程执行<clint>方法完毕。
初始化阶段,当执行完类构造器<clint>方法之后,才会执行实例构造器的<init>方法,实例构造方法同样是按照先父类,后子类,先成员变量,后实例构造方法的顺序执行。
使用:执行Class的业务逻辑指令,通过堆中java.lang.Class对象的入口地址,调用方法区的方法逻辑,最后将方法的运算结果通过方法返回地址存放到方法区或堆中
卸载:当没有任何引用指向Class对象时就会被卸载,将被垃圾回收。释放空间,结束类的生命周期。
一段说明代码:
class A{ static int a;//类变量 String name; int id; //静态代码块 static{ a=10; System.out.println("这是父类的静态代码块"+a); } //构造代码块 { id=11; System.out.println("这是父类的构造代码块id:"+id); } A(){ System.out.println("这是父类的无参构造函数"); } A(String name){ System.out.println("这是父类的name"+name); } } class B extends A{ String name; static int b; static{ b=12; System.out.println("这是子类的静态代码块"+b); } B(String name) { super(); this.name = name; System.out.println("这是子类的name:"+name); } } public class Test666 { public static void main(String[] args) { B bb=new B("GG"); } }
输出的结果:
这是父类的静态代码块10
这是子类的静态代码块12
这是父类的构造代码块id:11
这是父类的无参构造函数
这是子类的name:GG
2017/03/31更新:
使用场景:替换指定对象属性
public static void changeFile(Object model) throws NoSuchFieldException,IllegalAccessException{ Field[] field = model.getClass().getDeclaredFields(); for(int j=0 ; j<field.length ; j++) { String name = field[j].getName(); // name = name.substring(0,1).toUpperCase()+name.substring(1); //将属性的首字符大写,方便构造get,set方法 String type = field[j].getGenericType().toString(); //获取属性的类型 if (type.equals("class java.lang.String")) { //如果type是类类型,则前面包含"class ",后面跟类名 Field f = field[j];//model.getClass().getDeclaredField(name); f.set(model, handleReplace(f.get(model).toString())); } } }
修改static class filed:
public static void changeAPI() throws NoSuchFieldException,IllegalAccessException{ APP_DOWNLOAD_ADDRESS = handleReplaceUrl(APP_DOWNLOAD_ADDRESS); HOME_URL = handleReplaceUrl(HOME_URL); Field[] field = Api.class.getDeclaredFields(); for(int j=0 ; j<field.length ; j++) { // String name = field[j].getName(); // name = name.substring(0,1).toUpperCase()+name.substring(1); //将属性的首字符大写,方便构造get,set方法 String type = field[j].getGenericType().toString();//获取属性的类型 if (type.equals("class java.lang.String")) { //类型前面包含"class ",后面跟类全名 Field f = field[j];//model.getClass().getDeclaredField(name); /*去除final修饰符的影响,将字段设为可修改的*/ if(f.getModifiers() == Modifier.FINAL) { Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); } f.set(null, handleReplaceUrl(f.get(null).toString())); } } } private static String handleReplaceUrl(String url){ int spiltPos = url.indexOf("/api/"); String pUrl = url; if(spiltPos > 0){ pUrl = getAppServer() + url.substring(spiltPos); } return pUrl; }
备注:当filed为static final 修饰时,Integer,Long,基本类型包装类,及Object对象类型可以修改成功,然而int ,long,boolean,基本类型以及String时修改不能成功
——反射失效了吗?
对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值。
参考:Modifying final fields in Java
即对于常量 public static final int maxFormatRecordsIndex = 100 ,
代码
if( index > maxFormatRecordsIndex){
index = maxFormatRecordsIndex ;
}
这段代码在编译的时候已经被java自动优化成这样的:
if( index > 100){
index = 100;
}
所以,自然,无论怎么修改BmaxFormatRecordsIndex 都还是会依然固执地输出100了。