Java反射机制(2)

简介: Java反射机制

Java反射机制(1)https://developer.aliyun.com/article/1530899

1.调用方法

当我们获取到一个Method类对象时,就可以对它进行调用。我们以下面的代码为例:

// 一般情况下调用 String 类的 substring() 方法
String s = "Hello world";
String r = s.substring(6); // "world"
如果用反射来调用substring方法,需要以下代码:
import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        // String 对象:
        String s = "Hello world";
        // 获取 String substring(int)方法,形参为 int:
        Method m = String.class.getMethod("substring", int.class);
        // 在 s 对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);
    }
}
import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        // String 对象:
        String s = "Hello world";
        // 获取 String substring(int)方法,形参为 int:
        Method m = String.class.getMethod("substring", int.class);
        //获得方法 的所属的类
        System.out.println(m.getDeclaringClass());
        // 在 s 对象上调用该方法并获取结果:
        String r = (String) m.invoke("qqqqqqqqq", 6);
        // 打印调用结果:
        System.out.println(r);
    }
}
  • 注意到substring()有两个重载方法,我们获取的是String substring(int)这个方法(即形参类型为 int,且只有一个)。思考一下如何获取String substring(int, int)方法。
  • 对Method类对象调用invoke方法就相当于调用该substring(int)方法,invoke的第一个参数是实例对象(即在哪个实例对象上调用该方法),后面的实参要与方法参数的类型一致,否则将报错。
2.调用静态方法

如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。我们以Integer.parseInt(String)方法为例:

import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        // 获取 Integer.parseInt(String) 方法,参数为 String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        // 调用该静态方法并获取结果:
        Integer n = (Integer) m.invoke(null, "12345");
        // 打印调用结果:
        System.out.println(n);// 12345
    }
}

经过测试,如果是静态方法invoke的第一个参数可以为任意对象

3.调用非 public方法

和Field类对象类似,对于非 public 方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法的实例对象,但直接对其调用将得到一个IllegalAccessException异常。为了调用非 public 方法,我们通过Method.setAccessible(true)允许其调用:

import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);
        m.invoke(p, "Bob");
        System.out.println(p.name);// Bob
    }
}
class Person {
    String name;
    
    private void setName(String name) {
        this.name = name;
    }
}
  • 同样,setAccessible(true)可能会失败。如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全
4. 多态

我们来考率这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也重写了hello()方法,那么,从Person.class获取的Method,作用于Student类对象时,调用的hello()方法到底是哪个?

import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Person的 hello方法:
        Method h = Person.class.getMethod("hello");
        // 对 Student实例调用 hello方法:
        h.invoke(new Student());
    }
}
class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}
class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}

调用的是子类的

  • 运行上述代码,发现输出的是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的重写方法(如果存在)。 上述的反射代码:
Method m = Person.class.getMethod("hello");
m.invoke(new Student());
  • 实际相当于
Person p = new Student();
p.hello();
5.小结
  1. Java 的反射 API 提供的Method类对象封装了类定义的全部方法的所有信息:
  2. 通过Class类对象的方法可以获取Method类对象:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
  3. 通过Method类对象可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
  4. 通过Method类对象可以调用某个对象的方法:Object invoke(Object instance, Object… parameters);
  5. 通过设置setAccessible(true)来访问非public方法;
  6. 通过反射调用方法时,仍然遵循多态原则。

调用构造方法

一般情况下,我们通常使用new操作符创建新的对象:

Person p = new Person();

如果通过反射来创建新的对象,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();
  • 调用Class.newInstance()的局限是,它只能调用该类的public无参构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

为了调用任意的构造方法,Java 的反射 API 提供了Constructor类对象,它包含一个构造方法的所有信息,通过Constructor类对象可以创建一个类的实例对象。Constructor类对象和Method类对象非常相似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回一个类的实例对象:

import java.lang.reflect.Constructor;
public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法 Integer(int),形参为 int
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        // 传入的形参必须与构造方法的形参类型相匹配
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);
        // 获取构造方法Integer(String),形参为 String
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

通过Class实例获取Constructor的方法如下:

  1. getConstructor(Class…):获取某个public的Constructor 参数是构造器参数的Class对象;
  2. getDeclaredConstructor(Class…):获取某个Constructor;
  3. getConstructors():获取所有public的Constructor;
  4. getDeclaredConstructors():获取所有Constructor。

注意:Constructor类对象只含有当前类定义的构造方法,和父类无关,因此不存在多态的问题。

同样,调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。但setAccessible(true)也可能会失败。

小结

Constructor类对象封装了其对应的类定义的构造方法的所有信息;

通过Class类对象可以获取Constructor类对象:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();

通过``Constructor类对象可以创建一个对应类的实例对象:newInstance(Object… parameters); 通过设置setAccessible(true)`来访问非public

六、获取继承方法

当我们获取到某个Class类对象时,实际上就获取到了一个类的类型:

Class cls = String.class; // 获取到 String 的 Class类对象

1

还可以用类对象的getClass()方法获取:

String s = "";
Class cls = s.getClass(); // s是String,因此获取到String的Class

最后一种获取Class的方法是通过Class.forName(“”),传入Class的完整类名获取:

Class s = Class.forName("java.lang.String");

1

这三种方式获取的Class类对象都是同一个对象,因为 JVM 对每个加载的Class只创建一个Class类对象来表示它的类型。

  1. 获取父类的Class
    有了Class类对象,我们还可以获取它的父类的Class类对象:
public class Main {
    public static void main(String[] args) throws Exception {
        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n);
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
    }
}

运行上述代码,可以看到,Integer的父类类型是Number,Number的父类是Object,Object的父类是null。除Object外,其他任何非接口interface的Class类对象都必定存在一个父类类型。

  1. 获取interface
    由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口:
import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

运行上述代码可知,Integer实现的接口有:

java.lang.Comparable
java.lang.constant.Constable
java.lang.constant.ConstantDesc
要特别注意:getInterfaces()方法只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型:
// reflection
import java.lang.reflect.Method;
public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class.getSuperclass();
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

Integer的父类是Number,Number类实现的接口是java.io.Serializable。

此外,对所有接口interface的Class类对象调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces():

System.out.println(java.io.DataInputStream.class.getSuperclass()); 
// 输出 java.io.FilterInputStream。因为 DataInputStream 继承自 FilterInputStream
System.out.println(java.io.Closeable.class.getSuperclass()); 
// 输出 null。因为对接口调用 getSuperclass()总是返回 null,获取接口的父接口要用 getInterfaces()

如果一个类没有实现任何interface,那么getInterfaces()返回空数组。

  1. 继承关系
    当我们判断一个对象是否是某个类型时,正常情况下,使用instanceof操作符:
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true

如果是两个Class类对象,要判断一个向上转型是否成立,可以调用isAssignableFrom()方法:

// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
**小结**

通过Class对象可以获取继承关系:

Class getSuperclass():获取父类类型;

Class[] getInterfaces():获取当前类实现的所有接口。

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

相关文章
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
131 2
|
3月前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
2月前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
98 20
|
2月前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
149 1
|
2月前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
74 2
|
2月前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
3月前
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
166 6
|
2月前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
87 4
|
2月前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
59 3
|
3月前
|
安全 Java 数据安全/隐私保护
有哪些场景不适合使用Java反射机制
Java反射机制虽强大,但并非万能。在性能要求极高、安全性严格控制、类结构复杂多变或对象创建频繁的场景下,使用反射可能带来性能下降、安全风险增加等问题,应谨慎选择。
93 11