黑马程序员_java高新技术总结【2】(反射)

来源:互联网 发布:java list 合并去重 编辑:程序博客网 时间:2024/06/06 03:08

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

一、反射的概念

反射:

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

        简单一句话:反射技术可以对类进行解剖。

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

       例如:一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。

        表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

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


二、反射的基石Class类

1、
Person类代表人,它的实例对象就是张三,李四这样一个个具体的人, Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
人Person
Java类Class

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

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?
如何得到各个字节码对应的实例对象(Class类型)
类名.class,例如,System.class
对象.getClass(),例如,new Date().getClass()
Class.forName("类名"),例如,Class.forName("java.util.Date");

九个预定义Class实例对象:
参看Class.isPrimitive方法的帮助
包括八种基本类型(byteshortintlongfloatdoublecharboolean)的字节码对象和一种返回值为void类型的void.class
Int.class == Integer.TYPE

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

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

3、Class类中的方法

        static Class forName(String className)

        返回与给定字符串名的类或接口的相关联的Class对象。

        Class getClass()

        返回的是Object运行时的类,即返回Class对象即字节码对象

        Constructor getConstructor()

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

        Field getField(String name)

        返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。

        Field[] getFields()

        返回包含某些Field对象的数组,表示所代表类中的成员字段。

        Method getMethod(String name,Class parameterTypes)

        返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。

        Method[] getMehtods()

        返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。

        String getName()

        以String形式返回此Class对象所表示的实体名称。

        String getSuperclass()

        返回此Class所表示的类的超类的名称

        boolean isArray()

        判定此Class对象是否表示一个数组

        boolean isPrimitive()

        判断指定的Class对象是否是一个基本类型。

        T newInstance()

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


4、通过Class对象获取类实例

        Class类是没有构造方法的, 因此只能通过方法获取类实例对象。之前我们用的已知类,创建对象的做法:

       (1)查找并加载XX.class文件进内存,并将该文件封装成Class对象。

       (2)再依据Class对象创建该类具体的实例。

        (3)调用构造函数对对象进行初始化。如:Person p=new Person();

 现在用Class对象来获取类实例对象的做法:

       (1)查找并加载指定名字的字节码文件进内存,并被封装成Class对象。

      ( 2)通过Class对象的newInstance方法创建该Class对应的类实例。

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

             如:

                     String className="包名.Person";

                     Class clazz=Class.forName(className);

                     Object obj=clazz.newInstance();

//Person类  public class Person {      private String name;      public int age;      public Person(){          System.out.println("Person is run");      }      public Person(String name,int age){          this.age=age;          this.name=name;      }            public String toString(){          return name+":"+age;      }  }   //Class public class CreateClassDemo {      public static void main(String[] args) throws Exception {          createPersonClass();      }      //通过Class对象创建类实例方法      public static void createPersonClass() throws Exception{          //获取Person类的Class对象          String className="Person";          Class clazz=Class.forName(className);          //通过newInstance方法获取类的无参构造函数实例          Person p=(Person)clazz.newInstance();      }  }  


三、
Constructor类


1、Constructor代表某个类中的一个构造方法
2、得到某个类所有的构造方法:
例:
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
3、得到某一个构造方法:
例:
 Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
注:获得方法时要用到类型

4、创建实例对象:
通常方式:String str = new String(new StringBuffer("abc"));
反射方式: String str = (String)constructor.newInstance(new StringBuffer("abc"));
调用获得的方法时要用到上面相同类型的实例对象
利用Constructor类来创建类实例的好处是可以指定构造函数,而Class类只能利用无参构造函数创建类实例对象。

5、Class.newInstance()方法:
例:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

通过Constructor创建Peron类实例对象:
//通过Constructor对象来创建类实例方法  public static void createPersonClass_2() throws Exception{      //获取Person类的Class对象      String className="Person";      Class clazz=Class.forName(className);                 //获取指定构造函数的类实例      Constructor con=clazz.getConstructor(String.class,int.class);      Person p=(Person) con.newInstance("lisi",30);      System.out.println(p.toString());  }  

四、
Field类

Field类代表某个类中的一个成员变量
方法:

 Field getField(String s);//只能获取公有和父类中公有

        Field getDeclaredField(String s);//获取该类中任意成员变量,包括私有

        setAccessible(ture);

        //如果是私有字段,要先将该私有字段进行取消权限检查的能力。也称暴力访问。

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

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

//获取Person对象的成员变量  public static void getPersonField() throws Exception{         Class clazz=Class.forName("Person");      Person p=(Person)clazz.newInstance();                //获取所有的成员变量      Field[] fs=clazz.getFields();      for(Field f:fs){          System.out.println(f);      }                //获取指定的成员变量      Field fage=clazz.getField("age");      Field fname=clazz.getDeclaredField("name");                //显示改变后的值      fage.set(p, 20);      System.out.println(fage.get(p));                //暴力访问私有变量      fname.setAccessible(true);      fname.set(p, "zhangsan");      System.out.println(fname.get(p));  }  

五、Method


1、Method类代表某个类中的一个成员方法
2、得到类中的某一个方法:
例子:     Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
3、调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
4、jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
5、方法

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

        Method[] getDeclaredMethods();//获取本类中所有方法包含私有。

        Method   getMethod("方法名",参数.class(如果是空参可以写null);

        Object invoke(Object obj ,参数);//调用方法

        如果方法是静态,invoke方法中的对象参数可以为null

//获取Person类中的方法  public static void getPersonMethod() throws Exception{      Class clazz=Class.forName("Person");      Person p=(Person)clazz.newInstance();                //获取所有方法      Method[] mes=clazz.getMethods();//只获取公共的和父类中的。      //mes=clazz.getDeclaredMethods();//获取本类中包含私有。      for(Method me:mes){          System.out.println(me);      }                //获取单个方法      Method me=clazz.getMethod("toString", null);      Object returnVaule=me.invoke(p, null);      System.out.println(returnVaule);      }  

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

在写源程序时,并不知道使用者传入的类名是什么,但是虽然传入的类名不知道,而知道的是这个类中的方法有main这个方法。所以可以通过反射的方式,通过使用者传入的类名(可定义字符串型变量作为传入类名的入口,通过这个变量代表类名),内部通过传入的类名获取其main方法,然后执行相应的内容。
问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
package cn.itheima;  //定义一个测试类  class Test{      public static void main(String[] args){          for(String arg : args){              System.out.println(arg);          }      }  }  //用反射方式根据用户提供的类名,去执行该类中的main方法。  import java.lang.reflect.Method;    public class PerformedMain{        public static void main(String[] args) throws Exception {          //普通方式          Test.main(new String[]{"123","456","789"});          System.out.println("-----------------------------");                            //反射方式          String className=args[0];          Class clazz=Class.forName(className);                            Method methodMain=clazz.getMethod("main",String[].class);          //方式一:强制转换为超类Object,不用拆包          methodMain.invoke(null, (Object)new String[]{"123","456","789"});          //方式二:将数组打包,编译器拆包后就是一个String[]类型的整体           methodMain.invoke(null, new Object[]{new String[]{"123","456","789"}});  

六、数组的反射
1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
2、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
Arrays.asList()方法处理int[]和String[]时的差异:
int [] a1 = new int[]{1,2,3};      String [] a2 = new String[]{"a","b","c"}; System.out.println(a1);//[I@4caaf64e    System.out.println(a2);//[Ljava.lang.String;@6c10a234    System.out.println(Arrays.asList(a1));//[I@4caaf64e    System.out.println(Arrays.asList(a2));//[a, b, c]                /* Arrays.asList()方法处理int[]和String[]时的差异。   * 打印Arrays.asList(a1);还是跟直接打印a1是一样的     打印Arrays.asList(a2);就会把a2的元素打印出来。     这是因为此方法在JDK1.4版本中,接收的Object类型的数组,     而a2可以作为Object数组传入。但是a1不可以作为Object数组传入,所以只能按照JDK1.5版本来处理。     在JDK1.5版本中,传入的是一个可变参数,所以a1就被当作是一个object,也就是一个参数,     而不是数组传入,所以打印的结果还是跟直接打印a1一样。  */  

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

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

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


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

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

(1)右击项目File命名一个配置文件如:config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList,等号右边的配置键,右边是值。

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

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

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

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

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

( 3)通过getProperty()方法获取className,即配置的值,也就是某个类名。

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

( 5)执行程序主体功能







































































































0 0