Java基础——反射

码农老张 后端 2024-11-25

Java基础——反射

反射

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()方法能够获取到的是当前类的所有方法,无论其方法对应的访问权限修饰符是什么
  3. 获取方法的返回值类型:通过Method对象的getReturnType()方法可以获取方法的返回值类型。

  4. 获取方法名:通过Method对象的getName()方法可以获取方法名。

  5. 获取方法的参数类型:通过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中方法的重载(已经同名了),仅体现在两个方面,方法的参数数量 以及 方法的类型上
  2. 方法反射的操作

    • 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

Apipost 私有化火热进行中

评论