Java反射机制笔记

很多时候,java程序运行中,我们需要在运行时了解类的信息,得到类的实例,并且进而继续得到类的方法,构造函数,权限,变量以及其他信息。这时候我们需要用到一门技术,java反射

反射说白了,就是把我们的一些文件,一些字符串,一些地址上具体的配置信息,能够把他们动态的在运行期实例化,并且我们能够操作这些实例。这就是反射。

以下几段话是我学习Android的思考,只是想学习反射的同学可以直接跳到Class的使用

我学习Android开发的时候,我就在想,我们在layout文件下写的xml布局文件,他是文本文件,是一些配置信息吧。既然万事万物皆对象,那我们的手机界面应该是一个类。
xml布局文件有LinearLayout布局,那是不是有一个LinearLayout类?我在xml布局文件那里配置

android:layout_width="match_parent"
android:layout_height="match_parent"

就能让这个LinearLayout的宽高匹配父布局。那么,如果真的有LinearLayout类的话,会不会有个方法类似于setWidth()让我设置宽高?事实上,有LinearLayout类,提供了setLayoutParams()方法让我们设置布局参数,当然这个方法能设置宽高

那,我们在xml布局文件写的配置信息,在手机界面上能够显示那么丰富的效果,有按钮,有文本,有列表。这是怎么做到的?其实,这就是用到反射,把xml布局文件写的配置信息,动态的实例化,比如Button按钮,TextView文本,在程序运行时刻全都动态实例化。如果没有反射,我们就无法使用(调用)xml布局文件,无法使用到xml布局文件的话,那Android系统怎么加载丰富多彩的界面?
同样的,AndroidManifest文件里配置了很多Activity,都是通过反射来实例化。如果没有反射,也无法使用AndroidManifest里的配置信息。当然也就没有了Android框架了。

Java反射那么重要,我们需要理解了才能够去使用他。那我们就来学习Java反射这门技术

Class类的使用

在面向对象的世界里,万事万物皆对象。

在java的世界里,有两样东西不是面向对象的。基本的数据类型和静态(static)的东西。

比如,int a = 5,他不是面向对象,但是他有包装类(Integer)或者说是封装类,弥补了他。Int a = 5;是直接在栈空间中分配内存。而Integer a = new Integer(5),对象在堆内存中,引用变量a在栈内存。像这样的普通数据类型还好几个,比如boolean与Boolean,char和Character,double与Double等等,他们都不是面向对象

还有是就是java静态的东西,他不属于某个对象,是属于类的
现在,除去以上不是面向对象的。

那么我们写的类,类是不是对象呢?类是谁的对象呢?类是哪个类的对象呢?
我们写的每一个类它也是对象,类是对象,类是java.lang.Class类的实例对象。

我写的一个Student类,他是不是对象,是一个对象。谁的对象?Class类的实例对象

public class Student {

}

注意,我们上面的程序class,c是小写,是关键字。在声明java类时使用。而我们说的java.lang.Class类的C是大写,他是一个类,因为我们定义一个类,类名的首字母是大写的呀。也可以这么理解:

现在有一个类,他的名字就叫Class。java.lang.Class类

Class类能不能new出实例对象呢?这个对象到底如何表示呢?
我们按住ctrl+鼠标左键,追踪Class这个类,发现他的构造方法是私有的,还给出了注释,只有java虚拟机才能创建Class对象,如下所示:

    /*
     * Constructor. Only the Java Virtual Machine creates Class
     * objects.
     */
    private Class() {}

既然不能通过调用构造方法,new出实例,那么Class类的实例对象如何表示呢?

接下来我们来学习Class类的使用

我们打开eclipse先新建一个Student类

public class Student {

    public void introduce(){
        System.out.println("我是一名大三学生");
    }

}

在新建一个demo,我们很简单的将Student类的实例对象表示出来

public class ClassDemo {
    public static void main(String[] args){
        //student1表示Student的实例对象
        Student student1 = new Student();       
    }
}

那么我们说到,Student这个类,也是一个实例对象,是Class类的实例对象

——》紧接上面提到的问题:Class类的实例对象如何表示呢?
任何一个类都是Class类的实例对象(这句话可以心里念三遍)。这个实例对象有三种表示方式:

第一种表示方式 —>通过类名调用静态成员变量class。 类名.class

Class class1 = Student.class;

这实际在告诉我们任何一个类都有一个隐含的静态成员变量class

第二种表示方式 —> 已经知道该类的对象,通过调用getClass方法。object.getClass()

Class class2 = student1.getClass();

第三种表达方式 —> Class类调用静态方法forName(“类的全称”)。
Class.forName(“类的全称”)

Class class3 = null;
    try {
        class3 = Class.forName("com.xyh.reflect.Student");
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

万一我们找不到这个类呢?这种方法会抛出ClassNotFoundException异常
好,现在我们已经将Class类的实例对象表示出来了。

class1, class2,class3 表示了Student类的类类型(class type)。
这里的三种表示方式,类名.class,object.getClass(),Class.forName(“类的全称”),是指哪个类调用.class返回的就是哪个类的类类型,哪个对象调用getClass()返回的就是这个对象所属类的类类型,Class.forName()同理

我是这么理解类类型的,类类型是某个类的另一种表示方式,另一种类型。他们大有不同,却又息息相关。(这有点绕口)
下面我们会学到用类类型来创建对象,如:通过class1对象调用newInstance()方法,需要进行一下强转

Student student2 = (Student) class1.newInstance();

这种通过类类型创建Student类的实例对象,我们也可以通过new Student()来创建对象,两种方式都可以创建对象嘛!所以我把Student的类类型理解成了Student类的另一种表示方式,
类也是对象,是Class类的实例对象
这个Class类的对象,我们称为该类的类类型

我们看到下面这行代码

System.out.println(class1 == class2);

输出:true

结论:不管class1 ,class2都代表了Student类的类类型,一个类只可能是Class类的一个实例对象,也就是唯一,只有一个

我们完全可以通过类的类类型创建该类的实例对象 —>
比如,可以通过class1,class2,class3创建Student类的实例对象

    try {
        //前提是需要有无参数的构造方法
        Student student2 = (Student) class1.newInstance();
        //student2调用方法验证一下
        student2.introduce();

    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } 

输出:true
我是一名大三学生

| 以下才是核心知识点 |
| 以下才是核心知识点 |
| 以下才是核心知识点 |

动态加载类

Class.forName(“类的全称”)
不仅表示了类的类类型,还代表了动态加载类。那么,什么是动态加载类,什么是静态加载类

编译时刻加载类是静态加载类,运行时刻加载类是动态加载类

我一开始写Java程序,是在Notepad++写的,写好后后通过命令行输入javac 类名.java编译,通过java 类名,执行程序。好怀念,后来我使用了eclipse写java,我就在想,为什么这些IDE能够做到:我一写好程序,点击运行就ok,他一定做了很多功能的封装,(比如我一点击运行,eclipse就执行了javac和java命令吧),不管怎样。IDE方便了开发者,而且他检错很好,比如下图
Java反射机制笔记_第1张图片
我现在Office类需要使用Word类的实例对象,但是我并没有定义这个Word类

如果我们使用Notepad++就没有那么好了,并没有提示。不过却能让我们更好的理解 动态加载类 这个知识点

好,我们通过Notepad++写一段程序回忆下

public class Office {
    public static void main(String[] args){

        //new创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类
        if("Word".equals(args[0])){
            Word word = new Word();
            word.open();
        }
        if("Excel".equals(args[0])){
            Excel excel = new Excel();
            excel.open();
        }

    }

}

我们知道这样编译肯定会报错的。因为我们现在没有声明Word类和Excel类。此时,我们不禁会想,Word一定用到吗?Excel一定用到吗?
Java反射机制笔记_第2张图片

那我们现在声明一个Word类

public class Word{

    public void open(){
        System.out.println("Word...Open");
    }

}

Java反射机制笔记_第3张图片
现在只报两个错了,这个程序有什么问题? Word类已经有了,我现在就想用Word,这个程序能跑吗?不能,因为程序一直会报找不到Excel类的错误,如果现在有一百个类,有一个类找不到或者出问题,全部都用不了。而且一百个类,每一个都还需要用if语句去判断

思路:我们这段程序实现的是静态加载。是在编译时加载。
而我们希望写的程序是可以扩展的,我想要用到哪个类就加载哪个类,即在程序运行时刻动态加载。接着我们新建一个OfficeBetter类如下

public class OfficeBetter{  
    public static void main(String[] args){         
        try{            
            Class class1 = Class.forName(args[0]);
            Word word = (Word)class1.newInstance();
            word.open();    
        }catch(Exception e){            
            e.printStackTrace();            
        }       
    }   
}

Java反射机制笔记_第4张图片
编译通过,运行OK。但是上面的代码也是有问题的,问题如下:

    Word word = (Word)class1.newInstance();
    word.open();

如果我传入的是Excel,PPT呢?那么就无法强转了。还是说我用到哪个类了,在来OfficeBetter这里修改强转的类型??这也太麻烦了吧。所以我们应该提供一个标准,新建一个OfficeAble接口

public interface OfficeAble{
    public void open();
}

以后我们想用到哪个类,就让他实现这个接口就行了。优化后的代码如下

public class Word implements OfficeAble{

    public void open(){
        System.out.println("Word...Open");
    }

}
public class OfficeBetter{  
    public static void main(String[] args){         
        try{            
            Class class1 = Class.forName(args[0]);
            OfficeAble officeAble = null;
            officeAble = (OfficeAble)class1.newInstance();
            officeAble.open();  
        }catch(Exception e){            
            e.printStackTrace();            
        }       
    }   
}

如果我们想用到Excel类,那就新建Excel类实现OfficeAble接口

public class Excel implements OfficeAble{

    public void open(){
        System.out.println("Excel...Open");
    }

}

Java反射机制笔记_第5张图片

呐,我想用到哪个类,我就写一个类去实现OfficeAble这个标准就行了,如图PPT类我是没有去写的,传进去参数才会报错。而那个OfficeBetter程序我更本不用去修改它

功能性的类一般使用动态加载

总结:到这里,我们已经学习了Class类的使用,和动态加载类的知识点,我们需要明白什么是类类型(class type)。
以上的内容我们只是通过类类型来创建该类的实例对象—>类类型.newInstance()
但是,类类型还能够做很多很多事情。

要获取类的信息,首先要获取类的类类型,(class type)


Java反射机制笔记_第6张图片


我们通过eclipse的代码提示,可以看到,类类型很多get方法
在这里我们可以封装一个工具类,去获取类的信息,就叫ClassUtil吧

package com.xyh.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 获取类的信息的工具类
 *
 */
public class ClassUtil {
    /**
     * 以后在任意情况下,我们想获取一个类的信息,首先要获取到这个类的类类型
     */

    /**
     * 
     * 打印类的成员函数信息
     * @param object 
     */
    public static void printMethodMessage(Object object){

        //获取到该类的类类型
        Class class1 = object.getClass();//Object类是一切类的父类,传递的是哪个子类的对象,class1就是该子类的类类型

        //获取类的名称
        System.out.println("类的名称是:"+class1.getName());

        /**
         * 方法也是对象,Method类的对象
         * 一个成员方法就是一个Method对象
         * getMethod()方法获取的是所有的public函数,包括父类继承而来的
         * getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
         * 
         */
        Method[] methods = class1.getMethods();
        for(int i = 0 ;i //得到方法的返回值类型的类类型
            Class  returnType = methods[i].getReturnType();
            System.out.print(returnType.getName()+" ");

            //得到方法的名称
            System.out.print(methods[i].getName()+"(");

            //获取参数类型 ->得到的是参数列表的类型的类类型
            Class[] paramTypes = methods[i].getParameterTypes();            

            for (Class class2 : paramTypes) {
                System.out.print(class2.getName()+",");
            }

            System.out.println(")");

        }

    }


    /**
     * 打印类的成员变量的信息
     * @param object
     */
    public static void printFieldMessage(Object object) {
        /**
         * 成员变量也是对象
         * java.lang.reflect.Field
         * Field类封装了关于成员变量的操作
         * getFields()方法获取的是所有的public的成员变量的信息
         * getDeckaredFields()获取的是该类自己声明的成员变量的信息
         */
        //Field[] fields = class1.getFields();
        Class class1 = object.getClass();
        Field[] fields = class1.getDeclaredFields();
        for(Field field :fields){
            //得到成员变量的类型的类类型
            Class fieldType = field.getType();
            String typeName = fieldType.getName();
            //得到成员变量的名称
            String fieldName = field.getName();
            System.out.println(typeName+" "+fieldName);
        }
    }

    /**
     * 获取对象的构造函数的信息
     * @param object
     */
    public static void printConstructorMessage(Object object){
        Class class1 = object.getClass();
        /**
         * 构造函数也是对象,
         * java.lang.Constructor中封装了构造函数的信息
         * getConstructors()获取所有的public的构造函数
         * getDeclaredConstructors()获取所有的构造函数
         * 
         */
        Constructor[] constructors = class1.getDeclaredConstructors();
        for(Constructor constructor : constructors){
            System.out.print(constructor.getName()+"(");
            //获取构造函数的参数列表->得到的是参数列表的类类型
            Class[] paramTypes = constructor.getParameterTypes();
            for (Class class2 : paramTypes) {

                System.out.print(class2.getName()+",");
            }
            System.out.println(")");
        }
    }

}

这里直接贴出了代码,注释也写的比较详细。还需要补充的是,ClassUtil工具类是欢迎大家去丰满他的,现在只能打印一些类的信息,但是我们开头说到的,反射可以在运行时实例化,获取到类的信息,并能操作这些实例,既然是操作,我们可以看到下图:

Java反射机制笔记_第7张图片

如果我们能得到Constructor构造函数对象,他有很多方法,是可以做很多事的。其他成员函数,成员变量亦同理。

最后一个知识点。

方法的反射操作

方法的反射操作是用method对象来进行方法调用。一般是这样的
method.invoke(对象,参数列表)

我们写一个Computer类

public class Computer {

    public void plus(int a,int b){
        System.out.println(a+b);
    }
    public void plus(String a,String b){
        System.out.println(a.toUpperCase()+","+b.toLowerCase());
    }

}

目标:我们要通过方法的反射操作来获取到plus方法

新建一个MethodDemo类,

package com.xyh.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodDemo {

    public static void main(String[] args){
        /**
         * 方法是类的信息,要获取一个方法,就是获取类的信息,获取类的信息首先要获取这个类的类类型
         * 
         */ 
        Computer computer = new Computer();

        Class class1 = computer.getClass();
        /**
         * 获取方法,名称和参数列表来决定
         * getMethod获取的是public的方法
         * getDelcaredMethod获取自己声明的方法
         */     
        try {
            Method method = class1.getMethod("plus",new Class[]{int.class,int.class});

                //方法的反射操作是用method对象来进行方法调用,和computer.plus(10, 20)调用的效果是一样的

                /**
                 * 其实可以这么理解,这个plus现在变成了方法对象了,那么就是computer这个对象操作plus这个对象
                 * 同样,我们也可以反过来操作,用plus这个方法所代表的对象method,来操作computer这个对象
                 * 
                 * 用plus这个方法对象。那么plus方法对象是谁?
                 * 是method。来操作computer
                 */
            Object object = method.invoke(computer, new Object[]{10,20});
            //Object object = method.invoke(computer, 10,20);

            Method method2 = class1.getMethod("plus", String.class,String.class);
            method2.invoke(computer,"hello","world");

            /**
            * 无参数
             *  Method method3 = class1.getMethod("plus");
             * method3.invoke(computer);
             */ 
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   
    }
}

最后需要补充的是,Class的getMethod方法和getMethods方法是不一样的
Java反射机制笔记_第8张图片

后者getMethods我们在ClassUtil工具类里用到过,返回的是一个数组,需要遍历每一个方法对象。
而getMethod方法,第一个参数传的是方法名,第二个参数是参数列表,是一个数组
方法名称和方法的参数列表才能唯一决定某个方法

你可能感兴趣的:(Java学习篇)