------- android培训、java培训、期待与您交流! ----------
反射理解
Java中的面向对象编程的基本思想是把现实世界中的实务抽象成一个个类来表示,而将这些有共同特性的事物的属性和行为封装成类的成员变量和成员方法。同样的,Java语言中的一个个Java类,我们也可以把它们当做一类相同事物,我们用Java语言中的一个类Class来对它们进行描述。
Class是Java类的抽象,而它的各个实例对象则是对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等。
Class
得到Class实例的三种方式
(1) 类名.class,例如,System.class
(2) 对象.getClass(),例如,new Date().getClass()
(3) Class.forName("类名"),例如,Class.forName("java.util.Date");
String s = new String("abc");
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(void.class.isPrimitive());
System.out.println(void.class == Void.class);
System.out.println(void.class == Void.TYPE);
System.out.println(int[].class.isPrimitive());
System.out.println(int[].class.isArray());
打印结果为:
true
true
true
false
true
false
true
可见一个Java类对应的Class实例只有一个,即在内存中只保留一个字节码序列。基本数据类型和封装类型的Class实例是不同的,一个是基本数据类型,一个是其包装类。而Void.TYPE返回的是其代表的基本数据类型对应的Class实例,故而void.class == Void.TYPE返回true。
九个预定义Class实例对象:
8个基本数据类型(int,char,long,float,double,short,byte,boolean)和void类型。可以使用Class.isPrimitive方法判断是否是预定义类型。判断是否是数组类型的Class实例对象Class.isArray()。总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
Constructor
Constructor类代表类中构造方法的抽象。
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();//得到该类的所有构造方法实例
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//根据参数类型,获得方法时要用到类型
//创建实例对象:
//String str = new String(new StringBuffer("abc"));//通常方式
String str = (String)constructor.newInstance(new StringBuffer("abc"));//反射方式
//调用获得的方法时要用到上面相同类型的实例对象
Constructor<String> cst = String.class.getConstructor(StringBuffer.class);
String s = cst.newInstance(new StringBuffer("abc"));//由于对Constructor应用了泛型,所以返回的是指定泛型的对象
System.out.println(s.charAt(2));
Class.newInstance()
方法:
例子:String obj = (String)Class.forName("java.lang.String").newInstance();该方法内部先得到默认的Constructor实例,然后用该实例创建实例对象。方法内部用到了缓存机制来保存默认构造方法的实例对象。
Field
Field类代表某个类中的一个成员变量
演示用eclipse自动生成Java类的构造方法
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
示例代码:
class Point
{
private int x;
int y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
}
对Point 类进行如下测试
Point point = new Point(3,7);
System.out.println(point);
Field fx = point.getClass().getDeclaredField("x");
Field fy = point.getClass().getField("y");//成员属性不是private类型
fx.setAccessible(true);//当类中的成员属性为private类型时,需设置可访问性为true,才能对其进行设置,否则会抛出异常
fx.setInt(point, 200);
fy.set(point, 300);
System.out.println(fx.get(point)+","+fy.getInt(point));
Method
Method类是类中的成员方法的抽象
得到类中的某一个方法:
String str = "fighting";
Method charAt = String.class.getMethod("charAt", int.class);
System.out.println(str.charAt(2));//通常方式
System.out.println(charAt.invoke(str, 2)); //反射方式
//如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!
Math.class.getMethod("abs", int.class).invoke(null,-25);
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})形式。
用反射方式执行某个类中的main方法
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
public class MethodTest {
public static void main(String[] args) throws Exception {
ArgTest.main(new String[]{"aaa","bbb","ccc"});//普通方法
//使用反射方式
Method mainMethod = ArgTest.class.getMethod("main", String[].class);
//尽管参数传递符合jdk1.5的要求,但运行会报出错误:java.lang.IllegalArgumentException: wrong number of arguments
mainMethod.invoke(null,(Object)new String[]{"aaa","bbb","ccc"});
mainMethod.invoke(null,(Object)new String[]{"aaa","bbb","ccc"});
mainMethod.invoke(null,new Object[]{new String[]{"aaa","bbb","ccc"}});
}
}
class ArgTest {
public static void main(String[] args) {
for(String arg:args){
System.out.println(arg);
}
}
ArgTest
中
main
方法使用反射方式调用时,需要传递一个字符串数组类型的参数。根据
jdk1.5
中对应的方法是:
method.invoke(Object,Object...)
参数是多参数类型,而
jdk1.4
中对应的方法是:
method.invoke(Objec,Object[])
。在实际运行过程中,jdk1.5会兼容
jdk1.4
的语法,会按
jdk1.4
的语法进行处理,即把数组(如上的String[]
数组)解析成Object
数组。所以,在给
main
方法传递参数时,不能使用代码
mainMethod.invoke(null,new String[]{
“
xxx
”
})
,
javac
只把它当作
jdk1.4
的语法进行理解,而不把它当作
jdk1.5
的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
数组的反射
int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[]{"aaa","bbb","ccc"};
System.out.println(a1.getClass() == a2.getClass());//true
System.out.println(a1.getClass() == a3.getClass());//编译报错
System.out.println(a1.getClass() == a4.getClass());//编译报错
System.out.println(a1.getClass().getName());//[I
System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
System.out.println(a1);//[I@18a992f
System.out.println(a4);//[Ljava.lang.String;@4f1d0d
System.out.println(Arrays.asList(a1));//[[I@18a992f]
System.out.println(Arrays.asList(a4));//[aaa, bbb, ccc]
具有相同维数和元素类型的数组属于同一个类型,即具有相同的
Class
实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
Array工具类用于完成对数组的反射操作。
public static void printObj(Object obj){
if(obj.getClass().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);
}
}
反射的作用->实现框架功能
张孝祥老师的经典比喻:做好房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
下面的例子使用配置文件配置存放Point元素的集合的类型对应的字符串,我们先通过Properties读取集合类名,然后用反射机制获得集合类的对象
Config.properties中
className=java.util.ArrayList
#className=java.util.ArrayList
下面是测试类
public class HashcodeTest {
public static void main(String[] args) throws Exception {
//通过配置文件以及反射机制获得相应的集合类
InputStream inStream = HashcodeTest.class.getClassLoader().getResourceAsStream("com/itheima/day2/config.properties");
Properties props = new Properties();
props.load(inStream);
String className = props.getProperty("className");
Collection<Point> collection = (Collection)Class.forName(className).newInstance();
Point p1 = new Point(2,2);
Point p2 = new Point(2,2);
Point p3 = new Point(3,3);
collection.add(p1);
collection.add(p2);
collection.add(p3);
collection.add(p1);
p1.setX(27);
collection.remove(p1);
System.out.println(collection.size());
}
}