深入浅出Java反射原理和使用场景

反射非常重要,特别是Spring这类框架离不开反射,而反射对于初学者理解起来其实还是有一定的难度的,本帖希望把晦涩的反色用最易懂的方式给你讲明白。

先不说反射是什么先看一个问题:如果不知道对象的真实类型怎么去调用他的方法?

Object obj = new Date();

 编译类型:Object
 运行类型(其实就是obj对象真实的类型):Date
 需求:根据对象obj调用Date类中的一个方法,toLocaleString,如何来做?

  obj.toLocaleString()代码在编译阶段去编译类型Object中检查是否有该方法,若没有,编译失败.

  解决方案1:强制转换obj为Date类型,前提:必须知道对象的真实类型是什么?

Date d = (Date)obj;
d.toLocaleString();//YES

如果我不知道obj的真实类型,那又如何去调用Date的toLocaleString()方法. 如何去做?

下面再开始讲反射的基础知识和理论体系。

类和元数据关系图,这张图是以前的学习笔记,感觉画的还可以留沿用下:

JDK文档关于Class的说明:

说明:

Class这个Java类保存的是一个Java类的meta信息(元信息)。一般在反射中使用。 
Object类,是所有Java类的根。包括Class类。

下面关于反射的说明出自詹姆斯·高斯林的一本书: 

Class类:用于描述一切类/接口.枚举是一种类,注解是一种接口

Class实例:就是指JVM中一份字节码

Class类:用于描述一切类/接口.问题:那Class实例到底表示的是哪一份字节码,为了明确区分出Class实例表示的是谁的字节码.Class类提供了泛型


Class<Date>  clz1 = Date.class;//clz1表示是Date的字节码
Class<String>  clz2 = String.class;//clz2表示的是String的字节码

如何得到Class的实例:
     1.类名.class(就是一份字节码)
     2.Class.forName(String className);根据一个类的全限定名来构建Class对象
     3.每一个对象多有getClass()方法:obj.getClass();返回对象的真实类型


一个类在JVM中只有一份字节码;

第一种方式:数据类型.class
  Class<User> clz1 = User.class;


第二种方式: Class.forName(String className);
Class<?>  clz2 = Class.forName("cn.itcast.cd.User");
使用?是因为此时Class不知道类,因为传的是字符串


反射很强大,但是耗性能.主要是为了做工具和框架使用的.

第三种方式:  对象.getClass();得到对象的真实类型,每个方法都有所以在Object里

User u  = new User();
   Class clz3 = u.getClass();

clz1 == clz2 == clz3:因为表示都是JVM中共同的一份字节码(User.class)

new一个类才会把字节码从IO加载到内存。

Java内置9大的Class实例

对于对象来说,可以直接使用对象.getClass()或者Class.forName(className); 类名.class都可以获取Class实例.


但是我们的基本数据类型,就没有类的权限定名,也没有getClass方法.
问题:那么如何使用Class类来表示基本数据类型的Class实例?
byte,short,int,long,char,float,double,boolean ,void关键字
上述8种类型和void关键字,都有class属性.
表示int的Class对象:  Class clz = int.class;
表示boolean的Class对象:  boolean.class;
void:  Class clz = void.class;


所有的数据类型都有class属性,表示都是Class对象.


思考:
int的包装类是Integer
Integer.class     ==?==         int.class
结果是false,说明是两份字节码.


Integer 和int是同一种数据类型吗? 不是


但是在八大基本数据类型的包装类中都有一个常量:TYPE
TYPE表示的是该包装类对应的基本数据类型的Class实例.
如:Integer.TYPE----->int.class
   Integer.TYPE==int.class;//YES
   Integer.TYPE == Integer.class;//ERROR

数组的Class实例:
String[] sArr1 = {"A","C"};
String[] sArr2 = {};
String[][] sArr = {};

int[] sArr = {};

表示数组的Class实例:
   String[] sArr1 = {"A","C"};
   Class clz = String[].class;//此时clz表示就是一个String类型的一位数组类型

   所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);
   注意:和数组中的元素没有一点关系.

获取类中的构造器

获取某一个类中的所有的构造器:
   1,明确操作的是哪一份字节码对象
   2,获取构造器


Class类获取构造器方法:
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器

public Constructor<?>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器

public Constructor<?>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关

public Constructor<T> getConstructor(Class<?>... parameterTypes) :获取当前Class所表示类中指定的一个public的构造器
      参数:parameterTypes表示:构造器参数的Class类型
      如:public User(String name)
        Constructor c =  clz.getConstructor(String.class);

public class User {
    public User(){};
    public User(String name){};
    public User(String name,String school){};
}
       System.out.println(Integer.TYPE);
        
        Class<User> clz=User.class;
        Constructor[]cs=clz.getConstructors();
        for (Constructor c : cs) {
            System.out.println(c);
        }
        
        //获取单个构造器
        Constructor c=clz.getDeclaredConstructor(String.class);
        System.out.println(c);

调用构造器,创建对象


Constructor<T>类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器


常用方法:
public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式.
     参数:initargs:表示调用构造器的实际参数
     返回:返回创建的实例,T表示Class所表示类的类型


如果:一个类中的构造器可以直接访问,同时没有参数.,那么可以直接使用Class类中的newInstance方法创建对象.
 public Object newInstance():相当于new 类名();


 

修改User

public class User {
    public User() {
        System.out.println("---");
    }
    public User(String name) {
        System.out.println(name);
    }
    public User(String name, String school) {
        System.out.println(name + " " + school);
    }
}
public static void main(String[] args) throws Exception {        
        System.out.println(Integer.TYPE);
        
        Class<User> clz=User.class;
        Constructor[]cs=clz.getConstructors();
        for (Constructor c : cs) {
            System.out.println(c);
        }
        
        //获取单个构造器
        //泛型
        Constructor<User> c=clz.getDeclaredConstructor(String.class);
        System.out.println(c);      
        User u=c.newInstance("abc");
        
        //不带泛型
        Constructor c2=clz.getDeclaredConstructor(String.class);
        System.out.println(c2);       
        Object u2=c2.newInstance("abc");        
    }

Class.forName方式

如果访问不带参数的构造器可以直接用Class的newInstance()方法

修改访问权限为private:

public class User {
    public User() {
        System.out.println("---");
    }
    private User(String name) {
        System.out.println(name);
    }
    public User(String name, String school) {
        System.out.println(name + " " + school);
    }
}


报错

Constructor<User> c=clz.getDeclaredConstructor(String.class);
        System.out.println(c);
        c.setAccessible(true);
        User u=c.newInstance("abc");

关于setAccessible的官方说明:

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AccessibleObject.html

Java反射机制提供的setAccessible()方法可以取消Java的权限控制检查。

package com.test.www;

class AccessibleTest {
    private int id;
    private String name;

    public AccessibleTest() {

    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
import java.lang.reflect.Field;
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("com.test.www.AccessibleTest");
        AccessibleTest at = new AccessibleTest();
        at.setId(1);
        at.setName("https://linuxstyle.blog.csdn.net/");
        for (Field f : clazz.getDeclaredFields()) {
            f.setAccessible(true);//AccessibleTest类中的成员变量为private,故必须进行此操作
            System.out.println(f.get(at));//获取当前对象中当前Field的value
        }
    }
}

 如果去掉setAccessible(true)就报错了:

========================

获取类中的方法

使用反射获取某一个类中的方法:
1.找到获取方法所在类的字节码对象
2.找到需要被获取的方法

Class类中常用方法:
public Method[] getMethods():获取包括自身和继承过来的所有的public方法

public Method[] getDeclaredMethods():获取自身所有的方法(不包括继承的,和访问权限无关)


public Method getMethod(String methodName,

                        Class<?>... parameterTypes):表示调用指定的一个公共的方法(包括继承的)
       参数:
               methodName: 表示被调用方法的名字
               parameterTypes:表示被调用方法的参数的Class类型如String.class


public Method getDeclaredMethod(String name,

                                Class<?>... parameterTypes):表示调用指定的一个本类中的方法(不包括继承的)
        参数:
               methodName: 表示被调用方法的名字
               parameterTypes:表示被调用方法的参数的Class类型如String.class

public class User {
    public User() {
        System.out.println("---");
    }
    public User(String name) {
        System.out.println(name);
    }
    public User(String name, String school) {
        System.out.println(name + " " + school);
    }
    public void Say(String name) {
        System.out.println(name);
    }
    public void Say(String name, String school) {
        System.out.println(name + " " + school);
    }
}
Class clz=User.class;
        Method[] m=clz.getMethods();
        for (Method c : m) {
            System.out.println(c);
        }
        System.out.println("----------------------");
        m=clz.getDeclaredMethods();
        for (Method c : m) {
            System.out.println(c);
        }

Method m1=clz.getMethod("Say", String.class);
        System.out.println(m1);
        Method m2=clz.getMethod("Say", String.class,String.class);
        System.out.println(m2);


发布了1593 篇原创文章 · 获赞 1108 · 访问量 1197万+

猜你喜欢

转载自blog.csdn.net/21aspnet/article/details/89402714