Java基础——反射

简介: 本文介绍了Java反射机制的基本概念和使用方法,包括`Class`类的使用、动态加载类、获取方法和成员变量信息、方法反射操作、以及通过反射了解集合泛型的本质。同时,文章还探讨了动态代理的概念及其应用,通过实例展示了如何利用动态代理实现面向切面编程(AOP),例如为方法执行添加性能监控。

反射

Class类的使用

在Java语言中,万事万物皆为对象,那么问题来了,"类"是谁的对象呢?

类是对象,任何一个类都是java.lang.Class类的实例对象

基本的数据类型,乃至于void关键字,都存在其对应的类类型(class type)

下面是获取自定义类的类类型(class type)

三种方法分别是:

  • 已知类名,通过类名.class 调用class静态成员变量
  • 已知对应类的对象,通过对象.getClass() 获取对应类的Class
  • 已知自定义类的路径,通过调用Class.forName("xxxx") 来获取

▲ 不管是通过上面哪种方法获取,任何一个类,其对应的Class ,都只会有一个。

java

代码解读

复制代码


public class demo1 {
    public static void main(String[] args) {
        reflectionDemo reflect = new reflectionDemo();
        // 任何一个类都是Class的实例对象
        //第一种表达方式  静态成员变量
        Class c1 = reflectionDemo.class;
        //第二种表达方式 已知某个类的对象,通过其对象调用getClass()方法来获取
        Class c2 = reflect.getClass();
        // 第三种表达方式
        Class c3=null;
        try{
            c3 = Class.forName("Day2.reflectionDemo");
        }
        catch (ClassNotFoundException e){
            e.printStackTrace();
        }

        System.out.println(c3==c2);
    }
}
class reflectionDemo{

}

动态加载类

在Java中,所有直接用new 来创建对象的方式,都叫做静态加载类。只要用这种方法来new对象,那么在编译阶段,都会检查这个对应的类在代码中是否实实在在的存在。

然后在很多时候这都是不必要的。比如说:

java

代码解读

复制代码

public static void main(String[] args){
    Scanner scanner = new Scanner(System.in);
    int num=scanner.nextInt();
    if(num == 1){
        Dog dog = new Dog();
    }
    else if(num==2){
        Cat cat = new Cat(); 
    }
}

上面的代码中,由于是通过new来创建类的对象,所以在编译阶段,就会检查代码中是否确实有Dog类和Cat类。

然而 ,换一个思路,实际上最终而言,最极端的可能,最终Dog类也没用上,Cat类也没用上。(比方说我输入的是3)。那么这时候就要考虑动态加载类。

换一个写法,获取命令行参数来创建对象(下面这儿还是静态创建):

java

代码解读

复制代码

public static void main(String[] args){
    if(args[0].equals("Dog")){
         Dog dog = new Dog();
    }
    if(args[0].equals("Cat")){
         Cat cat = new Cat();
    }
}

如果改成动态创建呢?(下面就暂时不考虑exception)

我们假设Dog类和Cat类都有makeNoise()方法

java

代码解读

复制代码

public static void main(String[] args) throws ClassNotFoundException{
    Class c = Class.forName(args[0]);
    //下面准备调用  c.newInstance()来动态创建类的对象
    //但是args[0]究竟是什么呢?我们又究竟应该创建哪个类对象呢? 这都是未知的。
}

这时候再结合Java的多态

java

代码解读

复制代码

interface Noise{
    public void makeVoice();
}
class Dog implements Noise{
    public void makeVoice(){
        System.out.println("小狗汪汪");
    }
}
class Cat implements Noise{
    public void makeVoice(){
        System.out.println("小猫喵喵");
    }
}

java

代码解读

复制代码

public static void main(String[] args) throws ClassNotFoundException{
    Class c = Class.forName(args[0]);
    Noise n = (Noise)c.newInstance();
    n.makeVoice();
}

获取方法信息

以下是如何使用反射来获取这些信息的步骤:

  1. 获取Class对象:首先,需要获取到目标类的Class对象,这可以通过类名的.class语法或者Class.forName()方法实现。
  2. 获取Method对象:使用Class对象的getMethod()getDeclaredMethod()方法来获取Method对象。getMethod()只能获取公共(public)方法,而getDeclaredMethod()可以获取所有方法,不论其访问权限。
  • 准确地说:getMethod()方法,能够获取到的是当前类的public方法以及其父类的public方法。
  • getDeclaredMethod()方法能够获取到的是当前类的所有方法,无论其方法对应的访问权限修饰符是什么
  1. 获取方法的返回值类型:通过Method对象的getReturnType()方法可以获取方法的返回值类型。
  2. 获取方法名:通过Method对象的getName()方法可以获取方法名。
  3. 获取方法的参数类型:通过Method对象的getParameterTypes()方法可以获取方法的参数类型数组。

获取成员变量的信息

java

代码解读

复制代码

public static void main(String[] args) {
        Class c = Double.class;
        Field[] fields = c.getFields();
        for(Field field:fields){
            Class<?> type = field.getType();
            // 获取类型名称
            String typeName = type.getName();
            // 获取参数名称
            String fieldName = field.getName();
            System.out.println(typeName +" "+fieldName);
        }
    }

注: 这里 getFields()方法获取的是所有public的成员变量的信息,如果是其他的权限修饰符的话,就无法获取。

如果是想获取构造函数信息的话 同理 通过Constructor获取

方法反射的基本操作

  1. 如何获取某个方法
  • 方法的名称方法的参数列表才能唯一决定某个方法
  • 所以这里也就能够落实到Java中方法的重载——Java中方法的重载(已经同名了),仅体现在两个方面,方法的参数数量 以及 方法的类型上
  1. 方法反射的操作
  • method.invoke(对象,参数列表)

java

代码解读

复制代码


public class demo1 {
    public static void main(String[] args) {


        Printer p = new Printer();
        Class c = p.getClass();
        try{
            Method method1 = c.getMethod("print");
            method1.invoke(p);
            Method method2 = c.getMethod("print",new Class[]{int.class,int.class});
            method2.invoke(p,new Object[]{10,20});
            Method method3 = c.getMethod("print",new Class[]{int.class,String.class});
            method3.invoke(p,new Object[]{10,"asdf"});
        }
        catch(Exception e){
            e.printStackTrace();
        }

    }

}
class Printer{
    public void  print(){
        System.out.println("无参print");
    }
    public void print(int a,int b){
        System.out.println("这是双int形参的print方法");
    }
    public void print(String a,String b){
        System.out.println("这是双String的print方法");
    }
    public void print(int a,String b){
        System.out.println("这是int和String各一个形参的print方法");
    }

}

输出如下:

无参print 这是双int形参的print方法 这是int和String各一个形参的print方法

通过反射了解集合泛型的本质

通过这里的反射,更好地去理解上面泛型章节中说的“类型擦除”究竟是什么

java

代码解读

复制代码

public class demo1 {
    public static void main(String[] args) {
        List list1 = new ArrayList();
        List<String> list2 = new ArrayList<>();
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c1==c2);
        //--------------------
        try{
            Method method=c2.getMethod("add",Object.class);
            method.invoke(list2,1);
            System.out.println(list2.size());
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }
}

▲ 注意看这里的输出

首先打印出来c1 和 c2都是class java.util.ArrayList

并且c1==c2 为true

然后专门add一个1到list2

其size从0 -》1 明显看到已经add进来了。这就是类型擦除!!

class java.util.ArrayList class java.util.ArrayList true 1

反射小练习:

注意:

  1. 如果在动态创建对象的时候,希望传参,那么只能通过获取对应的有参构造函数,并通过该构造函数来进行创建对象
  2. 正如下面例子中的bark方法所示,如果希望通过invoke来调用private修饰的成员方法,那么必须要调用setAccessable()方法,使其变得“可访问”

java

代码解读

复制代码

package com.nylonmin;

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

/**
 * 练习反射
 *
 */
public class Demo1 {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("com.nylonmin.Dog");
        Method[] methods = c.getDeclaredMethods();
        System.out.println("类 "+c.getName()+" 具有以下方法");
        for(Method method:methods){
            System.out.println(method.getName());
        }
        System.out.println("-----------");
        //step1 动态创建对象 通过构造函数来创建  调用有参构造函数
        Constructor constructor = c.getConstructor(String.class,int.class);
        Dog dog = (Dog)constructor.newInstance("小灰灰", 2);
        //step2 尝试调用bark方法
        Method barkMethod = c.getDeclaredMethod("bark", String.class);
        //因为bark这个方法被我定义成为私有的 所以要让其变得可访问
        barkMethod.setAccessible(true);
        barkMethod.invoke(dog,"汪汪汪");

    }
}
class Dog{
    private String name;
    private int age;

    public Dog(){}
    public Dog(String name,int age){
        this.name=name;
        this.age=age;
    }
    //特意定义为私有方法 看看获取情况
    private void bark(String voice){
        System.out.println(this.name+"是这样叫的——"+voice);
    }
    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;
    }
}

动态代理

程序为什么需要代理?代理长什么样?

  • 对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。
  • 对象有什么方法想被代理,代理就一定要有对应的方法。

代理就相当于是明星的经纪人,为什么说对象有什么方法想被代理,代理就一定要有对应的方法呢?

仍然是类比到明星和经纪人,比方说某老板想找明星唱歌,他势必首先会找到其经纪人进行对接,这时候如果老板看到经纪人根本不对外接唱歌业务,那肯定无法完成。

动态代理实例

从这个实例中实际上我们就能够一定程度地去理解Java中的AOP面向切面编程的概念了。

现在有这么个场景,比如某个类有一百个方法,现在要求对于执行该类中每个方法,都有一个性能指标,要求其运行时间不超过2秒钟。—— 那么最简单的思路就是在每个方法中都加入当前开始时间、当前结束时间 以及 计算时长的代码;这样导致的结果就是这100个方法中都充斥了重复且与业务无关的代码。

这时候就要用到动态代理了。

▲ 为什么呢?上面提到的这个类,实际上就相当于是我们前面说的杨超越大明星,那是她作为明星的业务功能,而计算开始时间结束时间之类的,是经纪人的活儿,我们需要用一个代理来帮大明星做。

JAVA

代码解读

复制代码

package com.nylonmin.Proxy;

public interface UserService {
    public void login() throws InterruptedException;
    public void sing(String songName) throws InterruptedException;
}
//------------------------
package com.nylonmin.Proxy;

public class UserServiceImpl implements UserService{
    private String name;
    private int age;

    public UserServiceImpl(){}
    public UserServiceImpl(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public void login() throws InterruptedException {
        System.out.println("用户进入本系统");
        Thread.sleep(1000);
    }

    @Override
    public void sing(String songName) throws InterruptedException {
        System.out.println("用户开始唱"+songName);
        int num=0;
        for(int i=0;i<100;i++){
            num++;
        }
        Thread.sleep(1000);
    }
    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 static UserService createProxy(UserService userService111) 这里面的形参,传入的是大明星,也就是说要对谁进行代理,这里的返回值类型UserService 表示我们想要创建什么类型的代理。
  • UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),new Class[]{UserService.class}, new InvocationHandler()
  • 第一个参数 直接是当前类的class loader加载就行了 第二个参数 new一个接口的数组,指定代理长什么样 第三个参数new InvocationHandler指定代理具体干啥事儿

java

代码解读

复制代码

package com.nylonmin.Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public interface ProxyUtil {
    public static UserService createProxy(UserService userService111){
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if(method.getName().equals("sing")||method.getName().equals("login")){
                            long startTime = System.currentTimeMillis();
                            Object rs = method.invoke(userService111, args);
                            long endTime = System.currentTimeMillis();
                            System.out.println(method.getName()+"执行耗时"+(endTime-startTime)/1000.0 +"秒");
                            return rs;
                        }
                        //传入的可能是其他方法 比如get set方法
                        else{
                            Object invoke = method.invoke(userService111, args);
                            return invoke;
                        }
                    }
                });
        return userServiceProxy;
    }
}


转载来源:https://juejin.cn/post/7430074347824037914

相关文章
|
5月前
|
安全 Java 测试技术
day26:Java零基础 - 反射
【7月更文挑战第26天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
35 5
|
6月前
|
Oracle IDE Java
Java基础13-深入理解反射机制(一)
Java基础13-深入理解反射机制(一)
54 5
|
6月前
|
Java 编译器 API
Java基础13-深入理解反射机制(二)
Java基础13-深入理解反射机制(二)
27 4
|
移动开发 Java 网络安全
【java】反射基础
【java】反射基础
56 0
|
Java API
Java基础篇 - 反射机制
Java基础篇 - 反射机制
|
设计模式 XML Java
Java基础 | 反射机制
反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法,反射在设计模式和框架底层都会用到
138 0
Java基础 | 反射机制
|
Java
Java基础 | 反射
Java中的反射
88 0
Java基础 | 反射
|
Java
Java基础篇:反射机制详解
Java基础篇:反射机制详解
121 0
|
Java 数据库 Spring
Java的膝跳反射
什么是反射? 反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力
89 0