反射的基石àClass类
l 对比提问:Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
Ø 人àPerson
Ø Java类àClass
l Class类代表Java类,它的各个实例对象又分别对应什么呢?
Ø 对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
Ø 一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
l 如何得到各个字节码对应的实例对象(Class类型)
Ø 类名.class,例如,System.class
Ø 对象.getClass(),例如,newDate().getClass()
Ø Class.forName("类名"),例如,Class.forName("java.util.Date");
l 九个预定义Class实例对象:
bytechar shory int float double long boolean void
Ø 参看Class.isPrimitive方法的帮助
Ø Int.class == Integer.TYPE
l 数组类型的Class实例对象
Ø Class.isArray()
l 总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
l 反射就是把Java类中的各种成分映射成相应的java类【冯伟立】。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。然后这些类中又有相应的特性和方法。
Constructor
l Constructor类代表某个类中的一个构造方法
l 得到某个类所有的构造方法:
Ø 例子:Constructor[] constructors= Class.forName("java.lang.String").getConstructors();
l 得到某一个构造方法:
Ø 例子: Constructor constructor =Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//获得方法时要用到类型
l 创建实例对象:
Ø 通常方式:Stringstr = new String(new StringBuffer("abc"));
Ø 反射方式: Stringstr = (String)constructor.newInstance(new StringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象
l Class.newInstance()方法:
Ø 例子:Stringobj = (String)Class.forName("java.lang.String").newInstance();
Ø 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
Ø 该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
1. 一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型,例如,Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。重点:参数类型用什么方式表示?用Class实例对象。例如:
int.class,(int []).class
int [] ints = new int[0];
ints.getClass();
Constructor对象代表一个构造方法, Constructor对象上会有什么方法呢?得到名字,得到所属于的类,产生实例对象。
2.通常情况下是怎样做的,String str = new String(new StringBuffer(“abc”));
String str =(String)constructor.newInstance(/*"abc"*/newStringBuffer("abc"));
System.out.println(str);
反射方式创建实例对象时,先用string作为参数传进去,根据错误让大家感受到确实是那个构造方法,然后再改为传一个StringBuffer类型的参数进去, String str =(String)constructor.newInstance(/*"abc"*/newStringBuffer("abc"));
好比,我叫来一个吃人不吃草的恐龙,等到它要吃东西时,我得给他送真人去了吧。
Field
l Field类代表某个类中的一个成员变量
l 问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。
l 示例代码:
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
ReflectPoint point = new ReflectPoint(1,7);
Field y =Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
System.out.println(y.get(point));
//Field x =Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
Field x =Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));
l 作业:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
class Xxx
{
String name="abc";
String email="abd";
int x = 5;
}
func(Object obj)
{
Field [] fields =obj.getClass().getDeclaredFields();
for(Field field : fields)
{
if(field.getType()==java.lang.String.class)//使用equals也行,当然这种方式更好
{
field.setAccesible(true);
String original = (String)field.get(obj);
field.set(obj,original.replaceAll("b","a");
}
}
}
一个问题,我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用暴力反射还是能够访问我,这说不通啊,能不能让人家用暴力反射也访问不了我。首先,private主要是给javac编译器看的,希望在写程序的时候,在源代码中不要访问我,是帮组程序员实现高内聚、低耦合的一种策略。你这个程序员不领情,非要去访问,那我拦不住你,由你去吧。同样的道理,泛型集合在编译时可以帮助我们限定元素的内容,这是人家提供的好处,而你非不想要这个好处,怎么办?绕过编译器,就可以往集合中存入另外类型了。别人提供的好处我可以不要嘛。
Method
对象和方法是没有关系的,方法是属于类的,在对象身上调用方法。
面向对象思想其实是专家模式,谁有数据谁就操作这个数据,提供给别人的知识你操作以后的结果(你跟外界的接口),别人无须知道你是怎么操作的,也没必要关心,自己干好自己的本质工作就好了。人关门(门有关的方法)、司机刹车(车有刹车的方法)、人在黑板上画圆(圆知道自己怎么画)
Method类代表某个类中的一个成员方法
得到类中的某一个方法:
例子:Method charAt =Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式:System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Objectobj,Object... args)
Jdk1.4:public Object invoke(Objectobj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”,new Object[]{1})形式。
用反射方式执行某个类中的main方法
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
用普通方式调完后,大家要明白为什么要用反射方式去调啊?
我编写程序的时候,我并不知道我会调用哪个类,你传给我参数或者在配置文件中指定我才知道类名,然后调用main方法。
我给你的数组,你不会当作参数,而是把其中的内容当作参数。
Class clazz = Class.forName(arg[0]);
Method mMain = clazz.getMethod("main", String[].class);
mMain.invoke(null,new Object[]{newString[]{"aaa","bbb"}});//编译器会拆包2个参数,我再打包
mMain.invoke(null,(Object)newString[]{"aaa","bbb"});//告诉编译器是一个对象不要拆包
数组也是对象。
class TestArrayArguments {
public static void main(String [] args)
{
for(String arg:args)
{
System.out.println("----------"+ arg + "----------");
}
}
}
程序启动的时候传递给参数【word ppt等打开一个文件时,是调用这个程序,把文件名作为参数】
数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
Object[] 与String[]没有父子关系,Object与String有父子关系,所以new Object[]{“aaa”,”bb”}不能强制转换成new String[]{“aaa”,”bb”};,Object x = “abc”能强制转换成String x = “abc”。
true
false
false
[I
java.lang.Object
java.lang.Object
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用(即基本类型不是Object);非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异。
Arrays.asList()方法处理int[]【整体,数组作为一个参数,1.5的可变参数】和String[]【一个字符串数组,对象数组】时的差异,以及Arrays.deepToString()方法不能处理int[],但能处理String[]的原因。
Array工具类用于完成对数组的反射操作。
private static void printObject(Object obj) {
if(obj.getClass().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);
}
}
思考题:怎么得到数组中的元素类型?
这是不行的,因为对于一个Object[] a来说,里面可以存放任何的数据类型,只能是针对每一个元素获取类型。a[i].getClass().getName();
1、equals方法用于比较对象的内容是否相等(覆盖以后),默认按照地址。
2、hashcode方法只有在集合中用到。不仅是HashMap,所有使用散列的数据结构(HashSet,HashMap,LinkedHashSet,and
LinkedHashMap),根据对象的hashCode值将对象放入集合的不同区域,提高查找的速度。
3、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。一般来说,要讲对象放入hash算法的集合中,不仅要覆盖其equals方法,还要覆盖其hashCode方法。
4、如果equals()比较相同,那么hashcode()肯定相同。如果hashcode()比较相同,那么equals()不一定相同。将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
5、Object的hashCode是根据地址计算,其equals方法是根据==也是是根据地址,你覆盖他们就是从业务逻辑上来看的。一般认为数据一样的对象就是一个。
造成内存泄露
反射的作用à实现框架功能
l 框架与框架要解决的核心问题
Ø 我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
Ø 什么是框架,例如,我们要写程序扫描.java文件中的注解,要解决哪些问题:读取每一样,在每一个中查找@,找到的@再去查询一个列表,如果@后的内容出现在了列表中,就说明这是一个我能处理和想处理的注解,否则,就说明它不是一个注解或者说至少不是一个我感兴趣和能处理的注解。接着就编写处理这个注解的相关代码。现在sun提供了一个apt框架,它会完成所有前期工作,只需要我们提供能够处理的注解列表,以及处理这些注解的代码。Apt框找到我们感兴趣的注解后通知或调用我们的处理代码去处理。
你做的门调用锁,锁是工具,你做的门被房子调用,房子是框架,房子和锁都是别人提供的。
Properties props = new Properties();
//先演示相对路径的问题
//InputStream ips = newFileInputStream("config.properties");
/*一个类加载器能加载.class文件,那它当然也能加载classpath环境下的其他文件,既然它有如此能力,它没有理由不顺带提供这样一个方法。它也只能加载classpath环境下的那些文件。注意:直接使用类加载器时,不能以/打头。*/
//InputStream ips =ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/javaenhance/config.properties");
//Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件
//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/javaenhance/config.properties");//**
props.load(ips);
Ips.close();
String className = props.getProperty("className");
Class clazz = Class.forName(className);
Collection collection = (Collection)clazz.newInstance();
//Collection collection = new ArrayList();
ReflectPoint pt1 = new ReflectPoint(3,3);
ReflectPoint pt2 = new ReflectPoint(5,5);
ReflectPoint pt3 = new ReflectPoint(3,3);
collection.add(pt1);
collection.add(pt2);
collection.add(pt3);
collection.add(pt1);
System.out.println(collection.size());
}
Ø 【工具类和框架类,都是使用别人的类,但是调用工具类是干具体的活【先有了工具类,直接使用】,自己写的类被框架调用【框架先写,使用反射调用后写的类】。框架怎么能够调用还没有出现的类呢?框架提供一个抽象,或者一个接口,比如一个方法是用来干什么的【抽象的】,具体怎么干就是你的事了。比如struts里面的validate方法抽象出来就是校验,具体怎么校验是你的事,我知道你的累里面肯定有个validate方法,所以我能够通过反射的方法来调用;你写的类里面可以有你的个性,但是必须满足框架的规定,规则,否则这么调用呢?】,如此一来,什么都可以配置。
内省à了解JavaBean
l JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
l 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量【属性只是一个桥梁】。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
Ø setId()的属性名àid
Ø isLast()的属性名àlast
Ø setCPU的属性名是什么?àCPU
Ø getUPS的属性名是什么?àUPS
总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。【setter、getter】
l 一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
Ø 在Java EE开发中,经常要使用到JavaBean【比如传递表单数据】。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
Ø JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
PropertyDescriptor[]pds = beanInfo.getPropertyDescriptors();
for(PropertyDescriptorpd :pds){
if(pd.getName().equals(propertyName)){//找到你要找的属性名
pd.getWriteMethod().invoke(pt1,value);
break;
}
}
开源工具BeanUtils