java学习_反射

来源:互联网 发布:单片机芯片 编辑:程序博客网 时间:2024/05/17 06:23

1、Class类(反射的基石)

    1.1、 Java中的类是描述一类事物的共性,表示该类事物具有什么属性,属性的值是什么则有这个类的实例化对象来表示,不同的实例化对象属性值不同。

    Java程序中的各个Java类属于同一类事物,描述该类事物的Java类名就是Class,Class类描述类的名字、类的访问属性、类所属的包名、字段名称的列表、方法名称的列表等。Class的实例对象代表的是类的字节码。

    注意:与小写class关键字不同

    1.2、 得到类的Class对象(字节码)的三种方式  (三种方式得到的字节码是一样的)

        1)Object类的getClass()方法     格式为   对象.getClass

               例如  Person p1=new Person();  

                         Class cls1=p1.getClass();

                弊端:必须要创建该类对象,才可以调用getClass方法        

        2)数据类型的静态属性class     格式为   类名.class   

               例如 Class cls1=String.class  

               弊端:必须先明确该类

        3)  Class类中的静态方法   public static Class forName(String className)

                例如Class.forName("java.util.Date");

                这种方式是用一个字符串,而不是一个具体的类名,可以把这样的字符串配置到配置文件中,扩展性好,开发中常用。

代码示例:

package com.itheima_01;/* * Class类: * 成员变量Field * 构造方法Constructor * 成员方法Method *  * 获取class文件对象的方式: * A:Object类的getClass()方法 * B:数据类型的静态属性class * C:Class类中的静态方法 * public static Class forName(String className) */public class ReflectDemo {public static void main(String[] args) throws ClassNotFoundException {// 方式1Person p = new Person();Class c = p.getClass();Person p2 = new Person();Class c2 = p2.getClass();System.out.println(p == p2);// falseSystem.out.println(c == c2);// true// 方式2Class c3 = Person.class;// int.class;// String.class;System.out.println(c == c3);// 方式3// ClassNotFoundExceptionClass c4 = Class.forName("cn.itheima_01.Person");System.out.println(c == c4);}}
package com.itheima_01;public class Person {private String name;int age;public String address;public Person() {}private Person(String name) {this.name = name;}Person(String name, int age) {this.name = name;this.age = age;}public Person(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}public void show() {System.out.println("show");}public void method(String s) {System.out.println("method " + s);}public String getString(String s, int i) {return s + "---" + i;}private void function() {System.out.println("function");}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + ", address=" + address+ "]";}}

    1.3、 9个预定义的Class:

                 八个基本类型(boolean、byte、char、short、int、long、float 和 double)和 void。

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

                 Integer.TYPE是Integer类的一个常量,Integer.TYPE==int.class。基本数据类型的字节码都可以用与之对应的包装类中的TYPE常量表示。

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

                 数组类型的Class实例对象,可以用Class.isArray()方法判断是否为数组类型。

2、反射

    反射就是把Java类中的各种成分映射成相应的java类。(反射会导致程序性能下降)

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

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

    2.1、Constructor类

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

    获取构造方法:

           public Constructor[] getConstructors():所有公共构造方法

           public Constructor[] getDeclaredConstructors():所有构造方法

           public Constructor<T> getConstructor(Class<?>... parameterTypes)  获取单个构造方法

                      参数表示的是:你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象

    

    示例:

             得到某个类中所有公共的构造函数:

                         Constructor [] cons= Class.forName("java.lang.String").getConstructors();

             得到某一个构造函数

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

代码示例

package com.itheima_02;import java.lang.reflect.Constructor;import com.itheima_01.Person;/* * 通过反射获取构造方法并使用。 */public class ReflectDemo {public static void main(String[] args) throws Exception {// 获取字节码文件对象Class c = Class.forName("<span style="font-family:SimSun;">com.itheima_01.Person</span>");// 获取构造方法// public Constructor[] getConstructors():所有公共构造方法// public Constructor[] getDeclaredConstructors():所有构造方法// Constructor[] cons = c.getDeclaredConstructors();// for (Constructor con : cons) {// System.out.println(con);// }// 获取单个构造方法// public Constructor<T> getConstructor(Class<?>... parameterTypes)// 参数表示的是:你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象Constructor con = c.getConstructor();// 返回的是构造方法对象// Person p = new Person();// System.out.println(p);// public T newInstance(Object... initargs)// 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。Object obj = con.newInstance();System.out.println(obj);// Person p = (Person)obj;// p.show();}}
package com.itheima_02;import java.lang.reflect.Constructor;/* * 通过反射去获取带带参构造方法并使用: * public Person(String name, int age, String address) *  * Person p = new Person("long",27,"上海"); * System.out.println(p); */public class ReflectDemo2 {public static void main(String[] args) throws Exception {// 获取字节码文件对象Class c = Class.forName("com.itheima_01.Person");// 获取带参构造方法对象// public Constructor<T> getConstructor(Class<?>... parameterTypes)Constructor con = c.getConstructor(String.class, int.class,String.class);// 通过带参构造方法对象创建对象// public T newInstance(Object... initargs)Object obj = con.newInstance("long", 27, "上海");System.out.println(obj);}}

package com.itheima_02;import java.lang.reflect.Constructor;/* * 通过反射获取私有构造方法并使用 * private Person(String name){} *  * Person p = new Person("long"); * System.out.println(p); */public class ReflectDemo3 {public static void main(String[] args) throws Exception {// 获取字节码文件对象Class c = Class.forName("com.itheima_01.Person");// 获取私有构造方法对象// NoSuchMethodException:每个这个方法异常// 原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以了。Constructor con = c.getDeclaredConstructor(String.class);// 用该私有构造方法创建对象// IllegalAccessException:非法的访问异常。// 暴力访问con.setAccessible(true);// 值为true则指示反射的对象在使用时应该取消Java语言访问检查。Object obj = con.newInstance("long");System.out.println(obj);}}

    2.2、Field类

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

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

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

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

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

        如果是私有的,就用setAccesiable(true);暴力访问

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

package com.itheima_03;import java.lang.reflect.Constructor;import java.lang.reflect.Field;/* * 通过发生获取成员变量并使用 */public class ReflectDemo {public static void main(String[] args) throws Exception {// 获取字节码文件对象Class c = Class.forName("com.itheima_01.Person");// 获取所有的成员变量// Field[] fields = c.getFields();// Field[] fields = c.getDeclaredFields();// for (Field field : fields) {// System.out.println(field);// }/* * Person p = new Person(); p.address = "上海"; System.out.println(p); */// 通过无参构造方法创建对象Constructor con = c.getConstructor();Object obj = con.newInstance();System.out.println(obj);// 获取单个的成员变量// 获取address并对其赋值Field addressField = c.getField("address");// public void set(Object obj,Object value)// 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。addressField.set(obj, "上海"); // 给obj对象的addressField字段设置值为"上海"System.out.println(obj);// 获取name并对其赋值// NoSuchFieldExceptionField nameField = c.getDeclaredField("name");// IllegalAccessExceptionnameField.setAccessible(true);nameField.set(obj, "long");System.out.println(obj);// 获取age并对其赋值Field ageField = c.getDeclaredField("age");ageField.setAccessible(true);ageField.set(obj, 27);System.out.println(obj);}}

练习:把字母b改成a,实现该功能代码如下

public static void changeStringValue(Object obj) throws Exception {Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {if (field.getType() == String.class) {field.setAccessible(true);String oldvalue = (String) field.get(obj);String newvalue=oldvalue.replace('b', 'a');field.set(obj, newvalue);}}}

    2.3、Method类

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

    大家应通过思考和推理的方式来学习反射中的API,例如,Class.getMethod方法用于得到一个方法,该方法要接受什么参数呢?显然要一个方法名,而一个同名的方法有多个重载形式,用什么方式可以区分清楚想得到重载方法系列中的哪个方法呢?根据参数的个数和类型,例如,Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。

再强调一遍参数类型用什么来表示啊?用Class对象!

    方法:
              Method[] getMethods();//只获取公共和父类中的方法。
              Method[] getDeclaredMethods();//获取本类中包含私有。
              Method   getMethod("方法名",参数.class(如果是空参可以写null));
              Object invoke(Object obj ,参数);//调用方法
              如果方法是静态,invoke方法中的对象参数可以为null。
    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})形式。
    
     用反射方式执行某个类中的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"});
                   这两种方式编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
      Class clazz = Class.forName(arg[0]);//运行时将类名作为参数传递进去,开发中一般写在配置文件中并读取
      Method mMain = clazz.getMethod("main",String[].class);
      mMain.invoke(null,new Object[]{new String[]{"aaa","bbb"}});
      mMain.invoke(null,(Object)new String[]{"aaa","bbb"});
class TestArrayArguments { public static void main(String [] args) {  for(String arg:args)  {   System.out.println("----------" + arg + "----------");  } }}
   2.4、数组的反射
        具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
        代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
        基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
        int[] a1 = new int[3];
        int[]a2 = new int[4];
        int[][]a3 = new int[3][4];
        String[]a4 = new String[3];
        Integer[]a5 = new Integer[3];
        Object[]aObj3 = a5;//可以,非基本类型的一维数组可以当做Object[]类型来使用
        //Object[]aObj4 = a1; //不可以,基本类型的一维数组不能当做Object[]来使用;
        ObjectaObj1 = a1;//可以,基本类型的一维数组可以当做Object类型来使用
        ObjectaObj2 = a4;//可以,非基本类型的一维数组能当做Object来使用;
        System.out.println(a1.getClass()== a2.getClass());//true
        //System.out.println(a1.getClass()== a3.getClass()); //不兼容类型
        //System.out.println(a1.getClass()== a4.getClass()); //不兼容类型
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a3.getClass().getName());//[[I
        //代表数组的Class实例对象的getSupperClass()方法返回的父类为Object类对应的Class。
       System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
       System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
3、反射的作用--实现框架功能
    3.1、什么是框架:
        比如:我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。
        框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。
    3.2、框架要解决的核心问题
        我在写框架(盖房子)的时候,将来的客户可能还在上小学,还不会写程序,那么我写的框架怎样能调用到你以后写的类(门窗)呢?
        因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。
        ----------->用反射技术来解决
    3.3、简单框架程序的步骤:
        1)右击项目File命名一个配置文件如:config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList,等号右边的配置键,右边是值。
        2)代码实现,加载此文件:
               A将文件读取到读取流中,要写出配置文件的绝对路径。
                    如:InputStream is=new FileInputStream(“配置文件”);
               B用Properties类的load()方法将流中的数据存入集合。
               C关闭流:关闭的是读取流,因为流中的数据已经加载进内存。
        3)通过getProperty()方法获取className,即配置的值,也就是某个类名。
        4)用反射的方式,创建对象newInstance()。
        5)执行程序主体功能
代码示例
class ReflecTest2 {public static void main(String[] args) throws Exception {// 应该先直接用ArrayList和HashSet,然后才引入从配置文件读,这样便于学员学习。Properties props = new Properties();// 先演示相对路径的问题// InputStream ips = new FileInputStream("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());}}


0 0
原创粉丝点击