Java 基础知识--反射

Posted Ruffian-痞子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 基础知识--反射相关的知识,希望对你有一定的参考价值。

反射在Java编程中是很常用的功能,开发和阅读源码时,总能看到反射的身影。这里要强调一下,Java的反射真的很简单很简单,很多人以为Java的反射是一个很高深的知识点,一直不敢去触碰,以至于成为很多程序员的短板。接下来就一起来看看这个简单了一逼的反射机制

Java的反射

反射概述

Java的反射是 在运行状态中 ,对于任何一个,都能知道它的所有属性方法;对于任何一个对象,都能调用它的所有属性方法。这种能够 动态获取类的信息动态调用对象的方法 的特性就是Java的反射机制

简单的说反射就是:动态获取类的信息 和 动态调用对象的方法(或属性)

反射的关键:获取代表字节码文件(.class文件)的Class对象

注: .java 文件通过编译最终生成 .class 文件
注: Class对象代表着 .class(字节码文件) [ 当然要先得到Class对象 ]

类加载简单示例

先简单了解一下类的加载过程

Java编译器会将 .java 文件编译成 .class 文件

1.当程序执行 new User() 时,JVM会查找并加载 User.class 到内存中

2.Jvm 将 .class 加载到内存,自动创建一个Class对象。 Class对象由JVM创建,有且仅有一个,第二次 new User() 不再产生新的 Class 对象

3.一个类对应一个Class对象

反射的本质就是:得到 Class 对象后,反向获取 User 类的各种信息。比如:构造方法,成员变量,方法等

接下来就看看如何使用 Class 对象获取到 User 类的各种信息

Java反射基础API使用

首先,创建一个 User 类

public class User 

这个类本身没有什么意义,用于演示反射示例

1.获取Class对象的方式

1.通过 Object 的 getClass()

Object 类中存在 getClass() 方法,所有对象继承自 Object 因此可以使用 Object 的 getClass() 方法获取 Class 对象

2.通过任意类的“静态”class属性 例如:User.class

3.通过Class类的静态方法 Class.forName(String name)

    public static void main(String args[]) 

        //1.使用Object 的 getClass() 方式获取 Class 对象
        User user = new User();
        Class uClass1 = user.getClass();
        System.out.println("type1:" + uClass1.getName());

        //2.使用任意类的"静态"class 属性
        Class uClass2 = User.class;
        System.out.println("type2:" + uClass2.getName());

        //3.使用Class静态方法 Class.
        try 
            Class uClass3 = Class.forName("demo.reflex.User");//参数是包括包名的完整路径
            System.out.println("type3:" + uClass3.getName());
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        
    

结果:

type1:demo.reflex.User
type2:demo.reflex.User
type3:demo.reflex.User

注意:运行期间,只有一个Class对象存在

以上几种方式都可以获取Class对象。第一个种能获取到对象了,一般就不需要使用反射;第二种需要导入包,依赖性太强;第三种较为常用,也比较符合反射的场景,根据一个完整的类名获取Class对象

2.通过反射获取类构造函数 Constructor

User 类

public class User 

    User(char ch) 
        System.out.println("默认 构造方法 ch:" + ch);
    

    public User() 
        System.out.println("public 构造方法 无参");
    

    public User(String name) 
        System.out.println("public 构造方法 name:" + name);
    

    public User(String name, int age) 
        System.out.println("public 构造方法 name:" + name + " age:" + age);
    

    protected User(int age) 
        System.out.println("protected 构造方法 age:" + age);
    

    private User(boolean flag) 
        System.out.println("private 构造方法 flag:" + flag);
    

User 类构造方法,包括了 public , private , protected , 默认 类型的构造方法

测试使用

    public static void main(String args[]) throws Exception 

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------获取所有公共的构造方法---------");
        Constructor[] consArray = cls.getConstructors();
        for (int i = 0; i < consArray.length; i++) 
            System.out.println(consArray[i]);
        

        System.out.println("-------获取所有的构造方法 包括:public,private,protected,默认类型---------");
        consArray = cls.getDeclaredConstructors();
        for (int i = 0; i < consArray.length; i++) 
            System.out.println(consArray[i]);
        

        System.out.println("-------获取 无参 构造方法并调用---------");
        Constructor cons = cls.getConstructor();
        System.out.println(cons);
        cons.newInstance();

        System.out.println("-------获取 private 构造方法并调用---------");
        cons = cls.getDeclaredConstructor(boolean.class);
        System.out.println(cons);
        cons.setAccessible(true);//暴力访问(忽略访问修饰符)
        cons.newInstance(true);

    

结果输出

-------获取所有公共的构造方法---------
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
-------获取所有的构造方法 包括:public,private,protected,默认类型---------
private demo.reflex.User(boolean)
protected demo.reflex.User(int)
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
demo.reflex.User(char)
-------获取 无参 构造方法并调用---------
public demo.reflex.User()
public 构造方法 无参
-------获取 private 构造方法并调用---------
private demo.reflex.User(boolean)
private 构造方法 flag:true

api解释

1.获取所有构造方法 返回符合要求的 列表

//获取所有"公共的"构造方法  public
public Constructor<?>[] getConstructors()

//获取所有的构造方法 包括: public,private,protected,默认类型 
public Constructor<?>[] getDeclaredConstructors() 

2.获取单个构造方法 返回符合要求的 对象

//获取"公共的"构造方法   
public Constructor<T> getConstructor(Class<?>... parameterTypes)

/**
 *获取任意访问类型的构造方法  
 *
 *parameterTypes:形参类型(记住是类型)  例:int.class ,String.class
 */
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

3.调用构造方法

/**
 *调用构造方法  
 *
 *initargs:构造方法参数
 */
public T newInstance(Object ... initargs)

newInstance() 方法属于 Constructor 类,无参构造方法参数可以 null 或者 不填; 对于调用 private 私有构造方法需要调用 cons.setAccessible(true); 表示打破限制,强制访问,否则出错

3.通过反射获取类成员变量 Field

User类

public class User 

    public User() 
    

    /*成员变量*/
    private int age;
    public String name;
    protected String phone;
    boolean isVip;

    @Override
    public String toString() 
        return "[User name=" + name + ", age=" + age + "]";
    

测试使用

    public static void main(String args[]) throws Exception 

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------获取所有公共的成员变量---------");
        Field[] fieldArray = cls.getFields();
        for (int i = 0; i < fieldArray.length; i++) 
            System.out.println(fieldArray[i]);
        

        System.out.println("-------获取所有的成员变量 包括:public,private,protected,默认类型---------");
        fieldArray = cls.getDeclaredFields();
        for (int i = 0; i < fieldArray.length; i++) 
            System.out.println(fieldArray[i]);
        

        System.out.println("-------获取公共属性---------");
        Field field = cls.getField("name");
        System.out.println(field);

        System.out.println("-------设置属性并查看值---------");
        //根据构造函数 .newInstance() 获取类对象
        Object object = cls.getConstructor().newInstance();
        field.set(object, "Ruffian");
        //验证刚刚设置的属性
        User user = (User) object;
        System.out.println("查看name属性值:" + user.name);

        System.out.println("-------获取私有属性---------");
        field = cls.getDeclaredField("age");
        System.out.println(field);

        System.out.println("-------设置私有属性并查看值---------");
        field.setAccessible(true);
        field.set(object, 18);
        //验证刚刚设置的属性
         System.out.println("查看age属性值:" + user.toString());
    

结果输出:

-------获取所有公共的成员变量---------
public java.lang.String demo.reflex.User.name
-------获取所有的成员变量 包括:public,private,protected,默认类型---------
private int demo.reflex.User.age
public java.lang.String demo.reflex.User.name
protected java.lang.String demo.reflex.User.phone
boolean demo.reflex.User.isVip
-------获取公共属性---------
public java.lang.String demo.reflex.User.name
-------设置属性并查看值---------
查看name属性值:Ruffian
-------获取私有属性---------
private int demo.reflex.User.age
-------设置私有属性并查看值---------
查看age属性值:[User name=Ruffian, age=18]

api解释

1.获取所有属性 返回符合要求的 列表

//获取所有"公共的"属性  public
public Constructor<?>[] getFields()

//获取所有的属性 包括: public,private,protected,默认类型 
public Constructor<?>[] getDeclaredFields() 

2.获取单个属性 返回符合要求的 对象

//获取"公共的"属性
public Field getField(String fieldName)

/**
 *获取任意访问类型的属性
 *
 *fieldName:属性名称  例: name , age 
 */
public Field getDeclaredField(String fieldName)

3.设置属性值

/**
 *1.obj:要设置的字段所在的对象;
 *2.value:要为字段设置的值;
 */
public void set(Object obj,Object value)

field.set(object, "Ruffian"); 表示为属性设置值: user.name=Ruffian

第一个参数:要设置的字段所在的对象
第二个参数:要为字段设置的值

至于上述示例中获取属性值的方式,是为了方便校验通过反射设置值是否成功,一般我们通过反射获取方法,得到属性值。下面看看如何通过反射获取成员方法和调用

4.通过反射获取成员方法 调用方法 Method

User类

public class User 

    public void showName(String name) 
        System.out.println("public showName()  参数name = " + name);
    

    protected void show() 
        System.out.println("public show()");
    

    void showDefault() 
        System.out.println(" showDefault() 默认无参方法");
    

    private String getAge(int age) 
        System.out.println("private getAge() 返回值:字符串  参数:int age = " + age);
        return "age:" + age;
    

使用示例

    public static void main(String args[]) throws Exception 

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------获取所有方法---------");
        //Method[] methodArray = cls.getMethods();
        Method[] methodArray = cls.getDeclaredMethods();
        for (int i = 0; i < methodArray.length; i++) 
            System.out.println(methodArray[i]);
        

        System.out.println("-------调用任意访问类型方法---------");
        //根据构造函数 .newInstance() 获取类对象
        Object object = cls.getConstructor().newInstance();
        Method method = cls.getDeclaredMethod("getAge", int.class);
        method.setAccessible(true);
        Object result = method.invoke(object, 18);
        System.out.println("方法返回值:" + result.toString());
    

结果输出

-------获取所有方法---------
private java.lang.String demo.reflex.User.getAge(int)
public void demo.reflex.User.showName(java.lang.String)
protected void demo.reflex.User.show()
void demo.reflex.User.showDefault()
-------调用任意访问类型方法---------
private getAge() 返回值:字符串  参数:int age = 18
方法返回值:age:18

api解释

1.获取所有方法 返回符合要求的 列表

//获取所有"公共的"方法  public
public Method[] getMethods()

//获取所有的方法 包括: public,private,protected,默认类型 
public Method[] getDeclaredMethods()

2.获取单个方法 返回符合要求的 对象

//获取"公共的"方法
public Method getMethod(String name, Class<?>... parameterTypes)


/**
 *获取任意访问类型的方法
 *
 *name:方法名称  例: getAge , showName 
 *parameterTypes:方法形参类型(记住是类型)  例:int.class ,String.class
 */
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

3.调用方法

/**
 *obj:要调用方法所在的对象
 *args:调用方法所需的参数
 */
public native Object invoke(Object obj, Object... args)

method.invoke(object, 18); 表示调用方法: User user=new User(); user.getAge(18);

第一个参数:

要调用方法所在的对象
如果调用方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null

第二个参数:

调用方法所需的参数
调用方法所需的形参个数为 0,则所提供的 args 数组长度可以为 0 或 null

4.Method 类的一点拓展

了解一个方法可以通过: 方法名称,方法修饰符,返回类型,形参类型/个数

看下示例程序

    public static void main(String args[]) throws Exception 

        //获取Class
        Class cls = Class.forName("demo.reflex.User");

        Method[] methodArray = cls.getDeclaredMethods();
        Method method;
        for (int i = 0; i < methodArray.length; i++) 
            method = methodArray[i];

            String name = method.getName();//方法名称
            int modifiers = method.getModifiers();//方法修饰符
            Class returnType = method.getReturnType();//方法返回类型
            Class[] parameterTypes = method.getParameterTypes();//方法参数类型数组(形参类型)
            System.out.println("方法名:" + name + " 修饰符:" + Modifier.toString(modifiers) + " 返回值类型:" + returnType);

            //方法参数(形参)
            for (int j = 0; j < parameterTypes.length; j++) 
                System.out.println("形参" + j + ":" + parameterTypes[j]);
            
            System.out.println("");
        
    

运行结果:

方法名:showName 修饰符:public 返回值类型:void
形参0:class java.lang.String

方法名:show 修饰符:protected 返回值类型:void

方法名:showDefault 修饰符: 返回值类型:void

方法名:getAge 修饰符:private 返回值类型:class java.lang.String
形参0:int
5.Java 反射的一点场景使用
5.1 反射结合配置文件使用

User类

public class User 

    public void show() 
        System.out.println("show方法被调用...");
    

在 D 盘目录下新建一个 pro.txt 文件

className = demo.reflex.User   //类名完整路径
methodName = show              //方法名

示例代码:

    public static void main(String args[]) throws Exception 

            //获取Class
            Class cls = Class.forName(getValue("className"));
            //获取方法
            Method method = cls.getMethod(getValue("methodName"));

            Object object = cls.getConstructor().newInstance();//获取对象
            //调用方法
            method.invoke(object, null);
    

    /***
     * 根据key获取配置文件中 value
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static String getValue(String key) throws IOException 
        Properties pro = new Properties();//获取配置文件的对象
        File file = new File("D:\\\\pro.txt");//获取文件
        FileReader in = new FileReader(file);//获取输入流
        pro.load(in);//将流加载到配置文件对象中
        in.close();
        return pro.getProperty(key);//返回根据key获取的value值
    

输出结果:

show方法被调用...
5.2 通过反射越过泛型检查

还记得 method.invoke(Object obj, Object... args) 方法吗? 第二个参数表示:调用方法所需参数。这是一个 Object 对象,那么,嘿嘿嘿~~~

示例代码:

    public static void main(String args[]) throws Exception 

        ArrayList<String> list = new ArrayList<>();
        list.add("AAA");
        list.add("BBB");

        /**
         * 获取ArrayList的Class对象,反向的调用add()方法,添加数据
         */
        Class listClass = list.getClass(); //获取Class对象
        //获取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //调用add()方法
        m.invoke(list, false);
        m.invoke(list, 100);

        //遍历集合
        for (Object obj : list) 
            System.out.println(obj);
        

    

输出结果:

AAA
BBB
false
100

上述代码,ArrayList 指定类型 String 如果直接 list.add(100) 则会包类型错误,通过反射调用 m.invoke(obj, obj); 传入其他类型的对象,从而实现越过泛型的检查。当然这只是一个例子,实际编码中,还是需要严格按照规范去写代码

虽然,博文比较长,但是内容真的很简单,纯属无脑式的API调用,起码通过学习反射 API 的使用先了解反射的基础。这也是为什么文章开篇说反射简单了一逼,就是为了鼓舞大家有勇气面对这些知识点

由于本文都是API的简单使用,也都参考网络资料,就不一一附上连接了。

以上是关于Java 基础知识--反射的主要内容,如果未能解决你的问题,请参考以下文章

java反射基础知识反射应用实践

java反射基础知识反射应用实践

Java反射基础

java反射基础知识

JAVA基础知识|反射

java反射基础知识