Java学习笔记@反射

笔者:unirithe

日期:11/12/2021

反射概述

  • 反射是指对于任何一个Class类,在"运行的时候"都可以直接得到这个类全部成分
  • 例如,在运行时可直接得到这个类的构造器对象:Constructor
  • 在运行时,可直接得到这个类的成员变量对象:Filed
  • 在运行时,可直接得到这个类的成员方法对象:Method
  • 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制

反射操作

获取Class对象

User.java

package com.uni;
public class User {}

方式一Class.forName

调用静态方法 : Class.forName(包名+类名)

Class c = Class.forName("com.uni.User");
System.out.println(c);

运行结果

class com.uni.User

方式二类名.class

直接取类的class属性

Class c = com.uni.User.class

方式三对象.getClass()

调用对象的getClass方法

Class c = new com.uni.User().getClass()

获取构造器对象 Constructor

Class类中获取构造器的方法

返回类型 方法名 描述
Constructor<?>[] getConstructors() 返回所有构造器对象的数组(只能是public)
Constructor<?>[] getDeclaredCounstructors() 返回所有构造器对象的数组,存在就能拿到
Constructor<T> getConstructor(Class <?> ... parameterTypes) 返回单个构造器对象(只能拿public)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造器对象,存在就能拿到

User.java

package com.uni;
public class User {
    public User(){
        System.out.println("无参构造");
    }
    public User(String id){
        System.out.println("有参构造1");
    }
    private User(String id, String pw){
        System.out.println("有参构造2");
    }
}

测试 getConstructors() 方法 ( 只能获取public构造方法)

Demo.java

Class classUser = User.class;
Constructor [] constructors = classUser.getConstructors();
for (Constructor constructor : constructors) 
    System.out.println("该构造方法的参数个数: " + constructor.getParameterCount());

运行结果

该构造方法的参数个数: 0
该构造方法的参数个数: 1

测试 getDeclaredCounstructors() 方法 (可获取所有构造方法)

Class classUser = User.class;
Constructor [] constructors = classUser.getDeclaredConstructors();
for (Constructor constructor : constructors) 
    System.out.println("该构造方法的参数个数: " + constructor.getParameterCount());

运行结果

该构造方法的参数个数: 0
该构造方法的参数个数: 1
该构造方法的参数个数: 2

测试获取单个public构造器的方法 getConstructor ()

classUser.getConstructor(); // 无参构造
classUser.getConstructor(String.class); // 指定类型的有参构造

classUser.getConstructor(String.class, String.class); // 该句报错,因为对应的构造方法是private类型

获取praivte修饰的构造器方法 getDeclaredConstructor()

classUser.getDeclaredConstructor(String.class, String.class);

调用构造器方法 newInstance

在获取到构造器对象后,通过 Constructor对象的newInstance() 方法 进行实例化

Class classUser = User.class;
Constructor constructor = classUser.getConstructor(String.class);
User user =(User) constructor.newInstance("uni");

特别的是,对于 private 修饰的构造方法,在使用 newInstance() 方法前需打开权限即 setAccessible(true)

Class classUser = User.class;
Constructor constructor = classUser.getDeclaredConstructor(String.class, String.class);
constructor.setAccessible(true);
User user =(User) constructor.newInstance("hello", "world");

获取成员变量对象 Field

java.lang.reflect.Field 取值赋值方法

  • 赋值 void set(Object obj, Object value)
  • 取值 Object get(Object obj)

范例:获取类的所有成员变量,包括final常量、private私有成员变量

User.java

public class User {
    public String name = "uni";
    private String password = "123456";
    public static char sex = '男';
    public static final String lcoation = "中国";
    public String getPW() {return password;}
}
Class classUser = User.class;
Field[] fileds = classUser.getDeclaredFields();
for (Field filed : fileds) {
	System.out.println(filed.getName() + "的类型是:  " + filed.getType());
}

结果

name的类型是: class java.lang.String
password的类型是: class java.lang.String
sex的类型是: char
lcoation的类型是: class java.lang.String

范例:获取类的私有成员变量并进行修改

Class classUser = User.class;
Field userF = classUser.getDeclaredField("password");
User user = new User();
System.out.println(user.getPW());

userF.setAccessible(true); //打开权限
userF.set(user, "123");
System.out.println(user.getPW());

运行结果

123456

123

获取方法对象 Method

常用API的get方法和之前相同,只是将后缀替换成了Method

User.java

public class User {
    public User(){}
    private void eat(String food){
        System.out.println(food + " nice!");
    }
    public String sleep(String time){return time + " good!";}
    public static boolean study(){return true; }
}

范例:获取类的所有方法名称及其返回结果

Class classUser = User.class;
Method[] methods = classUser.getDeclaredMethods();
for (Method method : methods)
    System.out.println(method.getName() +": "  + method.getReturnType());

运行结果

sleep: class java.lang.String
eat: void
study: boolean

范例:获取类的public方法

Method method = classUser.getMethod("sleep", String.class);

调用Method方法

Object invoke(Object obj, Object ..args)

参数说明

  • 参数一:用obj对象调用方法

  • 参数二:调用方法的传递的参数(没有则不写)

返回值:方法的返回值(没有则不写)

范例:调用有返回值的public方法

Class classUser = User.class;
Method method = classUser.getMethod("sleep", String.class);
User user = new User();
String res = method.invoke(user, "2021")
System.out.println(res);

运行结果

2021 good!

范例:调用无返回值的private方法

Class classUser = User.class;
Method method = classUser.getDeclaredMethod("eat", String.class);
User user = new User();
//调用 private 方法需打开权限
method.setAccessible(true);
Object res = method.invoke(user, "fish");
System.out.println(res);

运行结果

fish nice!
null

反射应用

擦除泛型

  • 反射是作用在运行时的计数,此时集合的泛型将不能产生约束,此时可以为集合存入其他任意类型元素。
  • 泛型只是在编译阶段可以约束集合只能操作某种类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList,泛型被擦除

案例:绕过编译阶段为集合添加数据

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class Demo {
    public static void main(String[] args) throws Exception {
        ArrayList<String> list1 = new ArrayList<>();
        ArrayList<Integer> list2 = new ArrayList<>();
        System.out.println(list1.getClass() == list2.getClass()); // 结果为 True

        Class c = list1.getClass(); // ArrayList.class ==> public boolean add(E e)
        Method add = c.getDeclaredMethod("add", Object.class);
        boolean rs = (boolean) add.invoke(list1, "测试");
        System.out.println("add执行结果: " + rs);
        System.out.println(list1);
    }
}

通用框架的底层原理

案例:

需求:给任意对象,把未知的字段名称和对应值存储到文件中

分析:

  • 定义一个方法可接收任意类的对象
  • 每收到一个对象需解析该对象的全部成员变量名称
  • 使用反射获取对象的Class类对象,然后获取全部成员变量信息
  • 遍历成员变量信息,然后提取本成员变量在对象中的具体值
  • 存入成员变量名称和值到文件中去

User.java

public class User {
    private String id;
    public User(String id) {
        this.id = id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

UserUtils.java

import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class UserUtils {
    public static void save(Object obj){
        try(PrintStream ps = new PrintStream(new FileOutputStream("data.txt", true))){
            Class userClass = obj.getClass();
            // 输出当前类名
            ps.println("=========" + userClass.getSimpleName() + "=========");
            // 获取所有成员变量
            Field[] fields = userClass.getDeclaredFields();
            for (Field field : fields) {
                String name = field.getName();
                field.setAccessible(true);
                String value = field.get(obj) + "";
                ps.println(name + " = " + value);
            }

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

Demo.java

import java.lang.reflect.Method;
import java.util.ArrayList;

public class Demo{
    public static void main(String[] args) {
            UserUtils.save(new User("uni"));
            UserUtils.save(new User("abc"));
            UserUtils.save(new User("java"));
    }
}

运行结果 data.txt

=========User=========
id = uni
=========User=========
id = abc
=========User=========
id = java

动态代理

java.lang.reflect.Proxy

构造方法:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

参数说明:

  • loader 类加载器,负责加载代理类别到内存中使用
  • interfaces 获取被代理对象实现的全部接口,代理要为全部接口的全部方法进行代理
  • h 代理的核心处理逻辑

代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的

关键步骤:

  • 必须有接口,实现类要实现接口(代理通常是基于接口实现)
  • 创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象

案例:模拟企业业务功能开发,并完成每个功能的性能统计

需求:模拟某企业用户管理业务,需包含用户登陆,用户删除,用户查询功能,并要统计每个功能的耗时

定义:

UserService表示用户业务接口,完成用户登陆、删除、查询功能

UserServiceImpl实现UserService,并完成相关功能

定义测试类,创建实现类对象,调用方法

UserService.java

public interface UserService {
    String login(String username, String password);
    void selectUsers();
    boolean deleteUsers();
}

UserServiceImpl.java

public class UserServiceImpl implements UserService{

    @Override
    public String login(String username, String password) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if ("admin".equals(username) && "123456".equals(password))
                return "success";
            else
                return "密码错误";
    }

    @Override
    public void selectUsers() {
        System.out.println("模拟查询 正在执行...");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean deleteUsers() {
        System.out.println("模拟删除 正在执行...");
        try {
            Thread.sleep(2000);
            return true;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        }
    }
}

ProxyUtil.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {

    public static UserService getProxy(UserService obj) {
        return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 参数1: 代理对象本身,一般不管
                        // 参数2:正在被代理的方法
                        // 参数3:被代理方法,应该传入的参数
                        long startTime = System.currentTimeMillis();
                        // 触发方法的真正执行
                        Object result = method.invoke(obj, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName() + " 方法耗时: " + (endTime - startTime) / 1000.0 + " s");
                        // 把业务功能方法执行的结果返回给调用者
                        return result;
                    }
                });

    }
}

TestDemo.java

public class TestDemo {
    public static void main(String[] args) {
        UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
        System.out.println(userService.login("admin", "123456"));
        System.out.println(userService.deleteUsers());
        userService.selectUsers();
    }
}

运行结果

login 方法耗时: 1.001 s
success
模拟删除 正在执行...
deleteUsers 方法耗时: 2.002 s
true
模拟查询 正在执行...
selectUsers 方法耗时: 2.002 s

其中 ProxyUtil.java 的作用是在执行对象的方法时候进行性能评优,即输出方法执行的耗时,除了固定的类型外,可以使用泛型

public class ProxyUtil {
    public static <T> T getProxy(T obj) {
        return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      
                        long startTime = System.currentTimeMillis();
                        Object result = method.invoke(obj, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName() + " 方法耗时: " + (endTime - startTime) / 1000.0 + " s");
                        return result;
                    }
                });
    }
}

这样就可输出任意对象的任意方法执行的耗时结果,非常方便。

动态代理优点

  • 非常灵活,支持任意接口类型的实现类对象做代理,也可直接为本身做代理
  • 可以为被代理对象的所有方法做代理
  • 可以在不改变方法源码的情况下,实现对方法功能的增强
  • 不仅简化了编程工作,提高了软件系统的可扩展性,同时也提高了开发效率
posted @ 2021-11-12 23:32  Unirithe  阅读(31)  评论(0)    收藏  举报