反射机制(二,java-Reflection及类各种相关一切的获取)

Reflection
我在前面写springIoC是就有写到动态代理,这就是反射的运用。
Reflection(反射),在上一章说了该类中的很多方法。这里总结上一篇的累述观点:反射是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能够直接操作任意对象的内部属性及方法。
这里不得不讲讲运行过程:*.java文件— 》 java编译器 --》 生成.class文件 --》 类装载器 --》 字节码校验器 --》 解释器 --》OS

java反射机制提供的功能:(每个运行时类,只加载一次,且对应一个.class实例)
1,在运行时,判断任意一个对象属性的类。
2,在运行时,构造任意一个类的对象。
3,在运行时,判断任意一个类所具有的成员变量和方法。
4,在运行时,调用任意一个对象的成员变量和方法。
5,生成动态代理。

Reflection相关API:
java.lang.class : 代表一个类 ;
java.lang.reflect.Method : 代表类的方法 ;
java.lang.reflect .Field :代表类的成员变量(即属性);
java.lang.reflect.Constructor : 代表类的构造方法

重中之重–通过反射调用类中指定的方法、属性
抛砖引玉:

// 实体类
package com.reflect.first;
public class Person {
public String name;
private int age;

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 void show() {
    System.out.println("hello!" + name + ";您已经" + age + "了");
}
 }

测试类

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.junit.Test;

public class TestReflection {
@Test
public void test2() throws Exception {
    // 不用反射,创建类对象,并调用其中的方法和属性。
    Person person = new Person();
    person.setAge(25);
    person.setName("zhouyi");
    person.show();

    // 1,用反射创建类对象,并调用其中的方法和属性。
    Class clazz =Person.class; // 拿到com.reflect.first.Person 。这里提醒一句有时也用Class<Person>
    System.out.println(clazz);
    // 2,创建clazz对应的运行时类Person类的对象。
    Person person1 = (Person) clazz.newInstance();

    // 3,获取Person类的结构。
    Field fname = clazz.getField("name");
    Field fage = clazz.getDeclaredField("age");
    // 设置person1对象的,属性fname
    fname.set(person1, "huihui");
    fage.setAccessible(true);  // 设置private,protected修饰符限定符,可写操作
    fage.set(person1, 24);

    // 4,通过反射调用运行时类的指定方法
    Method method = clazz.getMethod("show");
    method.invoke(person1); // 返回值就是show方法的返回值。
}
}

运行结果:
在这里插入图片描述

类获取
四种常见的拿到类方式:(就不给出实例了)

// 1,第一种:运行时类本身(推荐使用)
Class clazz1 = Person.class;
System.out.println(clazz1.getName());

// 2,第二种:运行时类对象
Person person = new Person();
Class clazz2 = person.getClass();
System.out.println(clazz2.getName());

// 3,第三种:Class静态方法来获取,就好比数据库链接。(这样的共通性较好)
String className = "com.reflect.first.Person";
Class clazz3 = Class.forName(className );
System.out.println(clazz3.getName());

// 4,通过类的加载器
ClassLoader  classLoader = this.getClass().getClassLoader();
Class clazz4 = classLoader.loadClass(className);

ClassLoader 的介绍
类加载器是用来把类装载进内存的。JVM规范定义了两种类型的类加载器:启动类加载器Bootstrap和用户自定义加载器user-defined class loader。JVM在运行时会产生3个类加载器组成的初始化加载器层次结构,如下所示:
Bootstrap ClassLoader(引导类加载器) ----- Extension ClassLoader(扩展类加载器) ----System ClassLoader(系统加载器) 的来回加载方式。

属性获取
属性 = 权限修饰符 + 变量类型 + 变量名
前提准备clas和接口(这样的话,想要测试什么,我只需要修改测试类即可)
后期改动:为了测试拿到方法的注解,给show加上了@PersonAnnotation(value = “zhouyi”)
在这里插入图片描述

package com.reflect.first;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;

import org.junit.Test;


public class TestReflection {
@Test
public void test2() throws Exception {

    // 1,用反射创建类对象,并调用其中的方法和属性。
    Class clazz =Person.class; // 拿到com.reflect.first.Person
    // 拿到运行时类以及父类的所有public类型的属性--getFields
    Field[] fields = clazz.getFields();
    Arrays.asList(fields).forEach(t->{System.out.println(t.getName());});
    System.out.println("------------------------------");
    // 解决上面拿不到私有和保护类型的问题getDeclaredFields ;只能拿到运行时类本身的属性
    Field[] fields1 = clazz.getDeclaredFields();
    for (Field field1 : fields1) {
        // 属性 = 权限修饰符 + 变量类型 + 变量名
        System.out.println("变量名:" + field1.getName());  // 变量名
        System.out.println("变量类型:" + field1.getType()); // 变量类型
        System.out.print("权限修饰符:" + field1.getModifiers() + "-----"); // 权限修饰符
        System.out.println(Modifier.toString(field1.getModifiers())); // 权限数字转换为原本权限修饰符
    }

    System.out.println("------------------------------");
    System.out.println(Arrays.toString(fields1));

    // 2,创建clazz对应的运行时类Person类的对象。
    Person person = (Person) clazz.newInstance();
}
}

运行结果:
在这里插入图片描述

方法获取
方法的完整结构 — 方法 = 注解 + 权限修饰符 + 返回值类型 + 方法名 + 形参列表 + 异常

package com.reflect.first;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

import org.junit.Test;


public class TestReflection {
@Test
public void test2() throws Exception {

    // 1,用反射创建类对象,并调用其中的方法和属性。
    Class clazz =Person.class;
    // 拿到运行时类以及父类的所有public方法--getMethods
    Method[] methods = clazz.getMethods();
    // 当前类:compareTo,show;父类:say
    Arrays.asList(methods).forEach(t->{System.out.print(t.getName() + "---");});

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

    // 拿到当前类所有方法,拿不到直接/间接父类
    // 方法的完整结构  方法 = 注解 + 权限修饰符 + 返回值类型 + 方法名 + 形参列表 + 异常
    Method[] methods1 = clazz.getDeclaredMethods();
    Arrays.asList(methods1).forEach(t->{System.out.print(t.getName()+ "---");});
    for (Method method : methods1) {
        Annotation[] ann = method.getAnnotations(); // 注解
        Arrays.asList(ann).forEach(t->{System.out.println(t + "---");});

        System.out.println(Modifier.toString(method.getModifiers()) + "---"); // 权限修饰符

        System.out.println(method.getReturnType().getName() + "---"); // 返回值类型

        System.out.println(method.getName() + "---"); // 方法名

        Class[] prams = method.getParameterTypes(); // 形参列表
        Arrays.asList(prams).forEach(t->{System.out.println("["+ t.getName() +"]");});

        Class[] exceptions = method.getExceptionTypes(); // 异常 由于没有这里就不打印了
    }
}
}

运行结果:
在这里插入图片描述
获得构造器,创建运行类的对象(带参数有无)
前面我们对象构造时,使用newInstance ,我们发现他只能是空参,显然是获取我们所需要的有参构造器的
这里解释下:构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统会为这个对象的实例进行默认的初始化。如果想改变这种默认的初始化,就可以通过自定义构造器来实现。比如上面person类的构造器:Person() { }
举例:

Constructor[] constructors = clazz.getConstructors(); // 简单同上,就不运行了,参数无/有
Person person = (Person) constructors .newInstance("zhouyi",20);

各种杂项获得
父类,包,接口,类的注解

package com.reflect.first;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;

import org.junit.Test;

public class TestReflection {
@Test
public void test2() throws Exception {

    // 1,杂项获得
    Class clazz =Person.class;
    // 获取父类
    Class superClass = clazz.getSuperclass(); // 一般父类
    System.out.println(superClass);

    Type type = (Type) clazz.getGenericSuperclass(); // 带有泛型的父类
    System.out.println(type);
    Type[] types = ((ParameterizedType)type).getActualTypeArguments();
    System.out.println(((Class)types[0]).getName()); // 获得泛型的类型

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

    // 获取实现的接口, 只拿到了直接接口
    Class[] interfaes = clazz.getInterfaces();
    Arrays.asList(interfaes).forEach(t->{System.out.println(t);});

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

    // 获取所在的包,类的注解
    System.out.println(clazz.getPackage()); // 获取所在包
    // 获取类的注解,拿到都是Runtime级别注解
    Annotation[] annotations = clazz.getAnnotations();
    Arrays.asList(annotations).forEach(t->{System.out.println(t);});
    }
}

讲实话学到这里,我很疑惑,反射只能是子类和父类之间的调用吗?如果这样的话,作用范围不是很小吗?我尝试,跨域去调用方法全是调式通不过,难受。。。。。。让我再研究研究?
经过研究终于发现了问题在哪里:我发现了上面的理解除了两点问题。
1,对invoke的理解出了问题;2,对构造器的理解。
前提准备:

在这里插入图片描述测试类:

package com.reflect.second;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

public class TestClass {
public static void main(String[] args) throws Exception {
    // 这里我们用class的静态方法来获取
    Class<?> clazz = Class.forName("com.reflect.second.Person" );

    /**
     * 这里我们来说明上次留下的另一个问题:构造器
     * 1,其实每一个类都有一个默认的无参构造器,不如public Person() { }
     * 2,但是当我们人为的写了一个构造器,就不会再有上面的没人构造器,比如
     * public Person(String name, int age, String sex, String charcter) {
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.charcter = charcter;
        }
           当然invoke的理解,也在其中
     */
    //clazz.getConstructor(); 为了实验现象,选用下面的,其实在这个用这个就可以了
    Constructor[] cons = clazz.getConstructors();
    // 这里的结果是获得了所有的构造函数,你可以尝试去掉没有参数的看看结果
    Arrays.asList(cons).forEach(t->{System.out.println(t);});
    Person person = (Person) cons[0].newInstance("zhouyi",25,"male","你是真的帅气的一批");

    Method method = clazz.getMethod("show");
    method.invoke(person);
//        Person person2 = new Person(); // 这里这样写是不可以的,这又是个坑?  经过仔细思考实验得到,传的唯一person,不是参数,而是对象实例
//        method.invoke(person,person); // 这样就很好的解释了这个问题

    Method method1 = Class.forName("com.reflect.second.Animal").getMethod("distinct", Person.class);
    Animal animal = (Animal) Class.forName("com.reflect.second.Animal").newInstance();
    //Animal animal = new Animal(); // 这样也是可以的
    method1.invoke(animal, person); // 方法 + 对象实例 + 传参参数
}
}

运行结果:
在这里插入图片描述
1,对构造器的理解,注释已经说说过了。
2,从上面的例子,我们很清楚地看到,并不是父类和子类才能调用,这里我们进入了一个误区,我们利用反射去哪了某样东西,构造参数,那方法,此时我们却忘了拿了里面的所有东西,参数,参数类型,方法等,但唯独没有去那个类实例。上面解决方式,就是拿了类实例即可,这就是之前我们对invoke的错误理解:他的第一个参数,是一个实例对象参数,第二个才是真的传参参数。
3,最终无非是达到这个效果,终极反射等式 = 方法 + 对象实例 + 传参参数。

来回一个周,终于填完了,反射的坑。一把辛酸泪啊!

猜你喜欢

转载自blog.csdn.net/weixin_42603009/article/details/89222907