【Java进阶】Java反射机制初识

本篇博客主要包含以下内容:

  • Class类的使用
  • 方法的反射
  • 成员变量及构造函数的反射
  • 通过反射理解泛型的本质
  • 相关概念

  • 文章开始先打个广告:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。——百度百科

Class类的使用

首先查阅Java的api有下面的对Class的解释:

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。——API

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。

  • 关于面向对象的Java可以说,万物皆对象。 在Java中,每个class都有一个对应的Class对象,也就是说。我们编写一个类,编译完成就生成.class文件中,就代表着一个Class对象,用来表示这个类的类型信息。
  • 获取Class实例对象也就是这个类的类类型,是不能直接创建的,因为Class类的构造方法是私有的。那么获取Class实例及实例操作则有以下几种方式。
    1. 利用.class的方式获取
    2. 已知该类对象,利用getClass()方法获取
    3. 使用Class类的静态方法forName(“类的全称”) ,这不仅表示着类的类类型,还代表了动态加载类

问题引申:什么是动态加载类?
编译时刻加载类就是静态加载类,运行时刻加载类是动态加载类
new创建对象是静态加载类,程序编译时就加载。forName是动态加载类,用到时才加载

最常见的表示这个Class对象的三种方式:程序如下

package reflect;

class Foo{

    void print(){
        System.out.println("this is foo"); 
    };
}
public class ReflectTest1 {

    public static void main(String[] args) {
        //  Foo的实例对象
        Foo foo1 = new Foo();
        //  任何一个类都是Class的实例对象,这个实例对象有三种表示方式

        //  第一种表示-->>任何一个类都有一个隐示静态的成员变量class
        Class c1 = Foo.class;
        //  第二种表示,已知该类对象通过getClass方法获取该类的Class对象
        Class c2 = foo1.getClass();

        /**
         * foo1表示的是Foo的实例对象
         * c1 ,c2表示的是Foo类的类类型(class type),类类型是Class的实例对象
         * c1,c2都代表lFoo类的类类型,一个类只可能是Class类的实例对象
         */
        System.out.println(c1==c2);//  output:true
        System.out.println(c1); // otuput:class reflect.Foo
        System.out.println(c1.getName());//  output:reflect.Foo
        System.out.println(c1.getSimpleName()); //  output:Foo


        //  第三种表示,通过Class的forName("类的全称")方法
        Class c3 = null;
        try {
            c3 = Class.forName("reflect.Foo");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(c2==c3);//  output:true
    }
}

基本的数据类型,void,基本数据类型包装类都 存在类类型,可通过.class方式获取。

通过类的类类型和newInstance()方法获得该类的实例对象 ,前提是本类存在无参的构造函数,代码如下:

       /* 可通过类的类类型创建该类的实例对象---通过newInstance()方法
        * 前提要求,这个类必须要有无参的构造方法
        */
        try {
            Foo foo2 = (Foo) c1.newInstance();
            foo2.print(); //  output:this is foo
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

方法的反射之类成员的获取:

获取方法的信息
java.lang.reflect.Method中封装了对类中方法的操作

package reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectClassUtil{
    /**
     * 打印某个对象所属类的所有成员方法
     * @param obj
     */
    public static void getClassMethodsMessage(Object obj) {
        //  获取此对象所属类的类类型,传递的是谁的对象或是谁子类对象,c就是该类或子类的类类型
        Class c = obj.getClass();

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

        /**
         * Method类,方法对象
         * 一个成员方法就是一个Method对象
         * getMethods()方法获取的是所有public的函数,包括父类继承而来的
         * getDeclaredMethods()获取的是所有该类自己声明的方法,不考虑访问权限
         */
        Method[] ms = c.getMethods();
        for (int i = 0; i < ms.length; i++) {

            // 得到方法的返回值类型的类类型
            Class returnType = ms[i].getReturnType();
            System.out.print(returnType.getName()+"\t");

            // 得到权限修饰符
            String str = Modifier.toString(ms[i].getModifiers());
            System.out.print(str+"\t");

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

            // 获取参数类型-->得到的是参数列表的类类型
            Class[] paramTypes = ms[i].getParameterTypes();
            // 解析参数列表数组
            for (Class class1 : paramTypes) {
                System.out.print(class1.getName()+",");
            }

            System.out.println(")");
        }
    }

    public static void main(String[] args) {
        String s = "hello";
        ReflectClassUtil.getClassMethodsMessage(s);
    }
}

output:
类的名称
返回值类型 | 权限修饰符 | 方法名(参数列表)

类名称是:java.lang.String
boolean public  equals(java.lang.Object,)
java.lang.String    public  toString()
int public  hashCode()
int public  compareTo(java.lang.String,)
int public volatile compareTo(java.lang.Object,)
int public  indexOf(java.lang.String,int,)
int public  indexOf(java.lang.String,)
int public  indexOf(int,int,)
int public  indexOf(int,)
java.lang.String    public static   valueOf(int,)
java.lang.String    public static   valueOf(long,)
java.lang.String    public static   valueOf(float,)
java.lang.String    public static   valueOf(boolean,)
java.lang.String    public static   valueOf([C,)
java.lang.String    public static   valueOf([C,int,int,)
java.lang.String    public static   valueOf(java.lang.Object,)
java.lang.String    public static   valueOf(char,)
java.lang.String    public static   valueOf(double,)
char    public  charAt(int,)
int public  codePointAt(int,)
int public  codePointBefore(int,)
int public  codePointCount(int,int,)
int public  compareToIgnoreCase(java.lang.String,)
……省略更多

获取成员变量的信息:

成员变量也是对象,java.lang.reflect.Field中封装了关于成员变量信的操作。
成员变量一般就有权限修饰符,变量类型,和变量名称。如何获取?在以上程序中加入一个获取成员变量的方法,代码如下:

    /**
     * 获取成员变量的信息
     * @param obj
     */
    public static void getClassFieldMessage(Object obj) {
        Class c = obj.getClass();

        //  获取类名称
        System.out.println("类名称是:"+c.getName());
        /**
         * 获取成员变量信息
         * 成员变量也是对象
         * java.lang.reflect.Field中封装了关于成员变量信的操作
         * getFields()方法获取的是所有的public的成员变量的信息
         * getDeclaredFields()方法获取的是该类自己声明的成员 变量的信息
         */
        Field[] fs = c.getDeclaredFields();
        for (Field field : fs) {
            // 获取每个属性的权限修饰符
            int i = field.getModifiers();
            String str = Modifier.toString(i);
            // 得到成员变量的类型的类类型
            Class fieldType = field.getType();
            String typeName = fieldType.getName();
            // 得到成员变量的名称
            String fieldName = field.getName();
            System.out.println(str+"\t"+typeName+"\t"+fieldName);
        }
    }
// 调用之
String s = "hello";
ReflectClassUtil.getClassFieldMessage(s);

output:
类名称
权限修饰符 | 变量类的类类型 | 变量名

类名称是:java.lang.String
private final   [C  value
private int hash
private static final    long    serialVersionUID
private static final    [Ljava.io.ObjectStreamField;    serialPersistentFields
public static final java.util.Comparator    CASE_INSENSITIVE_ORDER

获取构造函数的信息

通过以上方法和成员变量的获取已经知道,想要获得一个类的所有信息,只要获取到这个类的类类型,那么得到其他的信息就是轻而易举的事情,那么一个类的构造函数信息如何获取?构造方法的特殊性,也决定了他会有有别于普通方法的地方,在以上程序中再加入一个方法,代码如下:

    /**
     * 获取对象所属类构造方法信息
     * @param obj
     */
    public static void getConMessage(Object obj) {
        // 要获取某个类的信息,首先获取类类型
        Class c = obj.getClass();
        //  获取类名称
        System.out.println("类名称是:"+c.getName());
        /**
         * 构造函数也是对象
         * java.lang.Constructor中封装了构造函数的信息
         * getConstructors获取所有的public的构造方法
         * getDeclaredConstructors获取所有的构造方法
         */
        Constructor[] cs = c.getDeclaredConstructors();

        for (Constructor constructor : cs) {
            System.out.print(constructor.getName()+"(");
            // 获取构造函数的参数列表,得到的是参数列表的类类型
            Class[] paramTypes = constructor.getParameterTypes();
            for (Class class1 : paramTypes) {
                System.out.print(class1.getName()+",");
            }
            System.out.println(")");
        }
    }
// 调用之
String s = "hello";
ReflectClassUtil.getConMessage(s);

output:
类名称
方法名(参数列表)

java.lang.String([B,int,int,)
java.lang.String([B,java.nio.charset.Charset,)
java.lang.String([B,java.lang.String,)
java.lang.String([B,int,int,java.nio.charset.Charset,)
java.lang.String([B,int,int,java.lang.String,)
java.lang.String([C,boolean,)
java.lang.String(java.lang.StringBuilder,)
java.lang.String(java.lang.StringBuffer,)
java.lang.String([B,)
java.lang.String([I,int,int,)
java.lang.String()
java.lang.String([C,)
java.lang.String(java.lang.String,)
java.lang.String([C,int,int,)
java.lang.String([B,int,)
java.lang.String([B,int,int,int,)

方法的反射之基本操作

一个方法由什么决定?
方法的名称和方法的参数列表才能唯一决定某个方法.

方法的反射如何操作?
method.invoke(对象,参数列表)

  • 既然是方法的反射,势必的获取方法的信息,那么获取方法信息又必须先获取类的类类型
  • java.lang.reflect.Method里封装了对方法反射的一些操作。包括获取和调用等
  • getMethod获取的是类中public的方法/getDeclaredMethod获取的是自己声明的方法
    代码示例:
package reflect;

import java.lang.reflect.Method;

class A{
    public void print() {
        System.out.println("print():Hello Word");
    }
    public void print(int a,int b) {
        System.out.println("print(int,int):"+(a+b));
    }
    public String print(String a , String b) {
        System.out.println("print(String,String):"+a.toUpperCase()+","+b.toUpperCase());
        return "this return:"+a+b;
    }
}
/**
 * 方法的反射操作
 * @author Administrator
 *
 */
public class MethodReflectTest {
    public static void main(String[] args) {
        // 1.获取方法信息必须先获取类的信息
        A a1 = new A();
        Class c = a1.getClass();

        /**
         * 2.获取方法 名称和参数列表来决定
         * getMethod获取的是public的方法
         * getDeclaredMethod获取的是自己声明的方法
         */
        try {
            // 获取print(int,int)方法
            Method m = c.getMethod("print",new Class[]{int.class,int.class});
            Method m1 = c.getMethod("print",int.class,int.class);
            // m与m1完全可以相互替换使用,两种参数方式都可用

            /**
             *  方法的的反射操作
             *  方法的反射操作是指用m对象,来进行方法的调用,和对象点方法效果一样
             *  方法如果没有返回值则返回null,有返回值返回具体的返回值
             */
            Object obj = m.invoke(a1, 10,20);
            Object obj1 = m1.invoke(a1, new Object[]{10,20});
            System.out.println(obj+"\t"+obj1);

            System.out.println("*************华丽人工分割线**************");
            // 获取print(String,String)方法
            Method m2 = c.getMethod("print", String.class,String.class);

            Object obj2 = m2.invoke(a1, "hello","word");
            System.out.println(obj2);

            System.out.println("*************华丽人工分割线**************");
            // 获取print()方法
            Method m3 = c.getMethod("print");
            // 或者
            Method m4 = c.getMethod("print", new Class[]{});

            Object obj3 = m4.invoke(a1);
            System.out.println(obj3);
        } catch (Exception  e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

output:

print(int,int):30
print(int,int):30
null    null
*************华丽人工分割线**************
print(String,String):HELLO,WORD
this return:helloword
*************华丽人工分割线**************
print():Hello Word
null

成员变量及构造函数的反射

和之前一样,如果需要获取成员变量或者构造函数,必须先获取该类的类类型。故代码如下:

package reflect;

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

class Person{
    public String name;
    private int age;
    static String desc = "不知是帅哥还是美女,反正是一个人呐";
    public Person() {}
    private Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
/**
 * 成员变量及构造函数的反射
 * @author Administrator
 *
 */
public class FieldReflectTest {
    public static void main(String[] args) {
        Class c = null;
        try {
            // 获取此类的类类型
            c = Class.forName("reflect.Person");
            // 通过类类型获取此类实例对象
            Person p = (Person)c.newInstance();

            /**
             * getField获取的是此类的public修饰的属性
             * getDeclaredField获取的是此类的所有属性,不问修饰符
             */

            // 调用private属性
            Field f1 = c.getDeclaredField("age");

            /**
             * 将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应 
             * 该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
             * 【锚点1】
             */

            f1.setAccessible(true);

            // 为属性赋值
            f1.set(p, 22);
            System.out.println(p.toString());

            // 调用public属性
            Field f2 = c.getField("name");
            f2.set(p, "StruggleYang");
            System.out.println(p.toString());

            // 调用static属性
            Field f3 = c.getDeclaredField("desc");
            System.out.println(f3.get(Person.desc));

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

        System.out.println("*************华丽分割线************");
        Class c1 = Person.class;
        /*
         * getConstructor获取public修饰的构造函数
         * getDeclaredConstructor获取所有的包括私有构造函数
         */
        try {
            Constructor con = c1.getDeclaredConstructor(String.class,int.class);
            // 关闭安全访问检查
            con.setAccessible(true);
            // 建立此类实例对象,用其私有的构造函数
            Person p1 = (Person)con.newInstance("StruggleYang",21);
            System.out.println(p1.toString());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }
}

output:

Person [name=null, age=22]
Person [name=StruggleYang, age=22]
不知是帅哥还是美女,反正是一个人呐
*************华丽分割线************
Person [name=StruggleYang, age=21]

【锚点1】:
- 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问
- 由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的

通过反射理解泛型的本质

代码如下:

package reflect;

import java.lang.reflect.Method;
import java.util.ArrayList;

public class MethodReflect {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();

        ArrayList<String> list1 = new ArrayList<String>();

        Class c1  = list.getClass();
        Class c2 = list1.getClass();

        System.out.println("c1==c2?-->"+(c1==c2));//  output:true
        // 为list1添加一个元素
        // list1.add(20); 编译出错 
        list1.add("张三");
        System.out.println(list1.size());
        System.out.println(list1);

        // 反射的操作都是编译之后的操作
        // 编译之后集合的泛型是去泛型化的
        // 验证:通过方法的反射来操作,绕过编译
        try {
            // 拿到集合的add方法
            Method m = c2.getMethod("add", Object.class);
            // list1是String泛型集合,绕过编译。是否能将int型数据添进去?
            m.invoke(list1, 20);
            System.out.println(list1.size());
            System.out.println(list1);

            // 如果此时用foreach遍历则会抛出类型转换异常
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }
}

output:

c1==c2?-->true
1
[张三]
2
[张三, 20]

【结论】
- Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了
- 通过反射操作,可以绕过编译

相关的概念:
什么是编译?
答:将原程序翻译成计算机语言,就是二进制代码,在java中是将.java文件也就是源程序翻译成.class的字节码

什么是编译时?
答:将原程序翻译成计算机语言的过程中,将.java翻译为.class文件的过程

什么是运行时?
答:就是在启动这个程序的时候,在java中是类加载器加载.class文件,并交给jvm处理

什么是编译型语言?
答:将原程序一次性全部转换为二进制代码,然后执行程序
答:转换一句,执行一句,java是既编译又解释的语言

编译型语言和解释型语言的区别:
答:编译型语言效率高,依赖于编译器,但是跨平台差,解释型的效率低,依赖于解释器,但跨平台强

什么是类加载器?
答:类加载器就是JVM中的类装载器,作用就是将编译好的.class字节码运到检查器进行安全检查的,检查通过后开始解释执行

什么是运行时动态加载类?
答:反射就是可以将一个程序(类)在运行的时候获得该程序(类)的信息的机制,也就是获得在编译期不可能获得的类的信息,因为这些信息是保存在Class对象中的,而这个Class对象是在程序运行时动态加载的 它就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成对象。类在运行的时候,可以得到该类的信息,并且 可以动态的修改这些信息,自己能看到自己,跟照镜子一样,class对象是在运行的时候产生的,通过class对象操作类的信息是在运行时进行的,当运行 程序的时候,类加载器会加载真正需要的类,什么是真正需要的呢?就是该类真正起作用,如:有该类的对象实例,或该类调用了静态方法属性等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值