您的位置:首页 > 理论基础 > 计算机网络

传智播客 Java网络公开课二反射详解

2009-10-23 21:58 302 查看
枚举讲解完以后接着就是反射,理解了反射机制,以后学习框架内部实现的原理就会容易很多了。首先要明白的是Class这个类。例如学JAVA的时候,经常将人这个对象用Person类来描述,Person类中将会用变量和方法来描述这一类对象该有什么和不该有什么。同样的,java类本身也是一种对象,也需要表述一个java类中应该有什么和不应该有什么。这个描述java类的类叫做Class,它描述类中应该有方法,有成员变量,而具体的方法和成员变量的名称是什么,则是在具体的class实例对象中指定的。所以Class类对于类中属性的值是什么,它不管。只有Class类的对象才会根据类来确定各个属性的值。下面是张老师课程中的比喻:Person的实例对象是什么?张三这个人,李四这个人,同样的,Class实例对象是什么?Person这个类的字节码,Date、Vector等这些类的字节码就是Class这个类的实例对象。
下面的代码可以帮助进行对比理解:
Person p1 = new Person("zhangsan");
Person p2 = new Person("lisi");

Class x1 = Vector.class;//Vector类在内存里的字节码
Class x2 = Date.class;//Date类在内存里的字节码
从另一个角度来说,运行JAVA程序,被调用的类都要按一定规则被类加载器加载到内存,占用一片存储空间,这个空间里面的内容就是类的字节码,因此需要用一个个对象来表示这一片片的空间(字节码),这些对象具有相同的类型,这个类型就是上面说的Class这个类。下面代码片断是main方法中的一段:
Class x1 = Date.class;
Class x2 = new Date().getClass();
Class x3 = Class.forName("java.util.Date");
if(x1 == x2)
{
System.out.println(x1.getName());
}
if(x1 == x3)
{
System.out.println(x1.getSimpleName());
}
以上代码是通过三种不同的方式获得Class的实例,代码的运行结果显示都能打印类的名字,说明x1==x2==x3,即三个引用的都是同一个对象,换句话说就是同一份一份字节码。所以通常一个类在虚拟机中只有一份字节码,而且是在第一次用到该类的时候才被载入内存的。
有了以上知识,接下来就可以认识反射了。所谓反射,就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,修饰符,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应的类来表示的,它们是Field、Method、Contructor、Package等等。
一个类中的一个成员就是这些类的一个实例对象,得到这些实例对象后,能用它们做什么呢?首先是获取构造方法类的对象。在实际应用中,能够通过获取到的构造方法来创建一个该类的实例对象:
Class clazz = String.class;
Constructor constructor = clazz.getConstructor(StringBuffer.class);
String str = (String)constructor.newInstance(new StringBuffer("abc"));
System.out.println(str);
首先通过其中一种方式获得类的Class实例clazz,然后通过clazz获得构造方法,这里需要提到的是,虽然可以直接在clazz上调用newInstance方法,但是那是一种默认的行为,所以产生的对象执行默认构造方法,因此也要求类本身有默认的构造方法,否则这种不穿常数直接产生实例的调用就会报错。代码示例中在获取构造方法的同时传入了构造方法参数的类型,同样也是用参数所在类的Class对象表示的。
通过反射得到对象后,可以进一步访问对象的属性,调用对象的方法。下面代码片断就是访问属性和调用方法的示例:
代码一:
public class ReflectPoint {
public int x;
private int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
class TestArrayArguments
{
public static void main(String [] args)
{
for(String arg:args)
{
System.out.println("----------" + arg + "----------");
}
}
}
代码二:
Class clazzPoint = ReflectPoint.class;
Field fieldX = clazzPoint.getField("x");
fieldX.get(new ReflectPoint(3,5));

Method mMain = TestArrayArguments.class.getMethod("main", String[].class);
mMain.invoke(null,new Object[]{new String[]{"aaa","bbb"}});
mMain.invoke(null,(Object)new String[]{"aaa","bbb"});
代码一是为了测试属性访问和方法的调用的辅助类。通过Class对象的getField方法可以得到域对象,然后通过域对象的get方法,传入具体的对象引用,就能访问到相应对象的属性了。当然,如果属性是私有的,就要强制用setAccessible方法来进行设定了。通过反射调用方法也是采用这种模式,先通过Class对象得到方法对象,然后通过该对象调用invoke方法来完成调用相应对象的方法。所invoke方法就有两个参数,一个是对象引用,如果是静态方法,不需要对象就能执行,就可以传递一个null参数,接着就是被调用方法的参数。jdk1.5使用的是可变参数列表,但是jdk1.4则是一个Object数组。因此,当我们将一个数组作为参数传递给invoke方法时,按jdk1.4的语法,数组中的每个元素对应一个参数,按jdk1.5的语法,整个数组是一个参数,就这就产生了矛盾。例如示例中的main方法的参数是一个数组,那么在这种情况下,我们该如何给它传递参数呢?jdk1.5肯定要兼容jdk1.4的语法,上面示例的两种调用方式就解决了问题,如果直接传递new String[]{"xxx"}这个参数,虽然这符合jdk1.5的语法要求,但是,为了兼容jdk1.4的语法,jdk把它理解成了jdk1.4的样子,所以,出现了参数类型不对的问题。对于传数组的情况,只能按jdk1.4的语法进行调用,因为如果参数类型就是Object,它是没有办法区别的。
反射有什么用?什么时候要用反射方式来做。张老师打了个比方,举例,我房子卖给用户,由用户自己装门和空调,那我在写本程序(房子框架)时,你这个用户可能还在上小学,我写的房子怎样调用到你的门窗呢?这个时候就只能通过反射来解决了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: