笔记:Java之反射机制

反射机制功能:

  1. 实现泛型数组,来动态操作Java代码;
  2. 运行时分析类的能力;
  3. 运行时检查对象;

Class类

程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。该标识跟踪每个对象所属的类。JVM利用运行时类型信息,选择要执行的正确方法。

获取名字

public static void main(String[] args) {
        var e = new Employee("Fucker", 1, 1, 1, 1);
        
        var cl = e.getClass();
        println(cl);  //~class reflection.Employee
        // 静态获取类的名字
        println(cl.getName());  //~ reflection.Employee
        // 动态获取类的名字
        String className = cl.getName();
        try {
        	// 获得类名对应的Class对象
            Class t = Class.forName(className);
            println(t);
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
		// T是任意地Java类型,T.class将代表匹配的类对象。
		Class cl1 = Random.class;
		Class cl2 = int.class;  // int不是类,然而int.class是一个类型的对象。
		Class cl3 = Double[].class;
    }

构造实例

JVM为每一个类型(Class)管理一个唯一的Class对象。因此可以用‘==’符号比较

if (e.getClass() == Employee.getClass()) ...

调用getConstructor()得到一个Constructor类型对象,然后使用newInstance方法构造实例。

var className2 = "reflection.Temp";
try {
     var cl2 = Class.forName(className2);
     Object obj = cl2.getConstructor().newInstance();
} catch (Exception ex) {
       ex.printStackTrace();
}

声明异常

异常有两种类型:非检查型和检查型
非检查型:例如数组越界错误或访问null引用;
检查型:编译器会检查你是否知道这个异常并做好准备来处理后果。

一种检查型异常的处理手段

public static void doSth(String name) throws ReflectiveOperationException{
        var cl = Class.forName(name);
        cl.getConstructor().newInstance();
    }

调用该方法的任何一种方法也需要该声明

利用反射分析类的能力

使用Field、Method和Constructor

这三个类都在reflect包中,拥有各种可以分析类的方法。

/**
 * 两个方法都返回包含Field对象的数组,前者里面每个对象对应这个类或其父类的公共字段;后者对应
 * 这个类的全部字段。但如果类中没有字段,或者Class对象描述的是基本类型或数组类型,则返回长度
 * 为0的数组
 */
Field[] getFields();
Field[] getDeclaredFields();

/**
 * 返回包含Method对象的数组,前者返回所有公共方法,包括从父类继承来的公共方法;后者返回全部
 * 方法,但不包括由父类继承来的方法。
 */
 Method[] getMethod();
 Method[] getDeclaredMethod();

/**
 * 返回包含Constructor对象的数组,包含该类的所有公共构造器或全部构造器
 */
 Constructor[] getConstructor();
 Constructor[] getDeclaredConstructor();

/** 
 * 通用代码,以构造器为例:
 * 简单来说,即用forName()获取对应的class对象,然后使用方法获得其信息
 */
     public static void printConstructors(Class cl) {
        // 获取这个类所有的构造器,包括public,private,protected
        Constructor[] constructors = cl.getDeclaredConstructors();

        for (Constructor c : constructors) {
            String name = c.getName();
            // 打印修饰符,toString负责打印,getModifiers负责返回一个描述修饰符的整数
            String modifiers = Modifier.toString(c.getModifiers());
            if (modifiers.length() > 0) print(modifiers + " ");
            print(name + "(");

            // 打印构造器的参数
            Class[] paramTypes = c.getParameterTypes();
            for (int j = 0; j < paramTypes.length; j++) {
                if (j > 0) print(", ");
                print(paramTypes[j].getName());
            }
            println(");");
        }
    }

使用反射在运行时分析对象

利用反射机制可以查看在编译时还不知道的对象字段。利用Field的get和set方法:
反射机制的默认行为受限于Java的访问权限。所以get和set受限于字段的访问权限
但是可以用三大类的对象的setAccessible方法覆盖Java的访问控制:

psvm() {
	Temp tp = new Temp("fuck you!");
	var cl3 = tp.getClass();
	// get
	var f = cl3.getDeclaredField("t");
	Object v = f.get(tp);
	println(v);
	// set
	f.set(tp, "ok");
	Object vc = f.get(tp);
	println(vc);
	// 假设Employee的name字段为private
	var harry = new Employee("Fucker" , 1, 1,1, 1);
    var cl1 = harry.getClass();
    var f2 = cl1.getDeclaredField("name");
    // 设置可访问标识
    f2.setAccessible(true);
    Object vs = f2.get(harry);
    println(vs);
}
class Temp {
	public String t;
	public Temp(String t) {
		this.t = t;
		pln("bitch");
	}
}   

可以利用toString()方法,获得字段并设置它们为可访问的。查看src/reflection/ObjectAnalyzer.java
要清楚java.util.ArrayList != Array类型

    public static void main(String[] args) {
        var a = new ArrayList<Integer>();
        Class<?> cl = a.getClass();
        var b = new String[10];
        Class<?> cl2 = b.getClass();

        System.out.println(cl);  //~class java.util.ArrayList
        System.out.println(cl2);  //~class [Ljava.lang.String;
        System.out.println(cl.isArray());  //~false
        System.out.println(cl2.isArray());  //~true
    }

使用反射编写泛型数组代码

清楚Array类的常用方法:

public class CopyOfTest {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        // 复制后强转
        a = (int[]) copyOf(a, 10);
        System.out.println(Arrays.toString(a));

        String[] b = {"Fuck", "Your", "Ass"};
        b = (String[]) copyOf(b, 10);
        System.out.println(Arrays.toString(b));

        Employee[] c = {new Employee("Shit", 1, 1, 1, 1),
                        new Employee("Pussy", 1, 1, 1, 1),
                        new Manager("Anal", 2, 2, 2, 2)};
        c = (Employee[]) copyOf(c, 10);
        System.out.println(Arrays.toString(c));
    }

    /**
     * 这个方法要声明返回类型、参入参数类型为Object,不能为Object[],因为Object[]无法向下强转
     * 为其他数组!
     * System.arraycopy()参数查看源码!
     * @param a 原数组
     * @param newLength 新数组长度
     * @return 复制后的新数组
     */
    public static Object copyOf(Object a, int newLength) {
        // 获取原数组的Class类型实例
        Class<?> cl = a.getClass();
        if (!cl.isArray()) return null;
        // 获取原数组元素的Class类型
        Class<?> componentType = cl.getComponentType();
        int length = Array.getLength(a);
        // 创建一个有确定的原数组元素Class对象类型和数组长度的新数组,第二个参数可以设为多维
        Object newArray = Array.newInstance(componentType, length);
        // 开始复制数组,将原数组元素复制到新数组。
        System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
        return newArray;
    }
}
/** 
 * 一些常用Array的方法,其中Xxx是基本类型
 */
 // 返回存储在给定数组中给定索引位置上的值
static xxx getXxx(Object array, int index)
// 将一个新值存储到给定数组中国呢的给定位置上
static xxx setXxx(Object array, int index)
// 返回一个有给定类型,给定大小的新数组;可以指定多维
static Object newInstance(Class componentType, int[] length)

调用任意方法和构造器

使用invoke方法调用

public class MethodTable {
    public static void main(String[] args)
            throws ReflectiveOperationException{
        // 获取Method对象
        Method square = MethodTable.class.getMethod("square", double.class);
        Method sqrt = Math.class.getMethod("sqrt", double.class);
        printTable(1, 10, 10, square);
        printTable(1, 10, 10, sqrt);
    }

    /**
     * 平方, 供Method.invoke用
     * @param x 一个double
     * @return 还是double
     */
    public static double square(double x) { return x * x;}

    /**
     * 打印表格
     * @param from 表格开始一行的数
     * @param to 表格结尾一行的数
     * @param n 行数
     * @param f 方法
     */
    public static void printTable(double from, double to, int n, Method f)
            throws ReflectiveOperationException {
        System.out.println(f);
        // x的公差
        double dx = (to - from) / (n - 1);
        // %n代表行分隔符。invoke()要传入符合f接受的参数
        for (double x = from; x <= to; x += dx) {
            double y = (Double) f.invoke(null, x);
            System.out.printf("%10.4f |%10.4f%n", x, y);
        }
    }
}

然而不建议使用invoke。最好用内部类以及lambda表达式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值