Java反射

文章介绍了Java反射机制,它是运行时获取类的变量和方法信息并进行动态操作的能力。通过Class类的三种获取方式展示了如何访问类信息。动态加载类如Class.forName()在运行时加载,避免了编译时必须知晓所有类的需求。文章还讨论了静态和动态加载的区别,并提供了获取类的成员变量、构造方法和方法信息的示例代码。最后,提到了泛型在编译后的去泛型化以及如何通过反射绕过编译阶段的限制。
摘要由CSDN通过智能技术生成

Java反射

概述

什么是反射?

Java反射机制:

​ 是指运行时去获取一个类的变量个方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍可扩展。

Class类

在面向对象的世界里,万事万物皆对象,
Java语言中,静态的成员、普通数据类型类是不是对象呢?

类是对象,类是java.lang.Class类的实例对象

There is a class named Class

获取Class类类型的三种方式

(1)类名.class
(2)类对象.getClass()
(3)Class.forName(“classpath”)

代码如下:

public static void main(String[] args) {
    /**
     * 获取类对象的三种方式:
     * (1)类名.class
     * (2)类对象.getClass()
     * (3)Class.forName("classpath")
     */
    Person person = new Person();
    // 1.类名.class
    Class c1 = person.getClass();
    // 2.类对象.getClass()
    Class c2 = Person.class;
    System.out.println(c1 == c2); // true
    try {
        // (3)Class.forName("classPath")
        Class c3 = Class.forName("Person");
        System.out.println(c2 == c3); // true
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

动态加载类

其中Class.forName("classpath") ,不仅表示了类类型,还代表了动态加载类;

  • 请大家区分编译运行
  • 编译时刻加载类是静态加载类
  • 运行时刻加载类是动态加载类

什么是静态加载?

举个例子:

目前没有WordExcel这两个类,编译肯定是不通过的。找不到符号 类Word、类Excel,想要通过编译,就需要创建WordExcel这两个类。言归正传,所以什么才是静态加载类?

  • 使用new 创建的对象是静态加载类,在编译时刻就需要加载所有可能使用到的类。使用动态加载可以解决这一问题。
public static void main(String[] args) {
    if("word".equals(args[0])){
        Word w = new Word();
        w.start();
    }

    if("excel".equals(args[0])){
        Excel e = new Excel();
        e.start();
    }
}

什么是动态加载?

利用反射机制,创建对象的实例对象。

举个例子:

定义一个接口Office,作为一个标准

public interface Office {
    void start();
}

WordExcel 都实现这个接口,这样我们传入参数时,就会动态的加载指定的类。

测试

public static void main(String[] args) {
    // 动态加载类,运行时刻加载
    Class clazz = Class.forName(args[0]);
    // 通过类型加载,创建该类对象 jdk9之后,class.newInstance() 过时!!!
    Office w = (Office) clazz.getDeclaredConstructor().newInstance();
    w.start();
}

编译

javac -encoding UTF-8 classDemo3.java

运行

java ClassDemo3 Word
word start

通过动态加载,可以在不修改原来的代码上进行扩展,只需要扩展子类,也符合开闭原则

反射的常用操作

获取类的成员变量

Field [] getFields(); 返回所有公共成员变量对象的数组
Field []getDeclaredFields(); 返回所有成员变量对象的数组
Field getFields(String name); 返回单个公共成员变量对象
Field getDeclaredFields(String name); 返回单个成员变量对象

public static void printFieldMessage(Object obj){
    Class<?> clazz = obj.getClass();
    /**
     * 成员变量也是对象
     * java.lang.reflect.Field
     * Field类封装了关于成员变量的操作
     * getFields(): 获取所有public的成员变量的信息
     * getDeclaredFields(): 获取该类自己声明的成员变量的信息
     */
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        // 得到成员变量的类型的类类型
        Class<?> fieldType = field.getType();
        String typeName = fieldType.getName();
        // 得到成员变量的名称
        String fieldName = field.getName();
        System.out.println(typeName + " " + fieldName);
    }
}

获取类的构造方法

Constructor<?> getConstructors() 返回所有公共构造方法对象的数组
Constructor<?> getDeclareConstructors() 返回所有构造方法对象的数组
Constructor<?> getConstructor(Class<?>...parameterTypes) 返回单个公共构造方法对象
Constructor<?> getDeclareConstructor(Class<?>...parameterTypes) 返回单个构造方法对象
T newInstance(Object...initargs); 根据指定的构造方法创造对象

public static void printConstructorMessage(Object obj){
    Class<?> clazz = obj.getClass();
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.print(constructor.getName()+"(");
        // 获取构造函数的参数列表
        Class<?>[] parameterTypes = constructor.getParameterTypes();
        for (Class<?> parameterType : parameterTypes) {
            System.out.print(parameterType.getName()+",");
        }
        System.out.println(")");
    }
}

获取类的方法信息

Class类中用于获取成员方法的方法
Method[] getMethods(); 返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods(); 返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name,Class<?>...parameterTypes); 返回单个公共成员方法对象
Method getDeclareMethod(String name,Class<?>..parameterTypes); 返回单个成员方法对象

Method类中用于调用成员方法的方法
Object invoke(Object obj,Object...args); 调用obj对象的成员方法,参数是args,返回值是Object类型

public static void printClassMessage(Object obj) {
    // 1.获取类类型
    Class<?> clazz = obj.getClass();
    System.out.println("类的名称是:" + clazz.getName());

    /**
     * Method:成员方法
     * 一个成员方法就是一个Method方法
     * getMethods(): 获取所有Public的方法,包括父类继承的
     * getDeclaredMethods(): 获取该类自己声明的方法,不限制访问权限,不包括父类
     */
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        // 方法返回值类型
        Class<?> returnType = method.getReturnType();
        System.out.print(returnType.getName() + " ");
        // 方法名称
        System.out.print(method.getName() + "(");
        // 方法中参数的类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (Class<?> parameterType : parameterTypes) {
            System.out.print(parameterType.getName() + ",");
        }
        System.out.println(")");
    }
}

测试

String为例

public static void main(String[] args) {
   ClassUtil.printClassMessage("hello");
}

控制台

类的名称是:java.lang.String
[B value()
boolean equals(java.lang.Object,)
int length()
java.lang.String toString()
int hashCode()
void getChars(int,int,[C,int,)
int compareTo(java.lang.String,)
int compareTo(java.lang.Object,) 
....

方法的反射

  1. 如何获取某个方法
    通过方法的名称参数列表可以确定唯一的某个方法

  2. 方法反射的操作
    method.invoke(实例对象,参数列表)

代码演示

Cal类

class Cal {
    public void print() {
        System.out.println("hello word");
    }

    public void print(int i, int j) {
        System.out.println(i + j);
    }

    public void print(String s, String t) {
        System.out.println(s + t);
    }
}

测试类

public static void main(String[] args) {
    // 1.获取类类型
    Cal cal = new Cal();
    Class<? extends Cal> clazz = cal.getClass();
    // 2.获取方法
    try {
        // 若参数列表没有参数,则可以直接省略
        Method method = clazz.getMethod("print",int.class,int.class);
        method.invoke(cal, 1,2); // 有返回值时返回返回值,没有则返回null
    } catch (Exception e) {
        e.printStackTrace();
    }
}

控制台

3

集合中泛型的本质

先看一段代码

public static void main(String[] args) {
    List list = new ArrayList();
    List<String> list1 = new ArrayList<>();

    System.out.println(list.getClass() == list1.getClass()); // true
}

代码运行之后,发现两个list的类类型相同。我们将代码进行手动编译。

编译之后的代码

public class ListDemo {
    public ListDemo() {
    }

    public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();
        ArrayList var2 = new ArrayList();
        System.out.println(var1.getClass() == var2.getClass());
    }
}

我们发现,编译之后集合的泛型是去泛型化的。也就是说,泛型只在编译阶段有效,经过编译就无效了。只是为了防止我们输入错误

通过反射,我们可以绕过编译阶段,将不同类型的值添加到集合中。

代码演示

public static void main(String[] args) {

    List<String> list1 = new ArrayList<>();
    
    // 获取class对象
    Class<? extends List> clazz = list1.getClass();
    // 获取方法
    try {
        Method add = clazz.getMethod("add", Object.class);
        add.invoke(list1,"hello");
        add.invoke(list1,20);
        System.out.println(list1.size()); // 2
    } catch (Exception e) {
        e.printStackTrace();
    }
}

你会发现,集合遍历的时候,会出现异常。其实,最后我们只需要知道,反射的操作是绕过编译的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值