敖胤

绳锯木断,水滴石穿;聚沙成塔,集腋成裘。

导航

Java Reflect(反射)入门学习

反射机制指在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能即Java的反射机制。
Java反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。
    Java反射机制相关的类主要有java.lang包中的Class类和java.lang.reflect包中的Constructor类、Field类、Method类和Parameter类。

1、Class对象

Class类是一个比较特殊的类,它是反射机制的基础,Class类的对象表示正在运行的Java程序中的类或接口,也就是任何一个类被加载时,即将类的.class文件(字节码文件)读入内存的同时,都自动为之创建一个java.lang.Class对象。Class类没有公共构造方法,其对象是JVM在加载类时通过调用类加载器中的defineClass()方法创建的,因此不能显式地创建一个Class对象。通过这个Class对象,才可以获得该对象的其他信息。
Java在将.class字节码文件载入时,JVM将产生一个java.lang.Class对象代表该.class字节码文件,从该Class对象中可以获得类的许多基本信息,这就是反射机制。所以要想完成反射操作,就必须首先认识Class类。

1.1、获取Class对象的三种方式

1.1.1、Object —> getClass();

在Java的Object类中,有一个 获取类信息的本地方法: public final native Class<?> getClass(); 对象可通过此方法获取其类的Class对象

//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());//main.basics.reflect.Student
1.1.2、静态class属性

从Class类的源码注释:

/* 
* Instances of the class {@code Class} represent classes and
* interfaces in a running Java application.  An enum is a kind of
* class and an annotation is a kind of interface.  Every array also
* belongs to a class that is reflected as a {@code Class} object
* that is shared by all arrays with the same element type and number
* of dimensions.  The primitive Java types ({@code boolean},
* {@code byte}, {@code char}, {@code short},
* {@code int}, {@code long}, {@code float}, and
* {@code double}), and the keyword {@code void} are also
* represented as {@code Class} objects.
*/

可知,任何数据类型(包括基本数据类型)以及void关键字,在JVM中,都是Class对象,而Java中的对象都有一个“静态”的class属性。

/第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个 
System.out.println(void.class);// void
System.out.println(int.class);// int
1.1.3、静态方法(常用)

通过Class类的静态方法Class.forName(String className) 可以由字符串获取一个Class对象。

//第三种方式获取Class对象
try {
    Class stuClass3 = Class.forName("main.basics.reflect.Student");//注意此字符串必须是类的全路径,就是带包名的类路径,包名.类名
    System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

小结:

  • 在运行期间,一个类,只有一个Class对象产生。
  • 三种方式常用第三种。对于第一种,已经有了对象了,就不需要反射了。第二种需要导入类的包,依赖太强,不导包就抛编译错误,而第三种,一个字符串可以传入也可写在配置文件中等多种方法,非常灵活。

2、动态获取Class对象的信息

通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;

2.1、构造方法

2.1.1、批量获取

public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

2.1.2、获取单个

public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;

2.1.3、调用构造方法

Constructor-->newInstance(Object... initargs)

//1.加载Class对象
Class clazz = Class.forName("main.basics.reflect.Student");
//2.获取所有公有构造方法
System.out.println("**********************所有公有构造方法*********************************");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
    System.out.println(c);
}
System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
    System.out.println(c);
}
System.out.println("*****************获取公有、无参的构造方法*******************************");
Constructor con = clazz.getConstructor(null);
//1、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
//2、返回的是描述这个无参构造函数的类对象。

System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
//  System.out.println("obj = " + obj);
//  Student stu = (Student)obj;
System.out.println("******************获取私有构造方法,并调用*******************************");
con = clazz.getDeclaredConstructor(char.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
obj = con.newInstance('男');

2.2、成员变量

2.2.1、批量获取

public Field[] getFields():获取所有的"公有字段"
public Field[] getDeclaredFields():获取所有字段,包括 私有、受保护、默认、公有;

2.2.2、获取单个

public Field getField(String fieldName):获取某个"公有的"字段;
public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)

2.2.3、设置字段的值:

Field --> public void set(Object obj,Object value):
参数说明:
​ obj:要设置的字段所在的对象;
​ value:要为字段设置的值;

//1.获取Class对象
Class stuClass = Class.forName("main.basics.reflect.Student");
//2.获取字段
System.out.println("************获取所有公有的字段********************");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
    System.out.println(f);
}
System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
    System.out.println(f);
}
System.out.println("*************获取公有字段**并调用***********************************");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println("**************获取私有字段****并调用********************************");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);

2.3、成员方法

2.3.1、批量获取

public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)

2.3.2、获取单个

public Method getMethod(String name,Class... parameterTypes) public Method getDeclaredMethod(String name,Class... parameterTypes)
参数:
name : 方法名;
Class ... : 形参的Class类型对象

2.3.3、调用方法

Method --> public Object invoke(Object obj,Object... args):
参数说明:
​ obj : 要调用方法的对象;
​ args:调用方式时所传递的实参;

//1.获取Class对象
Class stuClass = Class.forName("main.basics.reflect.Student");
//2.获取所有公有方法
System.out.println("***************获取所有的”公有“方法*******************");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
    System.out.println(m);
}
System.out.println("***************获取所有的方法,包括私有的*******************");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
    System.out.println(m);
}
System.out.println("***************获取公有的show1()方法*******************");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");

System.out.println("***************获取私有的show4()方法******************");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);

2.4、获取main方法

try {
    //1、获取Student对象的字节码
    Class clazz = Class.forName("main.basics.reflect.Student");

    //2、获取main方法
    Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
    //3、调用main方法
    // methodMain.invoke(null, new String[]{"a","b","c"});//java.lang.IllegalArgumentException: argument type mismatch
    //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,
    /*启动Java程序的main方法的参数是一个字符串数组,
                按照jdk1.5的语法,整个数组是一个参数,而按照jdk1.4的语法,数组中的每一个元素对应一个参数,
                当把一个字符串数组作为参数传递给invoke方法时,javac会按照jdk1.4的语法进行处理,
                因为jdk1.5肯定要兼容jdk1.4的语法,也就是把数组打散成若干个单独的参数,
                所以也就会出现上面的异常了。
                既然字符串数组会拆包成一个个的对象参数,那么我们就在这个字符串的外面再套上一层外衣,
                当拆包的时候只是拆掉外面的那层,里面的字符串数组就可以作为一个单独的参数进行传递了*/
    //这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
    methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
    //            methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二

} catch (Exception e) {
    e.printStackTrace();
}

3、反射使用示例

3.1、变换执行

通过配置文件,灵活的切换程序执行路线。

className = main.basics.reflect.Student
#className = main.basics.reflect.Student2
methodName = show
#methodName = show2
Properties pro = new Properties();//获取配置文件的对象
try {
    FileReader in = new FileReader("HelloJava\\resources\\pro.properties");//获取输入流
    pro.load(in);//将流加载到配置文件对象中
    in.close();
    //通过反射获取Class对象
    Class stuClass = Class.forName(pro.getProperty("className"));
    //2获取show()方法
    Method m = stuClass.getMethod(pro.getProperty("methodName"));
    //3.调用show()方法
    m.invoke(stuClass.getConstructor().newInstance());
    /**
     * 需求:
     * 当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,
     * 这时只需要更改pro.properties的文件内容就可以了。代码一点都不用改动
     */
} catch (Exception e) {
    e.printStackTrace();
}

3.2、破坏泛型(慎用)

ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");

//  strList.add(100);
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
try {
    Method m = null;
    //获取add()方法
    m = listClass.getMethod("add", Object.class);
    //调用add()方法
    m.invoke(strList, 100);
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

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

3.3、破坏单例及单例防止破坏

普通的单例类:

package main.basics.reflect;

public class SingletonStudent {
    private static SingletonStudent ourInstance = new SingletonStudent();

    public static SingletonStudent getInstance() {
        return ourInstance;
    }

    private SingletonStudent() {
    }
}

防止破坏的单例类:

package main.basics.reflect;

public class SingletonStudent2 {
    private static int count = 0;
    private static SingletonStudent2 ourInstance = null;

    public static SingletonStudent2 getInstance() {
        if (null == ourInstance) {
            ourInstance = new SingletonStudent2();
        }
        return ourInstance;
    }

    private SingletonStudent2() {
        synchronized (SingletonStudent2.class) {
            if (count > 0) {
                throw new RuntimeException("无法创建实例");
            }
            count++;
        }
    }
}
SingletonStudent singletonStudent = SingletonStudent.getInstance();
SingletonStudent2 singletonStudent_2 = SingletonStudent2.getInstance();
try {
    Constructor constructor = singletonStudent.getClass().getDeclaredConstructor();
    constructor.setAccessible(true);
    SingletonStudent singletonStudent1 = (SingletonStudent) constructor.newInstance();
    SingletonStudent singletonStudent2 = (SingletonStudent) constructor.newInstance();

    System.out.println(singletonStudent.hashCode());
    System.out.println(singletonStudent1.hashCode());
    System.out.println("----------------------------------");
    Constructor constructor2 = singletonStudent_2.getClass().getDeclaredConstructor();
    constructor2.setAccessible(true);
    SingletonStudent2 singletonStudent_21 = (SingletonStudent2) constructor2.newInstance();
    SingletonStudent2 singletonStudent_22 = (SingletonStudent2) constructor2.newInstance();

    System.out.println(singletonStudent_22.hashCode());
    System.out.println(singletonStudent_21.hashCode());

} catch (Exception e) {
    e.printStackTrace();
} 

4、附录:源码

4.1、Student

package main.basics.reflect;

public class Student {
    //---------------构造方法-------------------
    //(默认的构造方法)
    Student(String str){
        System.out.println("(默认)的构造方法 s = " + str);
    }

    //无参构造方法
    public Student(){
        System.out.println("调用了公有、无参构造方法执行了。。。");
    }

    //有一个参数的构造方法
    public Student(char name){
        System.out.println("姓名:" + name);
    }

    //有多个参数的构造方法
    public Student(String name ,int age){
        System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。
    }

    //受保护的构造方法
    protected Student(boolean n){
        System.out.println("受保护的构造方法 n = " + n);
    }

    //私有构造方法
    private Student(int age){
        System.out.println("私有的构造方法   年龄:"+ age);
    }

    //**********字段*************//
    public String name;
    protected int age;
    char sex;
    private String phoneNum;

    //**************成员方法***************//
    public void show(){
        System.out.println("is show()");
    }

    public void show1(String s){
        System.out.println("调用了:公有的,String参数的show1(): s = " + s);
    }

    protected void show2(){
        System.out.println("调用了:受保护的,无参的show2()");
    }

    void show3(){
        System.out.println("调用了:默认的,无参的show3()");
    }

    private String show4(int age){
        System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
        return "abcd";
    }


    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex
                + ", phoneNum=" + phoneNum + "]";
    }

    //**************main方法***************//
    public static void main(String[] args) {
        System.out.println("main方法执行了。。。");
    }
}

4.2、Student2

package main.basics.reflect;

public class Student2 {
    public void show2(){
        System.out.println("is show2()");
    }
}

posted on 2021-01-13 16:25  敖胤  阅读(77)  评论(0编辑  收藏  举报