【JavaSE-11】:Java反射机制

一、Java反射机制概述

● Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

● 加载完类之后,在堆内存的方法区中就产生了一个 Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射。

在这里插入图片描述

  • 谈谈反射机制的优缺点
  1. 优点 : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
  2. 缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

二、理解Class类并获取Class实例 ★

1.Class类

在Object类中定义了以下的方法,此方法 将被所有子类继承:
public final Class getClass()

以上的方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称。

对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构
**(class/interface/enum/annotation/primitive type/void/[])**的有关信息。

● Class本身也是一个类
● Class 对象只能由系统建立对象 
●  一个加载的类在 JVM 中只会有一个Class实例 
●  一个Class对象对应的是一个加载到JVM中的一个.class文件 
●  每个类的实例都会记得自己是由哪个 Class 实例所生成 
●  通过Class可以完整地得到一个类中的所有被加载的结构 
●  Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象

2.Class类的常用方法

在这里插入图片描述

3.获取Class实例的4种方法:

1)前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高
实例:Class clazz = String.class;

2)前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:Class clazz = “www.camille.com”.getClass();

3)前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方 法forName()获取,可能抛出ClassNotFoundException
实例:Class clazz = Class.forName(“java.lang.String”);

4)其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);

类代码:

public class Person {

    private String name;
    public int id;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private Person(String name) {
        this.name = name;
    }
    public String getName() {

        return name;
    }
    public void setName(String name) {

        this.name = name;
    }
    public int getAge() {

        return age;
    }
    public void setAge(int age) {

        this.age = age;
    }
    public Person() {

    }
    public void show() {
        System.out.println("I am a person.");
    }
    private String showNation(String nation) {
        System.out.println("My's nation is " + nation);
        return nation;
    }
    private static void showDect(){
        System.out.println("我很帅!~");
    }
}

//获取Class的实例的方式,前三种方式需要掌握
    @Test
    public void test3() throws ClassNotFoundException {
        //方式一:调用运行时类的属性:.class
        Class<Person> class1 = Person.class;
        System.out.println(class1);

        //方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class class2 = p1.getClass();
        System.out.println(class2);

        //方式三:调用Class的静态方法:forName(String classPath)
        Class class3 = Class.forName("com.camille.java.Person");
        System.out.println(class3);

        System.out.println(class1 == class2);//true
        System.out.println(class1 == class3);//true

        //方式四:使用类的加载项:ClassLoader
        ClassLoader classloader =ReflectionTest.class.getClassLoader();
        Class class4=classloader.loadClass("com.camille.java.Person");
        System.out.println(class4);

        System.out.println(class4==class1);//true

说明:加载到内存中的运行时类,会在缓存区存在一段时间(生命周期),在此时间内,可以通过不同的方式获取此运行时类。(获取的是同一个

4.哪些类型可以有Class对象?

(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[ ]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

Class c1 = Object.class; 
Class c2 = Comparable.class; 
Class c3 = String[].class; 
Class c4 = int[][].class; 
Class c5 = ElementType.class; 
Class c6 = Override.class; 
Class c7 = int.class; 
Class c8 = void.class; 
Class c9 = Class.class;

int[] a = new int[10]; 
int[] b = new int[100]; 
Class c10 = a.getClass(); 
Class c11 = b.getClass(); 
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);//ture

三、类的加载与ClassLoader(了解即可)

1.类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 如下三个步骤来对该类进行初始化。

在这里插入图片描述

1、加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。

2、链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

3、类的初始化:执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。

2.ClassLoader

类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的 类的加载器
在这里插入图片描述

拓展:

使用类加载器读取配置文件

@Test
    public void test1() throws Exception {

        Properties pros = new Properties();
        //读取配置文件的方式一:
        //此时的文件默认在当前的Module下
        FileInputStream fis = new FileInputStream("jdbc.properties");
        pros.load(fis);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = "+user+", password = "+password);

    }
@Test
    public void test1() throws Exception {

        Properties pros = new Properties();
     
        //读取配置文件的方式二:
        //此时配置文件的默认识别在:当前Module下的src下
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = "+user+", password = "+password);
    }

四、创建运行时类的对象 ★

创建类的对象:调用Class对象的 newInstance() 方法

要求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。

    @Test
    public void test1() throws IllegalAccessException, InstantiationException {

        Class<Person> clazz = Person.class;
        
        /**
         * newIntance():调用此方法创建对应的运行时类的对象
         *
         * 要象此方法正常的创建运行时类的对象,要求:
         * 1、运行时类必须提供空参的构造器
         * 2、空参的构造器的访问权限得够,通常,设置为public
         *
         * 原因:
         * 1、便于通过反射,创建运行时类的对象
         * 2、便于子类继承此运行时类时,调用默认super(),保证父类有此构造器
         */
        Person obj = clazz.newInstance();
        System.out.println(obj);

    }
    
}

或者使用带参构造器:

//1.根据全类名获取对应的Class对象 
String name = “atguigu.java.Person"; 
Class clazz = null; 
clazz = Class.forName(name); 

//2.调用指定参数结构的构造器,生成Constructor的实例 
Constructor con = clazz.getConstructor(String.class,Integer.class); 

//3.通过Constructor的实例创建对应类的对象,并初始化类属性 
Person p2 = (Person) con.newInstance("Peter",20); 
System.out.println(p2);

五、获取运行时类的完整结构

通过反射获取运行时类的完整结构:

Field、Method、Constructor、Superclass、Interface、Annotation

  1. 实现的全部接口
    public Class<?>[ ] getInterfaces()
    确定此对象所表示的类或接口实现的接口。

  2. 所继承的父类
    public Class<? Super T> getSuperclass()
    返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

  3. 全部的构造器
    public Constructor[ ] getConstructors()
    返回此 Class 对象所表示的类的所有 public构造方法。
    public Constructor[ ] getDeclaredConstructors()
    返回此 Class 对象表示的类声明的所有构造方法。

● Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?> [ ] getParameterTypes();

  1. 全部的方法
    public Method[ ] getDeclaredMethods()
    返回此Class对象所表示的类或接口的全部方法
    public Method[ ] getMethods()
    返回此Class对象所表示的类或接口的public的方法

● Method类中:
public Class<?> getReturnType() 取得全部的返回值
public Class<?>[ ] getParameterTypes() 取得全部的参数
public int getModifiers() 取得修饰符
public Class<?>[ ] getExceptionTypes() 取得异常信息

  1. 全部的Field
    public Field[ ] getFields()
    返回此Class对象所表示的类或接口的public的Field。
    public Field[ ] getDeclaredFields()
    返回此Class对象所表示的类或接口的全部Field。

Field方法中:
public int getModifiers() 以整数形式返回此Field的修饰符
public Class<?> getType() 得到Field的属性类型
public String getName() 返回Field的名称。

  1. Annotation 相关
    get Annotation(Class annotationClass) 获取运行时类的注解
    getDeclaredAnnotations()

  2. 泛型相关
    获取父类泛型类型:Type getGenericSuperclass()
    泛型类型:ParameterizedType
    获取实际的泛型类型参数数组:getActualTypeArguments()

  3. 类所在的包
    Package getPackage()

六、调用运行时类的指定结构 ★

1.调用指定属性

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。

public Field getField(String name)           返回此Class对象表示的类或接口的指定的 public的Field。
public Field getDeclaredField(String name)   返回此Class对象表示的类或接口的 指定的Field。

在Field中:
public Object get(Object obj) :取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value) :设置指定对象obj上此Field的属性内容

代码示例:

/*
    操作运行时类中的指定属性————需要掌握
     */
    @Test
    public void test2() throws Exception {

        Class<Person> clazz = Person.class;

        Person p = clazz.newInstance();

        //1、getDeclaredField(String fieldName):获取运行时类中制定变量名的属性
        Field name = clazz.getDeclaredField("name");

        //2、保证当前属性是可访问的(获取属性修改权限)
        name.setAccessible(true);
        //3、获取、设置指定对象的属性值
        name.set(p, "Tom");

        System.out.println(name.get(p));

    }

2.调用指定构造器

/*
    调用运行时类中的指定构造器——需要掌握
     */
    @Test
    public void test4() throws Exception {
        Class<Person> clazz=Person.class;

        //1、创建运行时类的对象
        Person p= clazz.newInstance();

        /*
        //private Person(String name)
        2、获取指定的某个构造器
        getDeclaredConstructor():参数:指明获取的构造器的参数列表
         */
        Constructor constructor=clazz.getDeclaredConstructor(String.class);

        //3、保证当前构造器是可访问的
        constructor.setAccessible(true);

        /*
        调用此构造器创建运行时类的对象
         */
        Person person=(Person)constructor.newInstance("Tom");
        System.out.println(person);
    }
}

3.调用指定方法

通过反射,调用类中的方法,通过Method类完成。步骤:

1.通过Class类的getMethod(String name,Class…parameterTypes)方法取得 一个Method对象,并设置此方法操作时所需要的参数类型。
2.之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中 传递要设置的obj对象的参数信息。

在这里插入图片描述

/**
     * 操作运行时类中的指定方法————需要掌握
     */
    @Test
    public void test3() throws Exception {

        Class<Person> clazz=Person.class;

        //1、创建运行时类的对象
        Person p= clazz.newInstance();

        /*
        2、获取指定的某个方法
        getDeclaredMethod():参数1:指明获取的方法的名称  参数2:指明获取方法的形参列表

         */
        Method showNation=clazz.getDeclaredMethod("showNation",String.class);
        //3、保证当前方法是可访问的
        showNation.setAccessible(true);

        /*
        调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即对应类中调用方法的返回值
         */
        Object returnValue=showNation.invoke(p,"China");
        System.out.println(returnValue);

        //如何调用一个静态方法
        // private static void showDect()
        Method showDect =clazz.getDeclaredMethod("showDect");
        showDect.setAccessible(true);

        //如果调用的方法没有返回值,则invoke()返回null

        //Object returnValue2=showDect.invoke(null);
        Object returnValue2=showDect.invoke(p);
        System.out.println(returnValue2);
    }

Object invoke(Object obj, Object … args)

说明:
1.Object 对应原方法的返回值,若原方法无返回值,此时返回null
2.若原方法若为静态方法,此时形参Object obj可为null
3.若原方法形参列表为空,则Object[] args为null
4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法。

4.关于setAccessible方法的使用

● Method和Field、Constructor对象都有setAccessible()方法。

● setAccessible启动和禁用访问安全检查的开关。

参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被 调用,那么请设置为true。
使得原本无法访问的私有成员也可以访问

● 参数值为false则指示反射的对象应该实施Java语言访问检查。

七、反射的应用:动态代理

链接: 动态代理.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值