黑马程序员 高新技术<二> 反射和内省

来源:互联网 发布:代码重复率 知乎 编辑:程序博客网 时间:2024/05/22 10:26

------- android培训 java培训、期待与您交流! ----------

第一节 Class

一.概述:

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

注意:Classclass的区别:

class:JAVA类用于描述一类事务的共性,该类有什么属性,没什么属性,至于这个属性的值是什么。则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。

Classjava程序中的各个java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事务呢?这个类的名字就是Class

Class类描述了哪些方面的信息呢?

类的访问属性,类所属于的包名,字段名称列表,方法名称的列表,等等。

Class类代表Java类,它的各个实例对象又分别对应什么呢?

1.对应各个类在内存中的字节码,例如Person类的字节码,ArrayList类的字节码,等等。

2.一个类被类加载器加载到内存中,占用一片存储空间,这个空间的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个对象来表示,这些对象显然具有相同的类型。

什么叫字节码?

  每创建一个类时,就会在产生一份字节码

每份字节码都是Class的实例对象

 Class代表内存中的一份字节码

得到字节码的方法:

类名.class  例如System.class

对象.getClass()   例如, new Date().getClass()

Class.forName();  

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

1.用类加载器去加载,加载进来后,就把字节码缓存起来,同时返回刚才加载进来的字节码。

2.这个类的字节码已经加载到内存中。这时只需要查找,找到直接返回。

九种预定义的Class

1.基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。 

注意:Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以int.class是相等的。

数组类型的Class实例对象是Class.isArray();

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[], void ......

特有方法:

static Class<?> forName(String className);:返回与带有给定字符串名的类或接口相关联的 Class 对象。

 boolean isPrimitive();:判定指定的 Class 对象是否表示一个基本类型。

boolean isArray():判定此 Class 对象是否表示一个数组类。

T newInstance();:创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。

示例代码:

package javaenhance;public class ReflectTest {/** * @param args */public static void main(String[] args)  throws Exception {// TODO Auto-generated method stub          String str ="abc";          Class c1 = str.getClass();          Class c2 =String.class;          Class c3 = Class.forName("java.lang.String");          System.out.println(c1==c2);          System.out.println(c2==c3);          String str2 ="abcd";          String str3 ="abb";          System.out.println(str==str2);          Class c4 = str2.getClass();          System.out.println(c1==c4);          Class c5 = str3.getClass();          System.out.println(c1==c5);          System.out.println(c2==c5);          System.out.println(c3==c5);          WeekDay2 week1 = new WeekDay2();          Class c6 = week1.getClass();          WeekDay2 week2  = new WeekDay2();          Class c7 = week2.getClass();          System.out.println(c6==c7);}}class WeekDay2{}

第二节 反射

一.概述:

1.什么是反射:

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

2.反射比较消耗性能,

第三节 构造方法的反射应用

一.概述:

Constructor类

 Constructor代表某个类中的一个构造方法

 得到某个类所有的构造方法:

 Constructor[] construct= 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"));

  //调用获得的方法时要用到上面相同类型的实例对象

Class.newInstance() 方法

  例子:String obj =(String) Class.forName("java.lang.String").newInstance();

  该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象

  该方法内部代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象

示例代码:

package javaenhance;import java.lang.reflect.Constructor;import Collection.Students;public class ReflectTest2 {/** * @param args */public static void main(String[] args) throws Exception{// TODO Auto-generated method stub         //获取某一个类上的所有构造方法Constructor[] constructors = Class.forName("java.lang.String").getConstructors();//获取某一个类具体的某一个构造方法Constructor  construct = Class.forName("java.lang.String").getConstructor(StringBuffer.class);String str = (String)construct.newInstance(new StringBuffer("abc"));System.out.println(str);String classconstruct =  (String) Class.forName("java.lang.String").newInstance();classconstruct = new String("abb");System.out.println(classconstruct);System.out.println(str.getClass()==classconstruct.getClass());Constructor constu = Students.class.getConstructor(String.class,int.class);String name ="小康";int age =22;Students stu = (Students) constu.newInstance(name,age);System.out.println(stu.toString());}}

package javaenhance;public class Person { private String name; private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}public Person(String name, int age) {super();this.name = name;this.age = age;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Person other = (Person) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}

第四节 成员变量的反射

Field类

   Field类代表某个类中的一个成员变量。

得到的Field对象是对应到类上面的成员变量。

特有方法:

void setAccessible(boolean flag);:将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。 

 Object get(Object obj):返回指定对象上此 Field 表示的字段的值。如果该值是一个基本类型值,则自动将其包装在一个对象中。

成员变量反射综合案例:

将一个类上的所有String类型的’a’换成’b’;

思路:

1.首先获取这个类上的所有成员变量。涉及到的方法:Field[] getFields()

2.再利用增强for循环逐个读取Field[]中的元素。

3.再判断这个类上的那些成员是String类型的。

4.如果有再获取这个成员变量的具体值。把’a’变成’b’;

5.利用set(Object obj,Obejct newValue).将Field对象上的指定值换成新值。

示例代码:

package javaenhance;import java.lang.reflect.*;public class ReflectTest3 {/** * @param args */public static void main(String[] args) throws Exception {// TODO Auto-generated method stub         ReflectPoint rp = new ReflectPoint(3,5);         Field fieldy = rp.getClass().getField("y");         System.out.println(fieldy.get(rp));         Field fieldx = rp.getClass().getDeclaredField("x");         fieldx.setAccessible(true);         System.out.println(fieldx.get(rp));         changeStrValue(rp);         System.out.println(rp);         Method method = Class.forName("java.lang.String").getMethod("charAt", int.class);         String str1="itcast";         System.out.println(method.invoke(str1, 1));         System.out.println(method.invoke(str1,new Object[]{2}));         String arguments = args[0];         Method mainmethod =Class.forName(arguments).getMethod("main", String[].class);         System.out.println(mainmethod.invoke(null, (Object)new String[]{"1111","2222","3333"}));         int[] int1 = new int[3];         int[] int2= new int[4];         int[][] int3 = new int[3][4];         String[] s = new String[]{"aaa","bbb","ccc"};         System.out.println(int1.getClass()==int2.getClass());         printObject(str1);         }@Deprecatedpublic static void printObject(Object obj) {// TODO Auto-generated method stub 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); }}public static void changeStrValue(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('a', 'b'); field.set(obj, newValue); } }}}class TestArguments{public static void main(String[] args){        for (String string : args) {System.out.println(string);}}}

第六节 数组与Object的关系及其反射类型

一.概述:

数组的反射:

1.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

3.基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用,非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[]类型使用。

4.无法得到某个数组的具体类型,只能得到数组内某个元素的类型。

例:

obj[0].getClass().getName();  得到的就是数组内第0个元素的类型。

第七节 HashSet和HashCode的分析

HashSet:

如果想查找一个集合众是否包含有某个对象,大概的程序代码怎样写呢?你通常是逐一

取出每个元素与药查找的对象进行比较,当发现某个元素与药查找的对象进行equals方法比较的结果相等时,

则停止继续查找并返回肯定的信息,否则返回否定的信息。如果一个集合中有很多个元素,例如有一万个元素,并且没有包含要查找的

的对象时,则意味着你的程序需要从该集合众取出一万个元素逐一比较才能得到结论。有人发明了一种哈希算法来提高从集合众查找元素的效率

这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希吗,可以将哈希吗分组,每组分别对应

某个存储区域,根据一个对象的哈希吗就可以确定该对象应该存储在哪个区域

 

HashSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希吗进行分组

和划分对象的存储区域。可见hashSet集合具有很好的对象检索性能

但是HashSet集合存储对象的效率相对低些

 

hashCode():Object类定义了一个hashCone()方法来返回每个Java对象的哈希吗

当从HashSet集合众查找某个对象时,Java系统首先调用对象的hashCode()方法来获得该对象的哈希吗。然后根据哈希吗找到相应的存储区域,最后取出该存储区域

内的每个元素与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以得到结论。

 

当我们覆盖hashCode()方法时,我们发现System.out.println(collections.size());的输出结果变为2了。

 

 

什么时候才要覆盖hashCode()方法呢?

只有类的实例对象要被采用哈希算法进行存储和检索时,这个类才需要按要求覆盖hashCode方法。即使程序可能暂时不会用到当前类的hashcode()方法,

但是为它提供一个hashcode方法也不会有什么不好,没准以后什么时候又用到了这个方法了,所以,通常要求hashcode方法和equals方法一并被同时覆盖

  提示:

 (1)通常来说,一个类的两个实例对象用equals()方法比较结果相等时,它们的哈希吗也必须相等,但反之则不成立,即equals方法比较结果不相等的对象可以有

相同的哈希吗,或者说哈希吗相同的两个对象的equals方法比较的结果可以不等,例如字符串"BB"和"Aa"的equals方法比较结果肯定不相等,但它们的hashcode方法返回值却相等。

 (2)当一个对象被存储进hashSet集合中以后,就不能修改这个对象中的那些参与计算的哈希值得字段了,否则,对象修改后的哈希值与最初存储进hashSet集合众时

的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合众检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露

示例代码:

package itcast;import java.util.ArrayList;import java.util.Collection;public class ReflectTest2 {    public static void main(String[] args){    Collection collections = new ArrayList();  //定义了一个ArrayList()对象    //使用构造方法public ReflectPoint(int a,int b) new出3个ReflectPoint对象     ReflectPoint   rp1 = new ReflectPoint(3,3);       ReflectPoint   rp2 = new ReflectPoint(5,5);     ReflectPoint   rp3 = new ReflectPoint(3,3);          //使用ArrayList对象Add()方法添加元素     collections.add(rp1);     collections.add(rp2);     collections.add(rp3);     collections.add(rp1);     System.out.println(collections.size());   //这里的输出结果为4  因为ArrayList继承自List                                                                 //List集合存储的是一组不唯一(可以重复),有序的对象        }}

package itcast;import java.util.Collection;import java.util.HashSet;public class ReflectTest3 {    public static void main(String[] args){    Collection collections = new HashSet();  //定义了一个HashSet()对象    //使用构造方法public ReflectPoint(int a,int b) new出3个ReflectPoint对象     ReflectPoint   rp1 = new ReflectPoint(3,3);       ReflectPoint   rp2 = new ReflectPoint(5,5);     ReflectPoint   rp3 = new ReflectPoint(3,3);          //使用HashSet对象Add()方法添加元素     collections.add(rp1);     collections.add(rp2);     collections.add(rp3);     collections.add(rp1);     System.out.println(collections.size());   //这里的输出结果变为3  为什么呢? 因为HashSet继承自Set                                                                 //Set集合存储的是一组唯一,无序的对象        }}

第八节 框架的概念及用反射技术开发框架的原理

一.概述:

1.框架与框架要解决的核心问题:

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

2.框架要解决的核心问题:

我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢。我写的框架程序怎样能调用到你以后写的类(门窗)呢?

因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。

3、简单框架程序的步骤:

1)右击项目名-->File-->命名,写入键值对:className=java.util.ArrayList,等号右边的可以自己定义集合的名称,即用户可以对此记事本修改成自己的类名。

2)代码实现,加载此文件:

①将文件读取到读取流中,一定要用完整的路径,可以使用getRealPath()方法获取路径名,再加上自己定义的文件夹名。

②用Properties类的load()方法将流加载经内存,即提取文件中的信息。

③关闭流:关闭的是读取流,因为流中的数据已经加载进内存。

3)通过getProperty()方法获取类名属性,将传入的类名赋值给指定变量。

4)用反射的方式,创建对象newInstance()

5)进行相关的具体操作。

示例代码:

package javaenhance;import java.io.FileInputStream;import java.io.InputStream;import java.util.Collection;import java.util.Properties;public class ReflectTest4 {/** * @param args */public static void main(String[] args) throws Exception {// TODO Auto-generated method stub//InputStream is = new FileInputStream("cofig.properties");//InputStream is=ReflectTest4.class.getClassLoader().getResourceAsStream("javaenhance/cofig.properties");InputStream is=ReflectTest4.class.getResourceAsStream("resources/cofig.properties");Properties props = new Properties();props.load(is);is.close();String className = props.getProperty("className");Collection list = (Collection)Class.forName(className).newInstance();        // Set  list = new HashSet();         ReflectPoint  rp = new ReflectPoint(3,3);         ReflectPoint  rp1= new ReflectPoint(5,8);         ReflectPoint  rp2 = new ReflectPoint(3,3);         list.add(rp);         list.add(rp1);         list.add(rp2);         list.add(rp);         System.out.println(list.size());}}


Eclipse会将源文件下的.java文件自动编译成.class放在classpath指定的目录下,而不是.java文件则会把它原封不动的复制到classPath指定的目录下。

1.将.clas文件加载进内存,也可将普通文件加载进内存。

2.如何管理资源和配置文件:

(1) 用绝对路径,一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。

   通过getRealPath()获取到程序安装的目录再加上你所需要的文件的在内部文件目录

(2) 用类加载器加载资源文件:

(3)利用类自己本身的方法getResourceAsStream()

 

第九节 由内省引出JavaBean的讲解

一.概述:

IntroSpector: 主要用于对javabean的操作。

二.什么是JavaBean?

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

三.作用:

如果要在两个模块之间传递多个信息,可以将这些信息封装到一个javaBean中,这种javaBean的实例对象通常被称之为值对象

这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。

四.命名规则:

JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思

即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId中文意思即为获取id,至于你从哪个变量上取,用管吗?

去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。

五.好处:

JDK提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套API操作javaBean比用普通类的方式更方便。

 PropertyDescriptor类:PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性。

构造方法:

public PropertyDescriptor(String propertyName,Class<?> beanClass)

参数:

propertyName- 属性的编程名称。

beanClass- 目标 bean 的 Class 对象。

 

特有方法:

Method getReadMethod();  :获得应该用于读取属性值的方法。

Method getWriteMethod(); :获得应该用于写入属性值的方法。

下面对JavaBean进行简单的内省操作。

思路:

1.创建 PropertyDescriptor的实例对象。传入要操作的属性值,和目标bean的Class对象。

2.通过getReadMethod()或getWriteMethod()方法获取到读取属性值得方法。

3.再通过Method的方法invoke().获得属性值。

六.对JavaBean的复杂内省操作。

思路:

1.通过Introspector的方法getBeanInfo(Class<?> beanClass).

2.在Java Bean上进行内省,了解其所有属性,公开的方法和事件。

3.在通过得到的benaninfo类的getPropertyDescriptors()方法获的PropertyDescriptor数组。

最后通过增强for循环获得自己想要获取的属性值对应的PropertyDescriptor类。

package javaenhance;import java.beans.BeanInfo;import java.beans.IntrospectionException;import java.beans.Introspector;import java.beans.PropertyDescriptor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import org.apache.commons.beanutils.BeanUtils;import org.apache.commons.beanutils.PropertyUtils;public class IntrospectorTest {/** * @param args */public static void main(String[] args) throws Exception{// TODO Auto-generated method stubReflectPoint rp = new ReflectPoint(5,5);String propertyName ="x";        getProperty(rp, propertyName);                         Object value =7;         setProperty(rp, propertyName, value);         System.out.println(rp.getX());         System.out.println(BeanUtils.getProperty(rp, propertyName).getClass().getName());                  BeanUtils.setProperty(rp, propertyName, "9");         System.out.println(rp.getX());         System.out.println(PropertyUtils.getProperty(rp, propertyName).getClass().getName());         BeanUtils.setProperty(rp, "birthday.time","111");         System.out.println(BeanUtils.getProperty(rp, "birthday.time"));        }public static void setProperty(Object rp, String propertyName,Object value) throws IntrospectionException,IllegalAccessException, InvocationTargetException {PropertyDescriptor pd = new PropertyDescriptor(propertyName,rp.getClass());         Method methodSetX = pd.getWriteMethod();                  methodSetX.invoke(rp,value);         }public static Object getProperty(Object rp, String propertyName)throws IntrospectionException, IllegalAccessException,InvocationTargetException {//PropertyDescriptor pd = new PropertyDescriptor(propertyName,rp.getClass());//         Method  methodGetX = pd.getReadMethod();//         Object obj= methodGetX.invoke(rp);//         System.out.println(obj);BeanInfo beaninfo =Introspector.getBeanInfo(rp.getClass());PropertyDescriptor[] pds =beaninfo.getPropertyDescriptors();Object retVal=null;for(PropertyDescriptor pd:pds){  if(pd.getName().equals(propertyName))  {  Method methodGetX = pd.getReadMethod();  retVal = methodGetX.invoke(rp);  System.out.println(retVal);  break;  }}return retVal;}}

七.工具类BeanUtils。

好处:

1. BeanUtils的get和set方法返回的String类型。这样做有什么好处呢?在Java web开发中,用户填入自己的年龄9,传入服务器时就是字符串9.而我们通过java bean设置这个9时,需要把9转成整数。所以BeanUtils提供了这个便利帮我们自动转换成我们所需要的类型。

2. 支持属性的级联操作。人有脑袋,脑袋上面有眼睛,眼睛有眼珠。如果用反射操作就很麻烦。而用BeanUtils工具包会很简单。

3.可以Map集合实现互转。

BeanUtils的扩展:

1.BeanUtils一共分4个包:

Org.APACHE.COMMONS.BEANUTILS.

Org.APACHE.COMMONS.BEANUTILS.converters

Org.APACHE.COMMONS.BEANUTILS.locale

Org.APACHE.COMMONS.BEANUTILS.locale.convert

这个包主要提供用于操作JavaBean的工具类

BeanUtils:JavaBean克隆及属性拷贝

PropertyUtils:JavaBean属性拷贝

ConvertUtils:类型转换

MethodUtils:JavaBean方法调用

ConstructorUtils:构造Bean对象

注意:BeanUtils与PropertyUtils的区别

BeanUtils在对Bean赋值会进行类型转化。举例来说也就是在copyProperty只要属性名相同就算类型不同,BeanUtils也可以进行copy,而PropertyBean则可能会报错。