1、通过反射API可以获取程序在运行时刻的内部结构。
使用反射API的时候就好像在看一个JAVA类在水中的倒影,可以知道JAVA类的内部结构,可以实现与它交互,包括创建新的对象和调用对象的方法等。
1)对构造方法的调用
//直接在源代码中使用String(StringBuffer buffer)构造方法
StringBuffer buffer = new StringBuffer("abc");
String str1 = new String(buffer);
//通过反射API调用String(StringBuffer buffer)构造方法
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = (String)constructor1.newInstance(new StringBuffer("abc"));
System.out.println(str2.charAt(2));
2)对属性的调用
ReflectPoint point1 = new ReflectPoint(3, 4);
Field fieldY = ReflectPoint.class.getField("y");
//field的值不是4,因为没有为其指定具体对象,field是代表ReflectPoint类的y字段的字节码
System.out.println(fieldY.get(point1));
Field fieldX = ReflectPoint.class.getDeclaredField("x");
fieldX.setAccessible(true);
System.out.println(fieldX.get(point1));
改变属性的值
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field field : fields){
//if(field.getType().equals(String.class))
//不用equals是因为字节码文件只会产生一个对象,用 == 比较
if(field.getType() == String.class){
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b', 'a');
field.set(obj, newValue);
}
}
}
3)对方法的调用
System.out.println(str1.charAt(1));
Method methodCharAt = String.class.getMethod("charAt", int.class);
System.out.println(methodCharAt.invoke(str1, 1));
//利用反射的方式得到字节码里面的方法,再用方法作用与某个对象
System.out.println(methodCharAt.invoke(null, 1));
//这个方法是静态的应写成null
总结:JAVA反射API可以获取程序在运行时刻的内部结构。可以遍历出来一个JAVA类的内部结构,包括构造方法、声明的域和定义的方法等。对应的方法分别getConstructor、getField和getMethod。这三个方法还有对应的getDeclaredXXX,区别在于getDeclaredXXX方法只会获取该类自身所声明的元素,而不会考虑继承下来的。Constructor、Field和Method这三个类分别表示类中的构造方法、域和方法。这些类中的方法可以获取到所对应结构的元数据。
得到类的字节码有三种方法
1 Person.class
2 new Date().getClass()
3 Class.forName("java.util.Data");//反射主要用第三种
2、对接收数组参数的成员方法进行反射时,
由于要兼容JKD1.5以前的版本,java虚拟机会把数组拆开,把数组当成一个包装参数的包,所以需要再用数组进行封装
如:
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
//TestArguments.main(new String[] { "111", "222", "333" });
// 一般的时候我们不知道要执行的类的名字,即TestArguments是不知道,可能是args里面传过来的字符串指明要执行的类的main方法,所以要用反射
String statringClassName = args[0];
Method mainMethod = Class.forName(statringClassName).getMethod("main", String[].class);
//mainMethod.invoke(null, new Object[]{ new String[] { "111", "222", "333" } });
//main方法是静态的,不需要对象,所以为null
//对于接收数组类型的成员方法的反射,需要将数组打包,因为要兼容JDK1.5之前的版本
mainMethod.invoke(null, (Object)new String[] { "111", "222", "333" });
//方法2,张老师解释为将String[]转换为Object告诉编译器我传的是一个对象,不是参数数组,不需要拆包,这种方式相对于方法1效率略高
}
}
class TestArguments {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
3、关于数组的反射
没有办法得到数组的类型
Object[] a = new Object[]{"a", 1};
a[0].getClass();
对象a中可能含有多种类型的元素,所以没有具体的类型。
public class ReflectTest3 {
public static void main(String[] args) {
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[] a3 = new int[3];
int[][] a4 = new int[2][3];
String[] s1 = new String[]{"aa","bb"};
Object obj1 = a1;
Object obj2 = a2;
Object obj3 = a3;
Object obj4 = a4;
Object obj5 = s1;
Object[] ob1 = a4;
Object[] ob2 = s1;
System.out.println(a1.getClass() == a2.getClass());
System.out.println(a1.getClass() == a3.getClass());
System.out.println(a1);
System.out.println(s1);
//基本数据类型的父类不是Object,所以不会被解析成list
//Object[] ob = a1; 同理这句报错
System.out.println(Arrays.asList(new Object[]{a1}));
System.out.println(Arrays.asList(s1));
printObject(a1);
}
private static void printObject(Object obj) {
Class cla = obj.getClass();
if(cla.isArray()){
int len = Array.getLength(obj);
for(int i = 0; i < len; i ++){
System.out.println(Array.get(obj, i));
}
}
else{
System.out.println(obj);
}
}
}
4、ArrayList和HashSet
HashSet是一种基于Hash表的集合,
向其集合添加元素的过程为:每一个元素被存之前,根据哈希算法对元素存储的物理地址,转换成对应的哈希值。
Collection collection = (Collection)Class.forName(className).newInstance();
ReflectPoint p1 = new ReflectPoint(1, 2) ;
ReflectPoint p2 = new ReflectPoint(1, 2) ;
ReflectPoint p3 = new ReflectPoint(2, 2) ;
collection.add(p1);
collection.add(p2);
collection.add(p3);
collection.add(p1);
System.out.println(collection.size());
当一个对象被存储进HashSet集合中以后,就不要修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了。这时候,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的效果。这会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄漏。
Collection collection = (Collection)Class.forName(className).newInstance();
ReflectPoint p1 = new ReflectPoint(1, 2) ;
ReflectPoint p2 = new ReflectPoint(1, 2) ;
ReflectPoint p3 = new ReflectPoint(2, 2) ;
collection.add(p1);
collection.add(p2);
collection.add(p3);
collection.add(p1);
//p1.y = 4;
collection.remove(p1);
使用别人写的类有两种方式
1、你调用别人的类
2、别人调用你的类(Struts调用你的类)
5、关于框架
框架要解决的核心问题
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做。
public class ReflectTest {
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
//InputStream ips = new FileInputStream("src/com/itheima/day3/config.properties");
//类加载器加载
//InputStream ips = ReflectTest.class.getClassLoader().getResourceAsStream("com/itheima/day3/config.properties");
InputStream ips = ReflectTest.class.getResourceAsStream("config.properties");
Properties properties = new Properties();
properties.load(ips);
ips.close();//关闭掉操作系统所占用的资源内存
String className = properties.getProperty("className");
Collection collection = (Collection)Class.forName(className).newInstance();
ReflectPoint p1 = new ReflectPoint(1, 2) ;
ReflectPoint p2 = new ReflectPoint(1, 2) ;
ReflectPoint p3 = new ReflectPoint(2, 2) ;
collection.add(p1);
collection.add(p2);
collection.add(p3);
collection.add(p1);
//p1.y = 4;
collection.remove(p1);
System.out.println(collection.size());
}
}
框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
你做的门调用锁,锁是工具;
你做的门被房子调用,房子是框架。
房子和锁都是别人提供的。