黑马程序员 java 反射的深入学习

 ----------------------ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

反射:反射就是把Java类中的各种成分映射成相应的java类。

一、透彻分析反射的基础-Class类


反射不是JDK1.5的新特性,是从Java1.2时就有的一个特性,我们后面要学到的框架-struts,spring,JUnit都要用到反射技术 


反射的基石 -- Class(这是一个类的名字)


  • Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。


Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同实例对象有不同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与小写class关键字的区别哦。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。学习反射,首先就要明白Class这个类。

 

  • 对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
    • 人 --> Person
    • Java类 --> Class


  • 对比提问:Person类代表人,它的实例对象就是张三,李四这样一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?
    • 对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等
    • 一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢


  • 如何得到各个字节码对应的实例对象(class类型)
    • 类名.class,例如System.class //返回的就是Class的实例对象,就是字节码
    • 对象.getClass(),例如,new Date().getClass();
    • Class.forName(“类名”),例如,Class.forName(“java.util.Date”)


Person p1 = new Person();

Person p2 = new Person();


(1)Class cls1 = Date.class;//字节码1

 Class cls2 = 字节码2;

每一份字节码就是一个Class的实例对象

(2)p1.getClass(); //也可以得到字节码(这个对象所属的Class)

(3)Class.forName(“java.lang.String”);


forName方法的作用(静态方法,做反射的时候主要用这种,因为并不会事先知道会是什么类)

得到一个类的字节码有两种情况:

一个是这个类的字节码已经加载到内存中了,得到它的字节码,就不需要加载,直接找到那份字节码把他返回。

另一个是,我想得到这份类的字节码,但是虚拟机里还没有这个字节码,要用类加载器加载,加载进来后,把这份字节码缓存起来,同时,forName方法返回刚才加载进来的这个类的字节码。

		String s = "adc";
		Class cls1 = s.getClass();
		Class cls2 = String.class;
		Class cls3 = Class.forName("java.lang.String");
		
		System.out.println(cls1 == cls2);//true
		System.out.println(cls1 == cls3);//true
  • 九个预定义Class实例对象

八个基本的数据类型(boolean, byte, char, short, int, long, float, double)分别对应八个Class的实例对象,还有关键字void也是一个Class的实例对象(Class c1 = void.class)

  System.out.println(cls1.isPrimitive());//false
  System.out.println(int.class.isPrimitive());//true
  System.out.println(int.class == Integer.class);//false
  System.out.println(int.class == Integer.TYPE);//true
  System.out.println(int[].class.isPrimitive());//false
  System.out.println(int[].class.isArray());//true

二、理解反射的概念


  • 反射:反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类用一个Class类的对象来表示,一个类中的的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field, Method, Constructor, Modifier, Package 等等。

我们通过写程序可以得到那个类里面的各个成分所对应的类的对象。一个成分在程序里也是用一个对象表示的。


  • 一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢??怎么用呢?这正是反射的要点。

Constructor类


  • Constructor 类代表某个类中的一个构造方法
  • 得到某个类所有的构造方法:
    • 例子:Constructor[] constructors = 

Class.forName(“java.lang.String”).getConstructors();

  • 得到某一个构造方法:
    • 例子: Constructor constructor = 

Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);

//获得方法时要用到类型(这里指StringBuffer.class)

  • 创建实例对象:
    • 通常方式:String str = new String(new StringBuffer(“abc”));
    • 反射方式:String str = (String)constructor.newInstance(new StringBuffer(“abc”))

// 调用获得的方法时要用到上面相同类型的实例对象(这里指StringBuffer的对象)

  • Class.newInstance()方法:
    • 例子:String obj = (String)Class.forName(“java.lang.String”).newInstance();
    • 该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
    • 该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
    • 目的是为了简化:之前的过程class --> constructor --> new object

现在直接可以得到new object不过调用的是默认的无参构造函数


反射会导致程序性能严重下降


四、成员变量的反射


 Field类

  • Field类代表某个类中的一个成员变量
  • 演示用Eclipse自动生成Java类的构造方法

alt+shift+s (mac OS X下是alt+commond+s) -->Generate Constructor using Fields

或者代码窗口右键Source --> Generate Constructor using Fields

  • 问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldX代表的是x的定义,而不是具体的x变量。
  • 示例代码

public class ReflectPoint {

private int x;

public int y;

public ReflectPoint(int x, int y) {

super();

this.x = x;

this.y = y;

}

}

ReflectPoint pt1 = new ReflectPoint(3, 5);

Field fieldY = pt1.getClass().getField("y");

System.out.println(fieldY.get(pt1));

fieldY不是对象身上的变量,而是类上的,要用它去取某个对象上对应的值

//Field fieldX = pt1.getClass().getField("x");

Field fieldX = pt1.getClass().getDeclaredField("x");

fieldX.setAccessible(true);

System.out.println(fieldX.get(pt1));

如果使用Field fieldX = pt1.getClass().getField("x");的getField方法只能得到那些可见的,如果运行这句话会报错,因为x是私有的,getField不能得到私有变量,应该使用pt1.getClass().getDeclaredField("x")方法

得到fieldX后,直接执行System.out.println(fieldX.get(pt1));也会报错,要加上fieldX.setAccessible(true)这句话后,就可以访问了,这叫暴力反射


五、成员变量反射的综合案例


  • 将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的”b”改成”a”.

结果把这个对象的一些值改掉

//对字节码的比较用==比,因为字节码只有一份

    • 先定义一个要被反射的类

public class ReflectPoint {

private int x;

public int y;

public String str1 = "ball";

public String str2 = "basketball";

public String str3 = "itcast";

public ReflectPoint(int x, int y) {

super();

this.x = x;

this.y = y;

}

public String toString() {

return str1 + ": " + str2 + ": " + str3;

}

}

    • 主程序中的反射代码

class ReflectTest {

public static void main(String[] args) throws Exception {

changeStirngValue(pt1);

System.out.println(pt1 );

}

private static void changeStirngValue(Object obj) throws  Exception {

Field[] fields = obj.getClass().getFields();

for(Field field: fields) {

if (field.getType() == String.class) {

 String oldValue = (String)field.get(obj);

 String newValue = oldValue.replace('b''a');

 field.set(obj, newValue); 

}

}

}

六、成员方法的反射


Method类

  • Method类代表某个类中的一个成员方法

然后可以拿着这个方法去调用各个对象

比如我们可以使用str1.charAt; str2.charAt -- 说明方法和这个对象是没有关系的,是属于类的,调用方法必须通过对象来调用

  • 得到类中的某一个方法:
    • 例子:  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对象对应的是一个静态方法;

System.out.println(methodCharAt.invoke(null, 1 ));

因为没有通过一个对象来调用这个方法,静态方法调用的时候不需要对象


  • jdk1.4和jdk1.5的invoke方法的区别:
    • jdk1.5: public Object invoke(Object, Object...args)
    • jdk1.4: public Object invoke(Object obj, Object[] args),即按jdk1,4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用jdk1.4改写为

charAt.invoke(“str”, new Object[]{1})形式

比如:

System.out.println(methodCharAt.invoke(str1, 1 ));

System.out.println(methodCharAt.invoke(str1, new Object[]{1}));

这两句是一样的


七、对接收数组参数的成员方法进行反射


用反射方式执行某个类中的main方法


  • 目标
    • 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。

普通方式调完后,大家要明白为什么要用反射方式去调啊?

    • 如在要执行的类ReflectTest的main[String[] args]方法中,args[0]指定是我们要调用main方法所在的类名TestArguments,在Eclipse中右键Run As --> Run Configuration,输入我要调用得类名cn.itcast.day1.TestArguments
    • 在主要的运行的类中定把这个args[0]传给一个String,也就是将要调用的类名传给String
    • 通过Method mainMethod=class.forName(startingClassName).getMethod(“main”, String[].class);

即得到args[0]这个参数传进来的类的main方法

ps:如果不给这个args[]传递参数,即要调用的类名,就会报错,脚标越界

还会碰到下面马上讲到的exception,有详细讲解。

    • 然后调用这个main方法 

先写一个类要被调用main方法的类

class TestArguments{

public static void main(String[] args) {

for(String arg: args) {

System.out.println(arg);

}

}

}

然后调用这个类的main方法

public class ReflectTest {

public static void main(String[] args) throws Exception {

//TestArguments.main(new String[] {"111", "222", "333"});

String startingClassName = args[0];

Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);

//mainMethod.invoke(null, new String[]{"111", "222", "333"});

//mainMethod.invoke(null, new Object[]{new String[]{"111","222", "333"}});

mainMethod.invoke(null, (Object)new String[]{"111""222""333"});

}

}

后面两句都可以调用


  • 问题
    • 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成若干单独的参数。所以,在给main方法传递参数时,不能使用代码。

main Method.invoke(null, new String[] {“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。


  • 解决办法:
    • main Method.invoke(null, new Objact[] {new String[]{“xxx”}});
    • main Method.invoke(null, (Object)new String[]{“xxx”});,编译器会作操作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干参数了

如:

//mainMethod.invoke(null, new Object[]{new String[]{"111","222", "333"}});

mainMethod.invoke(null, (Object)new String[]{"111""222""333"});


八、数组与Object的关系及其反射类型


数组的反射


  • 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
  • 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
  • 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,非基本类型的一维数组,既可以被当作Object类型使用,也可以当作Object[]类型使用 


Arrays这个类里面有大量对数组操作的方法

int[] a1 = new int[]{1,2,3};

int[] a2 = new int[4];

int[][] a3 = new int[2][3];

String[] a4 = new String[]{"a","b","c"};

System.out.println(a1.getClass() == a2.getClass());

//System.out.println(a1.getClass() == a4.getClass());

//System.out.println(a1.getClass() == a3.getClass());

//System.out.println(a1 == a4);

//System.out.println(a1 == a3);

System.out.println(a1.getClass().getName());

System.out.println(a2.getClass().getName());

System.out.println(a3.getClass().getName());

System.out.println(a4.getClass().getName());

System.out.println(a1.getClass().getSuperclass().getName());

System.out.println(a4.getClass().getSuperclass().getName());

Object aObj1 = a1;

Object aObj2 = a4;

//Object[] aObj3 = a1;

Object[] aObj4 = a3;

Object[] aObj5 = a4;  

System.out.println(a1);

System.out.println(a4);


  • Arrays.asList()方法处理int[]和String[]时的差异

System.out.println(Arrays.asList(a1));//输出结果为 [[I@43256ea2]

System.out.println(Arrays.asList(a4)); //输出结果为 [a, b, c]

说明a1被当成一个object对象Object obj1 = a1; 而不能被当成一个数组

a4被当成一个Object,也可以被当成一个Object[]


  • Array工具类用于完成对数组的反射操作

public class ReflectTest {

public static void main(String[] args) throws Exception {

printObject(a4);

printObject("xyz");

}

private static void printObject(Object obj) {

Class clazz = obj.getClass();

if(clazz.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);

}

}



  • 思考题:怎么得到数组中的元素类型

没有办法得到数组的类型(目前来讲)如:int[] a = new int[3]

Object[] b = new Object[] {“a”, 1}

只能得到数组中某一个具体元素的类型

如:a[0].getClass().getName();


九、ArrayList与HashSet的比较及Hashcode分析


  • Arraylist中存放Object是有顺序的,即使相同的引用变量,它也会被放在自己的位置上,只按先后位置顺序,还可以插队


  • HashSet是你添加一个Object时,它先比较有了的Object,没有才放进去,有了就不放进去,如果想放一个对象盖调原来的对象,就要先把之前的那个对象删除(remove),再插进去新的(比较的是HashCode)


  • HashCode的作用:

当你想放一个元素进HashSet时,HashSet要先比较是否有相同的对象,没有才放入。

传统的方法是,把这个Set中的所有元素逐一与要放入的元素进行比较,如果Set里有很多元素(比如10000个),而且没有包含要查找的对象,意味着程序需要比较10000遍。效率很低。

于是有人发明了Hash算法,把这个集合分成若干个区域,每个要存进来的对象根据某种算法得出一个值, 根据这个算出来的值放进相应的区域。如对32取模,则有32个区域。

查找时,先算出这个对象的Hash值,根据这个值,再到相应的区域里查找。这样性能提高很多。 


  • HashSet就是采用Hash算法存取的集合它内部采用对某个数字n进行取余方式对HashCode进行分组和划分对象的存储区域。

通常在把对象放进HashSet的时候,如果它们equals相等,应该让它们的hash code也相等。

当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。


  • Java中有内存泄露么?为什么(内存泄露就是这个对象不要用了,它却没有被释放,始终占用内存空间,浪费了内存,这就叫内存泄露)

这时可以举这个例子

public class ReflectTest2 {

public static void main(String[] args) {

//Collection collection = new ArrayList();

Collection collection = new HashSet();  

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);

pt1.y = 7;//这里pt1就被修改了,hash code就变了,删除时无法删除,造成内存泄露

collection.remove(pt1);

System.out.println(collection.size());

}

}

十、框架的概念及用反射技术开发框架的原理


反射的作用-->实现框架功能


  • 框架与框架要解决的核心问题
    • 我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。

  • 框架要解决的核心问题
    • 我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
    • 因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做。

之前通过反射调用main方法的时候已经有这样一个例子了

String startingClassName = args[0];

Method mainMethod = class.forName(startingClassName).getMethod(“main”, String[].class);

用struts框架举个例子,我们写的类给struts框架调用,struts框架先写完,我们的程序后写完。这就是反射的应用。


  • 综合案例
    • 先直接用new语句创建ArrayList和HashSet的实例对象,演示用eclise自动生成ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。

上一节的练习,在ReflectPoint中右键,Source-->Generate hashCode() and equals(),就会自动生成了

    • 然后改为采用配置文件加反射的方式创建ArrayList和HashSet实例对象,比较观察运行结果差异。
    • 引入了eclipse对资源文件的管理方式的讲解。
      • eclipse选择工程右键,New-->File,在File name:中填写文件名,这里写config.properties,在config.properties文件中写className=java.util.ArrayList,在运行的时候,不用该java源程序,只要改properties文件,这个类就换了。比如交给客户一个程序,客户没有javac,不能更改源文件,但是可以用记事本改properties文件。


java中的一个properties的对象就等效于一个HashMap,它内存里装的都是key value,但是他在HashMap的基础上扩展了一些功能,可以将自己内存里面的键值对存到系统中去,它也可以初始化的时候从一个文件里面把自己的键值对加载进来。

public class ReflectTest2 {

public static void main(String[] args) throws Exception{

InputStream ips = new FileInputStream("config.properties");

Properties props = new Properties();

props.load(ips);

ips.close(); //不写会有写内存泄露,不是这个对象不被释放,而是这个对象关联的系统资源没被释放

String className = props.getProperty("className");

Collection collection = (Collection)Class.forName(className).newInstance();

//Collection collection = new ArrayList();

//Collection collection = new HashSet();  

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);

//pt1.y = 7;

collection.remove(pt1);

System.out.println(collection.size());

}

}

properties的file文件中,如果是java.util.ArrayList就可以删,如果是java.util.Hashset就可以被删掉


十一、用类加载器的方式管理资源和配置文件

 

  • 一个程序要用到一个文件(.properties, .mp3, .avi...)我们练习的时候为了方便把他放在相对目录里面,也就是说这个文件要放在当前工作目录下才可以运行调用。因此,相对目录的坏处就是当你的classpath设置中指向你要用到的类的路径,你在任何目录下都可以运行这个类,但是在其他目录下你就用不了那个文件。


  • 如果用绝对路径,又会有一个问题,比如你的绝对路径在地盘下面,而那个电脑又没有d盘,还是找不到这个文件。


解决方案:

  • 是用绝对路径,比如在配置文件中,让用户去配置,指出要找的文件的位置,然后用户就可以去读,然后就知道这个文件在哪了。
    • JavaWeb提供了一个方法叫getRealPath();//如金山词霸/内部,你的配置文件又在金山词霸的内部,你得到了金山词霸的绝对位置,再拼上你内部的位置,就可以得到一个真实的完整位置。用户无论把金山词霸放在哪程序始终都能得到最终总目录的绝对位置。
    • 一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。
  • 还有一个方法是使用类加载器

ReflectTest2.class.getClassLoader().getResourceAsStream(name)

就是在classpath指定的那些目录下,逐一的去查找要加载的那个文件。

举个例子,就是

    • 把刚才刚才那个config.properties文件放在和类平级的那个目录下也就是源程序目录下src(cn.itcast.day1)eclipse编译后,会把编译好的class文件存在bin目录中,其他文件则原封不动的也搬去那个目录。
    • 在程序中写//InputStream ips = new FileInputStream("config.properties");

InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");

    • 这样系统就可以找到这个文件了。注意不能这样表示一个目录 "/cn/itcast/day1/config.properties",最前面多了一个/
    • 问题是使用类加载器这个方式是只读的,不能写入,因为没有OutputStream。
  • 大家在学struts, spring等框架的时候都有配置文件,它们是框架,框架就有配置文件。它们的配置文件都是放在classpath指定的目录下加载。
  • Class也提供了一个getResourceAsStream方法,内部实现也是通过了类加载器getClassLoader(),但是只要写文件名就可以了,不需要写目录名。

//InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");

InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");

  • 如果在cn/itcast/day1建一个子包,叫resources(eclipse中右键-->New-->Package),名字为cn.itcast.day1.resources,把之前的那个config.properties放进去,这时代码中就要把这个classpath下的子目录加进去。

//InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");

InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties");

  • 如果想简便,可以不用相对目录,而用绝对目录,这时就在路径前加/,这时就不指向类的相对目录了,而是指向了classpath的根,这个很不常用(如果加载的文件和这个类没关系)

//InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties");

InputStream ips = ReflectTest2.class.getResourceAsStream ("/cn/itcast/day1/resources/config.properties");


  • 那种绝对路径的解决办法用于需要存文件的情况。


十二、对JavaBean的简单内省操作


  • JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名复合某种命名规则。


  • 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果setId,就是设置id,不用管存到哪个变量上。如果是getId,就是获取id,也不用管从哪个变量上取/去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小写的。

JavaBean可以当作一个普通的类来编程,来操作。但是一个Java类不一定能把他当作JavaBean来操作,要看这个类有没有get和set操作方法,只要有get和set方法,就可以把他当作JavaBean来操作,也可以当普通类来操作。

Class Person {

private int x;

public int getAge(){

return x;

}

public void setAge(int age){

this.age = age;

}

}

比如这样一个类可以当作JavaBean也可以当作普通类。

如果把一个Java类当作JavaBean来看,那么这个JavaBean的属性是根据set和get方法的名称来推断出来的,而不是根据内部的成员变量得来的(也根本看不到)。如过把上面这个Person当作JavaBean来看,他有一个为age的属性(不是x)。

去掉set和get以后剩下来的名称Age -- >age(前提条件是如果第二个字母是小的,就把第一个变成小的)就是这个JavaBean的属性名。如:

    • gettime --> time
    • setTime -->time
    • getCPU --> CPU


  • 一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean,好处如下:
    • 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
    • JDK中提供了对JavaBean进行操作的一些API,这套API就成为内省。如果要你自己去通过getX方法来访问私有的x,是有一定难度的。用内省这套API操作JavaBean比用普通类的方式更方便。 


  • 内省综合案例
    • 演示用eclipse自动生成ReflectPoint类的setter和getter方法。

在ReflectPoint类中,右键Source-->Generate Getters and Setters,只勾选x,y。就生成了x,y的set和get方法。


    • 直接new一个PropertyDescriptor对象的方式来让大家了解JavaBean的API的价值,先用一段代码读取JavaBean的属性,然后再用一段代码设置JavaBean的属性。
    • 演示用eclipse将读取属性和设置属性的流水账代码分别抽取成方法
      • 只要调用这个方法,并给这个方法传递了一个对象、属性名和设置值,他就能完成属性修改的功能。

public static void main(String[] args) throws Exception {

// TODO Auto-generated method stub

ReflectPoint pt1 = new ReflectPoint (3, 5);

String propertyName = "x"

//"x" --> "X" --> "getX" --> MethodGetX -->

Object retVal = getProperty(pt1, propertyName);

System.out.println(retVal);

Object value = 7;

setProperty(pt1, propertyName, value);

System.out.println(pt1.getX());


}


private static void setProperty(Object pt1, String propertyName,

Object value) throws IntrospectionException,

IllegalAccessException, InvocationTargetException {

PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, pt1.getClass());

Method methodSetX =pd2.getWriteMethod();

methodSetX.invoke(pt1, value);

}


private static Object getProperty(Object pt1, String propertyName)

throws IntrospectionException, IllegalAccessException,

InvocationTargetException {

PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass());

Method methodGetX =pd.getReadMethod();

Object retVal = methodGetX.invoke(pt1);

return retVal;

}


Eclipse还有一个功能是,当有多行代码是共同为了执行一个方法时,可以把这段代码提取出来生成一个方法:先选择要放进方法的代码,右键,Refractor-->Extract Method, 设置好参数类型和参数名,以及方法名,则方法就抽取出来了。

    • 得到BeanInfo最好采用”obj.getClass()”方式,而不要采用“类名.class”方式,这样程序更通用


  • 采用遍历BeanInfo的所有属性方式来查找和设置某个ReflectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector方发,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。

最早是用的这种复杂的方式做,下面为演示

private static Object getProperty(Object pt1, String propertyName)throws IntrospectionException, IllegalAccessException,InvocationTargetException {

/*PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass());

Method methodGetX =pd.getReadMethod();

Object retVal = methodGetX.invoke(pt1);*/

BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());

PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

Object retVal = null;

for (PropertyDescriptor pd : pds) {

if(pd.getName().equals(propertyName)) {

Method methodGetX = pd.getReadMethod();

retVal = methodGetX.invoke(pt1);

break;

}  

}

return retVal;

}

这个就把之前那个简单的方法改成了稍微复杂的方法。


十三、使用BeanUtils工具包操作JavaBean


先下载BeanUtils,http://commons.apache.org/beanutils/download_beanutils.cgi,里面的commons-beanutils-1.8.3/apidocs/index.html可以查看BeanUtils的API. 在BeanUtils类下面,有很多的静态方法,来操作JavaBean.


  • 演示用eclipse如何加入jar包,先只是引入beanutils包,等程序运行出错后再引入logging包。

具体怎么导入可以看里面的README文件

在eclipse选中项目文件夹,右键Build Path --> Configure Build Path,在tab Libraries下,如果选择Add External JARs,把这个工程拷给别人的时候别人找不到这个JAR包的,因此要先在这个工程文件下建一个文件夹把这个JAR包放进去(选择工程文件夹,右键,New-->Folder, Folder name:我这里起名叫lib,把BeanUtilss那个JAR包拷进来),在把这个JAR包增加进这个工程中去(选择这个JAR包,右键,Build Path-->Add to Build Path)就被添加好可以使用了。

这时,在代码中加入System.out.println(BeanUtils.getProperty(pt1, "x"));测试,出错,找不到org/apache/commons/logging/LogFactory这个类。

现在把日志开发包导进来:

http://commons.apache.org/logging/download_logging.cgi上下载logging包,按照导入JavaUtils的JAR包的方法导入commons-logging-1.1.1.JAR(增加到Build Path上).

现在运行就成功的得到了x的值了。

  • 在前面内省例子的基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。
    • get属性时返回的结果为字符串,set属性时可接受任意类型的对象,通常使用字符串。

System.out.println(BeanUtils.getProperty(pt1, "x"));

BeanUtils.setProperty(pt1, "x""9");

BeanUtils.getProperty返回的结果是String------System.out.println(BeanUtils.getProperty(pt1, "x"));

好处:

  1.BeanUtils可以自动进行类型转换,很方便,set,get都用字符串来操作。

2.BeanUtils.setProperty(pt1, "birthday.time""111");在ReflectPoint中新定义一个Date birthday,为birthday写set和get方法,birthday是一个复合属性,不是一个基本属性,它本身就是一个对象可以使用Date的setTime方法,因此他有一个属性是time属性,给这个time属性赋值为“111”。这样就相当于给pt1中的birthday对象的time属性赋值了。

但这个时候会报错:No bean specified,因此定义Date的时候要给他赋初始值:private Date birthday = new Date();

这时就set进去了,BeanUtils支持属性的链级操作。可一级级往下设置。

也可以get------System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));

JavaUtils的一些其他方法:copyProperties(java.lang.Object dest, java.lang.Object.orig); describe(java.lang,Object bean)把一个JavaBean的属性转换为一个Map----map(age:7, name:‘zxx’);populate(java.lang.Object bean, java.util.Map porperties)这个方法可以把Map转化为JavaBean。

3. BeanUtils可以直接操作Map

Map map = (name:"zxx"age: 18);

BeanUtils.setProperty(map, "name""lhm");

Map像上面这样定义是java7的新特性。Map的key相当于JavaBean的属性,并且Map和JavaBean可以相互转换。


  • 用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。
    • get属性时返回的结果为该属性本来的类型,set属性时只接收该属性本来的类型。

//PropertyUtils.setProperty(pt1, "x", "9");会报错

PropertyUtils.setProperty(pt1, "x", 9);

System.out.println(PropertyUtils.getProperty(pt1, "x").getClass().getName());

说明BeanUtils是用字符串进行操作,而PropertyUtils是以这个属性本身的类型进行操作。

如果不想进行类型转换,或者BeanUtils进行自动转换的时候转换错了,就用PropertyUtils。

如果想进行类型转换就用BeanUtils。

 ----------------------ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值