——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
注:视频来源,张孝祥老师的 JAVA 高薪技术。
反射的定义:反射就是把Java类中的各种成分映射成相应的java类。
例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
一、Class.forName()
Class.forName() 的作用是用来返回字节码。
1)返回字节码的方式有两种:
1、这份字节码曾经被加载过,已经存在于 java 虚拟机中。
2、虚拟机中还没有字节码,需要使用类加载器加载,把加载的字节码放在 java 虚拟机中。
2)获取字节码对应的实例对象( Class类型),获取字节码的三种方式
1、类名.class,例如,System.class。虚拟中已经存在该类的字节码,只有用类名.class 就可以得到。
2、对象.getClass()。例如,new Date().getClass()。用这个方式去获取一个类的字节码。
3、Class.forName(“类名”)。例如,Class.forName(“java.util.Date”);主要用于反射。
3)一个奇怪的问题:加载了字节码,并调用了其getMethods之类的方法,但是没有看到类的静态代码块被执行,只有在第一个实例对象被创建时,这个静态代码才会被执行。准确的说,静态代码块不是在类加载时被调用的,而是第一个实例对象被创建时才执行的。
4)isPrimitive():判断是否是基本数据类型的字节码。不是,返回 false。是 ,返回 true。
5)字节码的使用注意:
1、int 的字节码不等于 Integer 的字节码 ,返回 false。
如:System.out.println(int.class == Integer.class);
2、int是 Integer 的包装类型中的,所以是同一份字节码,返回 true。
如:System.out.println(int.class == Integer.TYPE);
3、数组类型的Class 实例对象Class.isArray();
如:System.out.println(int[].class.isArray());
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void
二、Constructor类:构造器
1、 如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化。这时怎么办呢?这时就不能使用Class类中的newInstance方法了。既然要通过指定的构造函数进行对象的初始化。就必须先获取这个构造函数——Constructor。Constructor代表某个类的构造方法。
2、 得到某个类的所有构造方法,有如下两种写法,他们的作用是一样的。
1)得到这个类的所有构造方法:如得到上面示例中Person类的所有构造方法
Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();
2)获取某一个构造方法:
Constructor con=Person.class.getConstructor(String.class,int.class);
Demo:
package day26;
import java.lang.reflect.Constructor;
/**
*
*@author XiaLei
*/
public class Demo {
public static void main(String[] args) throws Exception{
String str=new String(new StringBuffer("ads"));
//通过获得带参数的构造函数创建对象。
Constructor<?> constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
String str1=(String)constructor.newInstance(new StringBuffer("ads"));
System.out.println(str);
//空参数构造函数创建对象
String str2=(String)Class.forName("java.lang.String").newInstance();
System.out.println(str2);
}
}
三、Field类
Field 类代表某个类中的一个成员变量
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,那么久会报错,java.lang.NoSuchFieldException: x
注意:
fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值。
想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible。这就是暴力反射,不管是否私有化,都能通过该方法获取。
code:
package day26;
import java.lang.reflect.Field;
/**
*
*@author XiaLei
*/
public class FieldDemo {
public static void main(String[] args)throws Exception {
ReflectPoint pt1=new ReflectPoint(3,5);
//得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,
//那么久会报错,java.lang.NoSuchFieldException: x
Field fieldX=pt1.getClass().getDeclaredField("x");//暴力反射,不管是否私有化,都能通过该方法获取
//那 fieldy 的值是多少呢? 5?错
//fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值
fieldX.setAccessible(true);
//想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible
System.out.println(fieldX.get(pt1));
Field fieldY=pt1.getClass().getField("y");
System.out.println(fieldY.get(pt1));
System.out.println(fieldX.getName());
changeStringValue(pt1);
System.out.println(pt1);
}
private static void changeStringValue(Object obj)throws Exception {
Field[] fields=obj.getClass().getFields();
for (Field field : fields){
if (field.getType()==String.class){//注意这里用== 而不用equals的区别:字节码(二进制数值)用 == 比较,不要用 equals 比较
String oldValue=(String)field.get(obj);//从对象身上拿到值。
String newValue=oldValue.replace('b', 'a');
field.set(obj, newValue);
}
}
}
}
class ReflectPoint {
public int y;
private int x;
public String str1="bascketball";
public String str2="baby";
public String str3="sdfa";
public ReflectPoint(int y, int x) {
super();
this.y = y;
this.x = x;
}
public String toString(){
return str1+":"+str2+":"+str3;
}
}
四、Method类
Method 类代表某个类中的一个成员方法。
1、得到类中的某一个方法:
Method charAt = Class.forName(“java.lang.String”).getMethod(“charAt”, int.class);
2、调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
3、jdk1.4和jdk1.5的invoke()方法的区别:
Jdk1.5:public Object invoke(Object obj,Object… args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
4、使用注意:
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法。
Class.getMethod(name,Class… args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。
package day26;
import java.lang.reflect.Method;
/**
*
*@author XiaLei
*/
public class MethodDemo {
public static void main(String[] args) throws Exception{
String str="abc";
Method charAtMethod=String.class.getMethod("charAt", int.class);
System.out.println(charAtMethod.invoke(str, 2));
String classStartingName = args[0];
Method mainMethod = Class.forName(classStartingName).getMethod("main", String[].class);
//方式一:强制转换为超类Object,不用拆包
mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
//方式二:将数组打包,编译器拆包后就是一个String[]类型的整体
// methodMain.invoke(null, new Object[]{new String[]{"111","222","333"}});
}
}
class MethodTestDemo{
public static void main(String[] args) {
for (String str : args){
System.out.println(str);
}
}
}
五、ArrayReflect数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
2、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
3、Arrays.asList()方法处理int[]和String[]时的差异。
4、Array工具类用于完成对数组的反射操作。
Demo:
package day26;
import java.lang.reflect.Array;
/**
*
*@author XiaLei
*/
public class ArrayDemo {
public static void main(String[] args) {
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] s1 = new String[3];
System.out.println(a1==a2);
//相同数据类型的数组,当维数相同时,他们的字节码是相同的
System.out.println(a1.getClass()==a2.getClass());
//System.out.println(a1.getClass()==a3.getClass());
System.out.println(a1.getClass().getName()+"=="+a2.getClass().getName());
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a3.getClass().getSuperclass().getName());
Object o1 = a1;
Object o2 = a2;
Object o3 = a3;
Object o4 = s1;
Object[] o5 = a3;
Object[] o6 = s1;
System.out.println(o3);
System.out.println("-----------------------------");
printObject(a1);
//Object 数组的反射
Object[] oo = new Object[]{"a",1,'a'};
String str = oo[1].getClass().getName();
System.out.println(str);
}
public static void printObject(Object obj){
Class clazz = obj.getClass();
if(clazz.isArray()){
//获取array 的长度
int len = Array.getLength(obj);
//for 循环
for(int i = 0;i<len;i++){
System.out.println((i+1)+":"+Array.get(obj, i));
}
}else{
System.out.println("OBJ:"+obj);
}
}
}
六、CollectionReflect:集合反射
1、一句话总结:配置文件的加载往往是用类加载器的方式加载。以后学到的框架都是这么加的。
2、类加载器:用类加载器加载配置文件。
–>类.class.getClassLoader().getResourceAsStream
说明:就是在classPath 下逐一的去查找你要加载的那个文件。
3、扩展知识: eclipse 会把源目录(src)下的所有的 .java 文件,自动编译成 .class 你一保存它就编译了,然后存放到 classPath 指定的目录下去。同时,它也把所有的非 .java 文件,原封不动的再保存一份到 classPath 下,那么你就会发现,config.properties存在与两个不同的文件目录下。但是当你真正在获取配置文件的时候,它其实是在classPath下找的。
4、使用注意:创建一个文件读取流,读取配置文件中的字符流数据。一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。
Demo:
package day26;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;
/**
*
*@author XiaLei
*/
public class CollectionReflect {
public static void main(String[] args) throws Exception{
//创建一个文件读取流,读取配置文件中的字符流数据
//那么,读取properties 配置文件的路径怎么取呢?
//一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。
//InputStream ips = new FileInputStream("config.properties");
//在 classPath 根目录下逐一的找 config.properties 文件
//那么,如果把配置文件放在包内,那么就要加上。。
//直接获取配置文件资源,并未使用类加载器,这种方法:只需要写上配置文件名,不需要写上目录名
//InputStream ips = CollectionReflect.class.getResourceAsStream("config.properties");
//当把配置文件整合到一个 resources 资源包下,那么只需要在获取的路径上加上当前的包名(下级目录)
InputStream ips = CollectionReflect.class.getResourceAsStream("resources/config.properties");
//初始化一个配置文件对象properties 。
Properties prop = new Properties();
//加载文件读取流
prop.load(ips);
//马上关闭文件读取流,这是一个习惯。如果没有及时关闭,那么会造成内存泄漏
//并不是因为 ips 对象不被释放,而是这个对象关联的系统资源没有被释放。这是两个概念
//总结:释放资源
ips.close();
String className = prop.getProperty("className");
System.out.println("className的值是:"+className);
//newInstance():不带参数的构造方法
Collection cols = (Collection) Class.forName(className).newInstance();
//Collection cols = new HashSet(); //得到的集合长度是3
//Collection cols = new ArrayList(); //得到的集合长度是4
ReflectPoint pt1 = new ReflectPoint(3, 4);
ReflectPoint pt2 = new ReflectPoint(2, 5);
ReflectPoint pt3 = new ReflectPoint(3, 4);
//给集合添加了两次的pt1。
Cols.add(pt1);
Cols.add(pt1);
Cols.add(pt1);
//如果重新给pt1的变量 y 赋值,那么当 remove 了pt1后,就会造成 hashCode 的内存泄露的现象
//pt1.y = 7;
//cols.remove(pt1);
System.out.println(cols.size());
//某个对象,我不要了,但是却一直在用,没有被释放掉。这就是内存泄漏。
}
}