高新技术 反射
一、Class类
Class类:Java程序中的各个Java类属于同一类事物,描述这类事物的Java类就是Class。
如何获得对象的字节码?
1.对象.getClass();。
2.类名.class();。
3.Class.fotNamre("类名");。
例:
package cn.itcast.day1;
public class ReflectTest {
public static void main(String[] args) throws Exception {
String s = "abcd";
Class c1 = s.getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");//会抛异常,因为可能不存在。
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c2 == c3);
}
}
解析:说明他们用的都是同一份字节码文件。
package cn.itcast.day1;
public class ReflectTest {
public static void main(String[] args) throws Exception {
String s = "abcd";
Class c1 = s.getClass();
Class c2 = String.class;
Class c3 = Class.forName("java.lang.String");
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c2 == c3);
System.out.println("是否是原始类型:"+c1.isPrimitive());//是否是元素类型(即基本数据类型)。
System.out.println("是否是原始类型:"+int.class.isPrimitive());
System.out.println("是否相同:"+(int.class == Integer.class));//判断int和Integer的字节码是否相同。
System.out.println("是否相同:"+(int.class == Integer.TYPE));//TYPE代表所包装的基本数据类型。
System.out.println("是否是原始类型:"+(int[].class.isPrimitive()));//判断数组类型是否是原始类型。
System.out.println("是否是数组类型:"+(int.class.isArray()));//判断是否是数组类型。
}
}
注:只要是在源程序中出现的类型,都有各自的Class实例对象,例如int[],void。
二、反射
神马是反射?
反射:就是把Java类中的各种成分映射成相应的类。例如,一个Java中用一个Class类的对象来表示。一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个Java类来表示。表示Java类的Class类显然要提供一系列的方法,来获取其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的实例对象来表示。它们是Field,Method,Constructor,Package等。
Constructor类:代表某个类中的一个构造方法。
Constructor<T> getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<?>[] getConstructors()
返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。
T newInstance()
创建此 Class 对象所表示的类的一个新实例。
例:package cn.itcast.day1;
import java.lang.reflect.Constructor;
public class ConstructorTest {
public static void main(String[] args) throws Exception {
//调用方法时,要用到类型
Constructor constructor = String.class.getConstructor(StringBuffer.class);
//调用获得的方法时,要用到上面相同类型的实例对象。
String s = (String) constructor.newInstance(new StringBuffer("abcd"));
System.out.println(s.charAt(2));
}
}
Filed类:代表某个类中的一个成员变量。
Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field[] getFields()
返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
Field getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
Field[] getDeclaredFields()
返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。
例:
package cn.itcast.day1;
import java.lang.reflect.Field;
public class FieldTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ReflectPoint fc = new ReflectPoint(3, 5);
Class clazz = fc.getClass();
Field fieldX = clazz.getField("x");//获取x字段封装成Field。
System.out.println(fieldX.get(fc));//获取对象fc上的Field指定字段上的值。
Field fieldY = clazz.getField("y");
System.out.println(fieldY.get(fc));
}
}
抛出异常找不到变量x,因为x是私有变量。要想获取到想,就要使用getDeclaredField()方法。要访问到私有变量还必须使用setAccessible(true)方法暴力反射。
改进:
package cn.itcast.day1;
import java.lang.reflect.Field;
public class FieldTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ReflectPoint fc = new ReflectPoint(3, 5);
Class clazz = fc.getClass();
Field fieldX = clazz.getDeclaredField("x");//将变量x封装成Field,即使该变量是私有的
fieldX.setAccessible(true);//将该Field对象设置为暴力反射
System.out.println(fieldX.get(fc));
Field fieldY = clazz.getField("y");
System.out.println(fieldY.get(fc));
}
}
练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”。
package cn.itcast.day1;
import java.lang.reflect.Field;
public class Demo {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Test test = new Test("bb", "bccb", 20);
Class clazz = test.getClass();
Field[] fields = clazz.getDeclaredFields();//获取所有成员变量数组
for(Field field : fields){
field.setAccessible(true);//将field设置为暴力反射,访问私有变量
if(field.getType() == String.class){//将变量类型字节码和String的字节码比较,因为String字节码只有一份,比较的是两引用地址,所有用“==”连接
String oldstr = (String) field.get(test);//获取字符串
String newstr = oldstr.replaceAll("b", "a");
field.set(test, newstr);//将新字符串存覆盖旧字符串
}
}
System.out.println(test);
}
}
Method类:代表某个类中的一个成员方法。
Method getMethod(String name, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method[] getMethods()
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
Object invoke(Object obj, Object... args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
例:
package cn.itcast.day1;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws Exception {
String str = "abcd";
Method method_charat = str.getClass().getMethod("charAt", int.class);//获取类的方法
System.out.println(method_charat.invoke(str, 3));//将方法作用到对象上
}
}
练习:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
package cn.itcast.day1;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws Exception {
String className = args[0];
Class clazz = Class.forName(className);
Method methodmain = clazz.getMethod("main", String[].class);//获取main方法
methodmain.invoke(null, new String[]{});//调用main方法。
}
}
package cn.itcast.day1;
public class Test {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
抛出数组角标越界异常,原因是没有传入类名。
点击Run Demo的下拉菜单——>Run Configurations
转到Arguments——>输入cn.itcast.day1.Test——Apply——>Run
抛出错误的参数个数,因为JDK1.5为了兼容1.4版本,虽然传入的是一个字符串数组,但是虚拟机会将其拆包,因而变成多个参数,也就发生了错误的参数个数异常。为了解决这一问题,需要把字符串数组装入Object数组中,这样虚拟机就只拆掉Object数组,而保留字符串数组了。
改进:
package cn.itcast.day1;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws Exception {
String className = args[0];
Class clazz = Class.forName(className);
Method methodmain = clazz.getMethod("main", String[].class);//获取main方法
methodmain.invoke(null, new Object[]{new String[]{}});//调用main方法。
}
}
也可以将数组强转为Object。
package cn.itcast.day1;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws Exception {
String className = args[0];
Class clazz = Class.forName(className);
Method methodmain = clazz.getMethod("main", String[].class);//获取main方法
methodmain.invoke(null, (Object)new String[]{});//调用main方法。
}
}
告诉虚拟机传入的是一个Object,避免被拆包。
数组的反射:具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
例:
package cn.itcast.day1;
public class Demo {
public static void main(String[] args) throws Exception {
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
System.out.println(a1.getClass() == a2.getClass());
//System.out.println(a1.getClass() == a3.getClass());不相等,因为维数不同
//System.out.println(a1.getClass() == a4.getClass());不相等,因为类型不同
}
}
注:基本数据类型的一维数组可以被当作Object类型使用,不能当做Object[]类型使用;非基本数据类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
package cn.itcast.day1;
public class Demo {
public static void main(String[] args) throws Exception {
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[3];
System.out.println(a1.getClass() == a2.getClass());
//System.out.println(a1.getClass() == a3.getClass());//不相等,因为维数不同
//System.out.println(a1.getClass() == a4.getClass());//不相等,因为类型不同
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object obj1 = a1;//相当于把int[]数组封装成Object
Object obj2 = a4;//相当于把String[]数组封装成Object
//Object[] obj3 = a1;//相当于把int[]数组转换成Object[],这是不行的,因为基本数据类型的父类不是Object
Object[] obj4 = a3;//把a3看作是一个一维数组里面装着一维数组。
Object[] obj5 = a4;//相当于把String[]数组封装成Object[]数组。
}
}
Arrays.asList()方法处理int[]和String[]时的差异:
package cn.itcast.day1;
import java.util.Arrays;
public class Demo {
public static void main(String[] args) throws Exception {
int[] a1 = new int[]{1,2,3,4};
String[] a2 = new String[]{"a","b","c","e"};
int a = 6;
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a2));
}
}
解析:JDK1.5的asList(T...a),是用可变参数接收。JDK1.4则是用asList(Object[] a),是用Object数组接收。当String[]数组传入时,用的是1.4版本。而int[]传入时,Object[]数组不能接收。所以用1.5版本接收。这时可变参数是把int[]数组当作一个对象,而非一个数组。所以打印的就是地址了。
练习:用反射原理打印数组或非数组。
package cn.itcast.day1;
import java.lang.reflect.Array;
public class Demo {
public static void main(String[] args) throws Exception {
int[] a1 = new int[]{1,2,3,4};
String a2 = "abcd";
printArrys(a1);
printArrys(a2);
}
private static void printArrys(Object obj) {
// TODO Auto-generated method stub
Class clazz = obj.getClass();
if (clazz.isArray()) {
int length = Array.getLength(obj);
for(int i=0;i<length;i++){
System.out.println(Array.get(obj, i));
}
} else {
System.out.println(obj);
}
}
}
知识回顾:
ArrayList:底层是数组结果,元素可重复。比较元素的时候依赖于equals()方法。
HashSet:底层是哈希表结构,元素不可重复。比较元素的时候,先比较hashCode(),如果相同再比较equals()方法。
内存泄露:
例:
package cn.itcast.day1;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
public class Demo {
public static void main(String[] args) throws Exception {
Collection hs = new HashSet();
Test t1 = new Test(3, 3);
Test t2 = new Test(5, 5);
Test t3 = new Test(3, 3);
hs.add(t1);
hs.add(t2);
hs.add(t3);
System.out.println(hs.size());
t1.x=8;
System.out.println(hs.remove(t1));
System.out.println(hs.size());
}
}
package cn.itcast.day1;
public class Test {
public int x;
public int y;
public Test(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Test other = (Test) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
注:当一个对象被存储进HashCode集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了。在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。
练习:小型框架
package cn.itcast.day1;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Properties;
public class Demo {
public static void main(String[] args) throws Exception {
//读取配置文件
Properties prop = new Properties();
InputStream in = new FileInputStream("cn/itcast/day1/config.properties");
prop.load(in);
//关闭资源,防止内存泄露
in.close();
Class clazz = Class.forName(prop.getProperty("classname"));
Constructor constructor = clazz.getConstructor(null);
Collection coll = (Collection) constructor.newInstance(null);
Method method = clazz.getMethod(prop.getProperty("addfun"), Object.class);
method.invoke(coll, "a");
method.invoke(coll, "b");
method.invoke(coll, "c");
method.invoke(coll, "d");
System.out.println(coll.size());
}
}
用类加载器的方式管理资源和配置文件:
例:
package cn.itcast.day1;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Properties;
public class Demo {
public static void main(String[] args) throws Exception {
//读取配置文件
Properties prop = new Properties();
InputStream in = Demo.class.getResourceAsStream("config.properties");
prop.load(in);
//关闭资源,防止内存泄露
in.close();
Class clazz = Class.forName(prop.getProperty("classname"));
Constructor constructor = clazz.getConstructor(null);
Collection coll = (Collection) constructor.newInstance(null);
Method method = clazz.getMethod(prop.getProperty("addfun"), Object.class);
method.invoke(coll, "a");
method.invoke(coll, "b");
method.invoke(coll, "c");
method.invoke(coll, "d");
System.out.println(coll.size());
}
}
因为配置文件要和.class文件一同打成jar包,所以把配置文件放到classpath目录下,用类加载器加载资源和配置文件。