黑马程序员--java基础加强之反射(Reflection)

℡╲_俬逩灬. 提交于 2020-04-07 06:56:36

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

反射

一、反射的基石-->Class类

          1、java类的作用:是用来描述一类事物的共性,有什么属性,没有什么属性,至于属性的值是什么,则由这个类的实例对象来确定的,而不同的    实例对象就有不同的属性值。

          2、Class类的产生:java程序中的各个java类也属于同一类事物,所以也可以用一个类来描述这些事物,这个类就是Class。

               例如:众多的人需要建一个Person类,同理众多的类需要建一个Class类。

二、Class类介绍

          1、创建Class类的的引用:Class class = 字节码(Person.class);

               字节码:每个类编译后会产生一个.class文件,该文件就是字节码。        

               1)类名.class,如:System.class;

               2)对象.getClass(),如:new Date().getClass();

               3)Class.forName("类名"),如:Class.forName("java.lang.String");该方法在反射中常用,用时将类名作为变量传入。        

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ReflectionClass {
    public static void main(String[] args) throws ClassNotFoundException {
        String str = "abc";
        //获取Class类实例对象的三种方式
        Class cls1 = str.getClass();
        Class cls2 = String.class;
        Class cls3 = Class.forName("java.lang.String");
        //三种方式产生的对象相同
        System.out.println(cls1 == cls2);//true
        System.out.println(cls1 == cls3);//true
             
        //判断该类是否是基本数据类型
        System.out.println(cls1.isPrimitive());//false     
        System.out.println(int.class.isPrimitive());//true
        // int和Integer是不同的对象
        System.out.println(int.class == Integer.class);//false
        //Integer.TYPE代表Integer中封装的类型,其他基本数据类型的使用一样
        System.out.println(int.class == Integer.TYPE);//true
        System.out.println(int[].class.isPrimitive());//false
        //判断是否是数组的方法
        System.out.println(int[].class.isArray());//true       
    }
}

       2、九个预定义Class实例对象:8个基本数据类型,加一个void.class;

三、反射

         1、定义:反射其实是将java类中的各种成分映射成相应的类。

               比如每个类可以用Class类的对象表示,同样每个类中的成员变量、函数、包等信息都可以用一个个类来表示。而表示Java类的Class类提供一些方法来获取其中的变量,方法,修饰符,包等信息,这些信息就是用相对应的类的实例对象来表示,如Field(成员变量),Method(方法),Constructor(构造函数),Package(包)等等;

       2、Constructor类(构造方法)

               1) 对象的获取:

                    a、得到某个类中的所有构造方法;

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

                    b、得到某个类中的某个构造方法;

                         //getConstructor(字节码)方法里传的字节码表示编译的时候调用哪个构造方法

                          Constructor c = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

                        //newInstence()括弧里传入的是编译时需要生成的对象,返回的是Object对象,需要向下转型

                          String string = (String) c.newInstence(new StringBuffer("a"));//调用newInstance方法来传入该构造方法对应的参数

                   注意:如果想要得到类中的无参构造方法时,可以直接Class.newInstance()来实现。

         3、Field类(成员变量)

               1) 获取某个类中的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person{
    //定义一个公有的变量
    public int id;
    //定义一个私有的变量
    private int age;
    public Person(int id, int age) {
        super();
        this.id = id;
        this.age = age;
    }  
}
public class ReflectionClass {
    public static void main(String[] args) throws Exception{
        //创建一个Person对象
        Person p = new Person(01,25);
        //获取Penson类中的公有变量:id
        //首先获取p引用属于哪个类,返回一个类对象,然后用该对象获取其自己的成员变量名,返回一个成员变量对象
        Field fId = p.getClass().getField("id");
        //用该成员变量对象获取该类的具体哪个对象的变量值,想获取哪个对象的就将该对象传入,比如在建个对象p2,就传入p2
        System.out.println(fId.get(p));
        //获取类中的私有变量,方法不同
        Field fAge = p.getClass().getDeclaredField("age");
        //设置访问权限为true,如果不设置运行时会报错
        fAge.setAccessible(true);
        System.out.println(fAge.get(p));
    }
}

              2) 用反射将一个类中的字符串类型的成员变量中的字母替换

                将下面Test类中的字符串中的'a'替换为'b'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Test {       
    public String str1 = "ababababababa";  
    public String str2 = "bbbbbbbaaaaaa";  
    public String str3 = "aaaaaaabbbbbb";  
       
    public Test() {}
       
    public String toString(){
        return " str1:"+str1 +" str2:"+ str2 +" str3:"+ str3;
    }  
}
   
public class ReflectionClass {
    public static void main(String[] args) throws Exception{
        Test t = new Test();
        method(t);
        System.out.println(t);         
    }
    //定义一个方法,实现替换一个类中字符串的字符
    public static void method(Object obj) throws Exception{
        //获取该类的全部成员变量,返回一个Field类型的数组
        Field [] fields = obj.getClass().getFields();
        //遍历数组
        for (Field field : fields) {           
            //判断是否是String类型的变量,此处用==来判断,因为他们指向同一字节码。
            if(field.getType() == String.class){
                //如果是,得到该变量的值
                String oldStr = (String) field.get(obj);               
                //替换
                String newStr = oldStr.replace('a', 'b');
                //将替换完的字符串设置到该成员变量中
                field.set(obj, newStr);
            }
        }
    }  
}

         4、method类(方法)

               1) 获取Method对象,调用某个类的方法.例:获取String类中的charAt()方法

                   //定义一个字符串。如果类里已经有了String类的字节码,那么下面可以直接使用String.class

                   String str1 = "abc";

                   //获取Method对象,getMethod("类中的某个方法","该方法所需的参数类型的字节码,可以是多参数")

                   Method method = Class.forName("java.lang.String").getMethod("charAt",int.class);

                   //将给方法作用到某个对象上,invoke(该方法作用于哪个对象,该方法操作的参数对应int.class);

                   //如果想要调用某个静态方法,那么invoke(null,该静态方法对应的参数);

                   method.invoke(str1,1);//也可以表示为method.invoke(str1,new Object [] {1});

               2) 调用某个类的main方法

                   a、普通方法:类名.main(new String [] {"111","222","333"});

                   b、反射方法:为什么要用反射调用?当某个程序执行过程中,需要调用其他类的main方法,但是不知道该main所属的类名,只能将该类名                                                                                                                                    作为参数传入,这是需要用反射代码来完成

               需求:用反射自定义一个方法调用某个类的main方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//定义一个Test类,提供main方法
class Test {       
    public static void main(String [] args){
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}
//该类调用Test中的main方法
class ReflectionClass{
    public static void main(String [] args) throws Exception{
        //普通方法,当知道要main方法属于Test类时
        //Test.main(new String [] args {"111","222","333"});
        //反射方法,运行中不知道调用Test类中的main方法
        //定义一个字符串变量,接收位未知的类名
        String className = args[0];
        //获取Method对象
        Method mainMethod = Class.forName(className).getMethod("main",String [].class);
        //调用该静态方法,并传入参数
        mainMethod.invoke(null, (Object)new String []{"111","222","333"});
        /*代码详解:关于(Object)new String []{"111","222","333"}为什么要向上转型,首先invoke()需要传入两个参数,第一个就是需要调用的类的对象或者null;
         第二个需要传入的是main方法的参数,该参数在1.4版本前是Object数组,数组中的每个元素对应一个main方法的参数。1.5版本后有了可变参数,就需要传入Object类型的数组,
         当我们传入一个String类型的数组后,编译器是兼容1.4的,它会按1.4的语法进行处理,将数组中的每个元素拆出来作为参数,所以会产生参数个数不匹配异常。
         解决办法就是将该数组向上转型成Object,或者new Object [] {{new String [] {"111","222","333"}}},将String数组作为一个整体传入Object数组中。*/
    }  
}

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

              1) 通过代码来说明数组与Object的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ReflectionClass{
    public static void main(String [] args){
        int [] arr1 = new int[]{1,2,3};
        int [] arr2 = new int[4];
        int [][] arr3 = new int[][]{{1,2,3},{4,5,6},{7,8,9}};
        String [] arr4 = new String[]{"a","b","c"};
         
        System.out.println(arr1.getClass() == arr2.getClass());//true,说明两个数组是同一份儿字节码
        //System.out.println(arr1.getClass() == arr3.getClass());//编译不通过,说明不是同一份儿字节码
        //System.out.println(arr1.getClass() == arr4.getClass());//同上,说明类型相同且具有相同维度的数组用的是同一份儿字节码
        System.out.println(arr1.getClass().getName());//[I  代表Integer的数组,详见Class类中的getName方法介绍
        System.out.println(arr4.getClass().getName());//[Ljava.lang.String
        System.out.println(arr1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(arr4.getClass().getSuperclass().getName());//java.lang.Object说明每个数组的父类都是Object
         
        //通过以上说明下面代码成立
        Object obj1 = arr1;
        Object obj2 = arr4;
        Object [] obj3 = arr3;
        Object [] obj4 = arr4;
        //但是下面代码就不成立了
        //Object [] obj5 = arr1;//改行代码编译不通过,说明基本数据类型的一维数组不能传入Object[]中,但是String类型的可以
         
        //打印数组中的内容
        //对于字符串的数组,可以转换成List列表并且能直接打印出来内容
        System.out.println(Arrays.asList(arr4));//[a, b, c]
        //而对于整数的数组来说,只能打印出地址值
        System.out.println(Arrays.asList(arr1));//[[I@5e55ab]
        System.out.println(Arrays.asList(arr3));//[[I@15093f1, [I@120bf2c, [I@e6f7d2]
        /*上述情况差异的原因:因为1.4版本之前,asList()方法需要传入的参数是Object[],String[]与Object类型匹配,所以可以将String[]中的每个字符串作为一个Object对象取出来打印,类似于161行代码。
        int[] 在191行证明不匹配与Object[],只能用1.5的asList()方法来处理,此方法传入的是可变参数的Object对象,而int[]只能作为一个Object对象传入,所以只能打印该数组的地址值。
    }
}

              2)反射在数组的应用

              需求:定义一个打印Object对象的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ReflectionClass{
    public static void main(String [] args){
        //printObject(obj);
    }
    public static void printObject(Object obj){
        //获取该对象的字节码
        Class cl = obj.getClass();
        //如果是数组,打印出数组中的每个元素,否则,直接输出
        if (cl.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);
        }
    }
}

四、反射的作用:实现框架功能

         1、什么是框架

              开发商盖好房子,卖给用户,由用户自己装好门窗空调等,房子就是框架,用户想要用框架,就得把门窗插入到开发商提供的框架中。

              框架和工具类是有区别的,框架是要调用用户提供的类,好比门窗。而工具类是被用户调用的类,好比门上的锁。

       2、框架要解决的核心问题

              因为在写框架的时候,无法知道要调用哪个类,所以,程序无法直接new某个类的实例对象,只能用反射来实现了。

              需求:写一个小框架,用反射获取对象.该对象定义在配置文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ReflectionClass{
    public static void main(String [] args) throws Exception{
        //创建一个读取流,关联配置文件
        InputStream is = new FileInputStream("config.properties");
        //创建properties对象
        Properties p = new Properties();
        //加载文件中的内容
        p.load(is);
        is.close();
        //获取配置信息中键值对的值,得到类名
        String className = p.getProperty("className");
        //创建该类的对象
        Collection collections = (Collection) Class.forName("className").newInstance();
        collections.add("abc");
        collections.add("bbc");
        collections.add("abc");
        System.out.println(collections.size());
    }
}

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!