黑马程序员——java基础学习--反射

来源:互联网 发布:java jframe重绘 编辑:程序博客网 时间:2024/06/04 18:06
------- android培训、java培训、期待与您交流! ----------


1、反射应用概述

    Java反射机制是在运行状态中,对于任意一个类都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。简单一句话:反射技术可以对类进行解剖。

  一个已经可以使用的应用程序如何使用后期出现的功能类呢?常用的作法是提供一个配置文件,供以后实现此程序的类来扩展功能,对外提供配置文件,让后期出现的子类直接将类名字配置到配置文件中即可。该应用程序直接读取配置文件中的内容,查找和给定名称相同的类文件,进行加载这个类、创建该类的对象、调用该类中的内容等操作。

  应用程序使用的类不确定时,可以通过提供配置文件,让使用者将具体的子类存储到配置文件中,然后该程序通过反射技术,对指定的类进行内容的获取。

  反射技术大大提高了程序的扩展性。
2、反射的基石—Class类

    所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class类,是描述字节码文件的对象。

 1)Class类属性有field(字段)、method(方法)、construction(构造函数)。

  field中有修饰符、类型、变量名等复杂的描述内容,因此也可以将字段封装称为一个对象,用来获取类中field的内容,这个对象的描述叫Field;同理,方法和构造函数也被封装成对象Method、Constructor。要想对一个类进行内容的获取,必须要先获取该字节码文件的对象,该对象是Class类型,每一个字节码就是class的实例对象。

    字节码:一个类被编译成class文件放在硬盘上以后,就是一些二进制代码,当源程序中用到类时,首先要从硬盘把这个类的那些二进制代码加载到内存中里面来,再用这些字节码去复制出一个一个对象。

  2)Class与class的区别:

class是Java中的类,用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则由此类的实例对象确定,不同的实例对象有不同的属性值。Class指的是Java程序中的各个Java类是属于同一类事物,都是Java程序的类,这些类称为Class。Class是Java程序中各个Java
类的总称;它是反射的基石,通过Class类来使用反射。

  3)获取Class对象的三种方式:

A、通过对象的getClass()方法获取:对象.getClass()

  如:Class cla = new Person().getClass();

B、类名.class。因为任何数据类型都有一个静态的属性class,这个属性可以直接获取到该类型对应的Class对象。

  如:Class cla = Person.class; 

C、Class.forName(类名)。这种方式只要知道类的名称,不需要使用该类和调用其具体的属性和行为,就可以获取到Class对象。

  如:Class cla = Class.forName("包名.Person");

 这种方式仅知道类名就可以获取到该类字节码对象的方式,更有利于扩展。
  4)九个预定义的Class:

 A、八种基本类型(byte、short、int、long、float、double、char、boolean)的字节码对象和一种返回值为void类型的void.class。

 B、TYPE表示基本类型int的Class实例,Integer.TYPE是Integer类的一个常量,它代表此包装类型包装的基本类型的字节码,所以和int.class是相等的。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示。
  5)只要是在源程序中出现的类型都有各自的Class实例对象,如int[].class,数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型的。
  6)Class类中的方法:

 staticClass forName(String className):返回与给定字符串名的类或接口相关联的Class对象;

 ClassgetClass():返回的是Object运行时的类,返回Class对象即字节码对象;

 ConstructorgetConstructor():返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法;

 FieldgetField(String name):返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段;

 Field[]getFields():返回包含某些Field对象的数组,表示所代表类中的成员字段;

 MethodgetMethod(String name,Class… parameterTypes):返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法;

 Method[]getMehtods():返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法;

 StringgetName():以String形式返回此Class对象所表示的实体名称;

 String getSuperclass(): 返回此Class所表示的类的超类的名称;

 booleanisArray():判定此Class对象是否表示一个数组;

 booleanisPrimitive():判断指定的Class对象是否是一个基本类型;

 TnewInstance():创建此Class对象所表示的类的一个新实例。

  7)通过Class对象获取类实例

    Class类没有构造方法,只能通过方法获取类实例对象。用Class对象来获取类实例对象的方法:

A、查找并加载指定名字的字节码文件进内存,并被封装成Class对象;

B、通过Class对象的newInstance方法创建该Class对应的类实例;

C、调用newInstance()方法会去使用该类的空参数构造函数进行初始化。

例如:

  StringclassName = "包名.Person";

  Classcla = Class.forName(className);

  Objectobj = cla.newInstance();

    以前我们用已知类创建对象的做法:

A、查找并加载:类名.class文件进内存,将该文件封装成Class对象;

B、依据Class对象创建该类具体的实例;

C、调用构造函数对对象进行初始化。

例如:Person p = new Person();
3、反射

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

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。

那么得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。 

4、Constructor类

  1)概述

  如果指定的类中没有空参数的构造函数,或者要创建的类对象需要通过指定的构造函数进行初始化,这时就不能使用Class类中的newInstance()方法了。既然要通过指定的构造函数进行对象的初始化,就必须先获取这个构造函数。

    Constructor就代表某个类的构造方法。

  2)获取构造方法:

    A、得到这个类的所有构造方法:

       如:Constructor[] cons =

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

B、获取某一个构造方法:

如:Constructor con=
                 String.Class.getConstructor(StringBuffer.class);

  3)创建实例对象:

  A、通常方式:String str = new String(new StringBuffer(“abc”));

  B、反射方式:String str = con.newInstance(new StringBuffer(“abc”));

  注意:

    A、创建实例时newInstance方法中的参数列表必须与获取Constructor类中getConstructor方法中的参数列表一致;

    B、newInstance():构造出一个实例对象,每调用一次就构造一个对象;

   C、利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。

5、Field类

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

  1)方法

  Field getField(String s):返回Field对象,只能获取公有和父类中公有;

  Field getDeclaredField(String s):获取该类中指定已声明字段;

  setAccessible(ture):如果是私有字段,要先将该私有字段进行取消权限检查的能力,也称暴力访问;

  set(Object obj, Object value):将指定对象变量上Field对象表示的字段设置为指定的新值;

  Object get(Object obj):返回指定对象上Field表示的字段的值。 

6、Method类

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

调用某个对象身上的方法,要先得到方法再针对某个对象调用。变量使用方法,是方法本身知道如何实现执行的过程,也就是方法的对象调用方法,才执行了方法的每个细节的。

  1)方法

  Method[] getMethods():只获取公共和父类中的方法;

  Method[] getDeclaredMethods():获取本类中包含私有的方法;

  Method getMethod("方法名",参数.class(空参写null));

    Object invoke(Object obj ,参数):调用方法,如果方法是静态,invoke

方法中的对象参数可以为null。

    例如:

      获取某个类中的某个方法:(如String str =”abc”)

   通常方式:str.charAt(1)

   反射方式:Method charAtMethod =

Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

    charAtMethod.invoke(str,1);

    如果传递给Method对象的invoke()方法的第一个参数为null,说明Method对象对应的是一个静态方法。

  2)用反射方式执行某个main方法:

  在写源程序时,并不知道使用者传入的类名是什么,虽然传入的类名不知道,但是知道的是这个类中的方法有main这个方法,所以可以通过反射的方式,通过使用者传入的类名获取其main方法,然后执行相应的内容。可定义字符串型变量作为传入类名的入口,通过这个变量代表类名。

  启动Java程序的main方法的参数是一个字符串数组(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,而jdk1.5要兼容jdk1.4的语法,所以会按jdk1.4的语法进行处理;当把一个字符串数组作为参数传递给invoke方法时,会把数组打散成为若干个单独的参数。

所以,在给main方法传递参数时:

      不能使用代码mainMethod.invoke(null,new String[]{“xxx”});

    解决办法有两种:

   A、mainMethod.invoke(null,newObject[]{new String[]{"xxx"}});

   B、mainMethod.invoke(null,(Object)newString[]{"xxx"});

  这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了。 

注意:此示例用eclipse运行时,需要添加执行的类名:

RunAs——>RunConfigurations——>Arguments——>Program arguments——>添加类名(如cn.itheim.Test)。

    配置文件:

   usb.properties

   usb1=cn.itheima.test.MouseUSB

   usb2=cn.itheima.test.KeyUSB

 7、数组的反射

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

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

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

   4)无法得到某个数组的具体类型,只能得到其中某个元素的类型:

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

   Array.getLength(Object obj):获取数组的长度

   Array.get(Object obj, int x):获取数组中的元素 

8、HashCode

    覆写hashCode()方法:只有存入的是具有hashCode算法的集合,覆写hashCode()方法才有价值。

   1)HashCode算法:有一个集合,把这个集合分成若干个区域,每个存进来的对象,可以算出一个hashCode值,根据算出来的值,就放到相应的区域中去,当要查找某一个对象,只要算出这个对象的hashCode值,然后到相应的区域中去寻找,看是否有与此对象相等的对象,这样就提高了查找的性能。

   2)如果没有复写hashCode方法,对象的hashCode值是按照内存地址进行计算的,这样即使两个对象的内容是相等的,若存入集合中的内存地址值不同,则hashCode值也不同,被存入的区域也不同,所以两个内容相等的对象,就可以存入集合中。所以,如果两个对象equals相等的话,应该让他们的hashCode值也相等,如果对象存入的不是根据hash算法的集合,就不需要复写hashCode方法。

   3)当一个对象存储进HashSet集合以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则对象修改后的哈希值与最初存储进HashSet集合时的哈希值就不同了;在这种情况下,调用contains方法或者remove方法,来寻找或者删除这个对象的引用,就会找不到这个对象,从而导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。

    内存泄漏就是某些对象不再被使用了,还占用着内存空间未被释放,则当程序不断增加对象,修改对象或者删除对象,内存就会用光,这就导致内存泄漏。

9、反射的作用

反射的作用:实现框架的功能。
   1)简单框架程序的步骤:
      A、右击项目File命名一个配置文件如:config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList,等号左边是键,右边是值。

 B、代码实现,加载此文件:

   ①将文件读取到读取流中,要写出配置文件的绝对路径;

    如:InputStream is=new FileInputStream(“配置文件”);

   ②用Properties类的load()方法将流中的数据存入集合;

   ③关闭流。

 C、通过getProperty()方法获取className,即配置的类名;

 D、用反射的方式,创建对象newInstance();

 E、执行程序主体功能。

  2)类加载器

    类加载器是将.class的文件加载进内存,也可将普通文件中的信息加载进内存。

    A、文件加载:eclipse会将源程序中的所有.java文件编译成.class文件,然后放到classPath指定的目录中去,并且将非.java文件复制到.class指定的目录中,在运行的时候,执行的是.class文件;将配置文件放到.class文件目录中一同打包,类加载器就会一同加载。

    B、资源文件加载:用getClassLoader()方法获取类加载器,然后用类加载器的getResourceAsStream(Stringname)方法,将配置文件(资源文件)加载进内存。利用类加载器来加载配置文件,需把配置文件放置的包名一起写上,这种方式只有读取功能。

  C、Class类也提供getResourceAsStream方法来加载资源文件,其实它内部就是调用了ClassLoader的方法;,配置文件是相对类文件的当前目录的,用这种方法,配置文件前面可以省略包名。

    D、配置文件的路径问题:

  a、对配置文件修改是需要要储存到配置文件中的,那么就要得到它的绝对路径才行,因此,配置文件要放到程序的内部,通过getRealPath()方法运算出来具体的目录,而不是内部编码出来的。

     b、如果配置文件和classPath目录没关系,就必须写上绝对路径,如果配置文件和classPath目录有关系,那么可写相对路径。


0 0
原创粉丝点击