《类的加载与反射》第3节:反射

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《类的加载与反射》第3节:反射相关的知识,希望对你有一定的参考价值。

​JAVA的反射机制是指在运行状态中,对于任意一个类都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。反射是Java语言中非常重要机制,很多第三方框架都用到了反射,本小节将详细讲解反射机制的原理和作用。

19.3.1获得Class类对象

每个类被加载之后,虚拟机就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到这个类。在Java程序中获得Class对象通常有如下三种方式:​

  • 使用Class类的forName()静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名,全限定类名包含完整的包名和类名。​
  • 调用某个类的class属性来获取该类对应的Class对象。例如,Person.class 将会返回Person类对应的Class对象。​
  • 调用某个对象的getClass()方法。该方法是Object 类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。​

第一种方式和第二种方式都是直接根据类来取得该类的Class对象。相比之下,第二种方式有如下两种优势。​

  • 代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在。​
  • 程序性能更好。因为这种方式无须调用方法,所以性能更好。​

也就是说,大部分时候都应该使用第二种方式来获取指定类的Class对象。但如果程序只能获得一个字符串,例如“java.lang.String”,如果需要根据字符串来获取对应的Class对象,则只能使用第一种方式,即使用Class的forName()静态方法获取Class对象,该方法可能抛出ClassNotFoundException异常。一旦获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了。​

19.3.2从Class类对象中获取类的信息

Class类代表了一个类,而一个类的方法、属性以及所在的包也都可以用类来表示。表示普通方法的类是Method,表示构造方法的类是Constructor,表示属性的类是Field,而表示包的类是Package,只有了解了这些类的意义才能很好的学习Class类。​

Class类提供了大量方法来获取该Class对象所对应类的详细信息,这些方法中很多都包括多个重载的版本,由于重载版本众多,下面的表19-2对每个方法只列出一个版本,读者可以查阅API文档来查看每个方法的详情。​

表19-2 Class类的方法​

方法

功能

Connstructor<T> getConstructor(Class<?>... parameterTypes)

返回此Class对象对应类的、带指定形参列表的public构造方法。

Constructor<?>[] getConstructors()

返回此Class对象对应类的所有public构造方法

Constructor<T> getDeclaredConstructor(Class <?>... parameterTypes)

返回此Class对象对应类的、带指定形参列表的构造方法,与构造方法的访问权限无关。

Constructor<?>[] getDeclaredConstructors()

返回此Class对象对应类的所有构造方法,与构造方法的访问权限无关

Method getMethod(String name, Class<?> ... parameterTypes)

返回此Class对象对应类的、带指定形参列表的public方法

Method[] getMethods()

返回此Class对象所表示的类的所有public方法

Method getDeclaredMethod(String name, Class<?>.. parameterTypes)

返回此Class对象对应类的、带指定形参列表的方法,与方法的访问权限无关

Method[] getDeclaredMethods()

返回此Class对象对应类的全部方法,与方法的访问权限无关

Field getField(String name)

返回此Class对象对应类的、指定名称的public属性

Field[]getFields()

返回此Class对象对应类的所有public属性

Field getDeclaredField(String name):


返回此Class对象对应类的、指定名称的属性,与属性的访问权限无关

Field[] getDeclaredFields():


返回此Class对象对应类的全部属性,与属性的访问权限无关

<A extends Annotation> A getAnnotation(Class<A> annotationClass);​

尝试获取该Class对象对应类上存在的、指定类型的注解,如果该类型的注解不存在,则返回null

<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)

该方法尝试获取直接修饰该Class对象对应类的、指定类型的注解,如果该类型的注解不存在,则返回null

Annotation[] getAnnotations()

返回修饰该Class对象对应类上存在的所有的注解

Annotation[] getDeclaredAnnotations()

返回直接修饰该Class对应类的所有注解

<A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass)

该方法的功能与getAnnotation()方法基本相似。但由于Java8增加了重复注解功能,因此需要使用该方法获取修饰该类的、指定类型的多个注解

Class<?>[] getDeclaredClasses()

返回该Class对象对应类里包含的全部内部类

Class<?> getDeclaringClass()

返回该Class对象对应类所在的外部类

Class<?>[] getlnterfaces()

返回该Class对象对应类所实现的全部接口

Class<? super T> getSuperclass()

返回该Class对象对应类的父类的Class对象

int getModifiers()

返回此类或接口的所有修饰符。修饰符由public、 protected、private、final、static、abstract 等对应的常量组成,返回的整数应使用Modifer工具类的方法来解码,才可以获取真实的修饰符

Package getPackage()

获取此类的包

String getName()

以字符串 形式返回此Class对象所表示的类的名称

String getSimpleName()

以字符串形式返回此Class对象所表示的类的简称,即不包含包名,只类名

boolean isAnnotation()。

:

返回此Class对象是否表示一个注解类型

boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

判断此Class 对象是否使用了Annotation修饰

boolean isAnonymousClass()

返回此Class对象是否是一个匿 名类

boolean isArray()

返回此Class对象是否表示一个数组类

boolean isEnum()

返回此Class对象是否表示一个枚举

boolean isInterface

返回此Class对象是否表示一个接口

boolean islnstance(Object obj)

判断obj是否是此Class 对象的实例,该方法与

instanceof操作符作用相同

表19-2中,getMethod0方法和getConstructor()方法都需要传入多个类型为Class<?>的参数,这些参数用于获取指定的方法或指定的构造方法。假设某个类内包含如下三个版本的info()方法:​

  • public void info()​
  • public void info(String str)​
  • public void info(String str , Integer num)​

这三个同名方法属于重载,它们的方法名相同,但参数列表不同。在Java语言中要确定一个方法​

光有方法名是不行的,如果仅仅只指定info()方法,实际上可以是上面三个方法中的任意一一个。如果需要确定一个方法,则应该由方法名和参数列表来确定,但参数名没有任何实际意义,所以只能由参数类型来确定。例如想指定第二个info()方法,则必须指定方法名为info,形参列表为String.class。因此在程序中获取第二个info()方法使用如下代码​

//前一个参数指定方法名,后面的个数可变的Class参数指定参数类型列表​
clazz. getMethod("info",String.class)

如果需要获取第三个info()方法,则使用如下代码:​

//前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表​
clazz . getMethod("info", String.class, Integer.class)

获取构造方法时无须传入构造方法名称,因为同一个类的所有构造方法的名字都是相同的,所以要确定一个构造方法只要指定参数列表即可。下面的【例19_06】展示了如何通过Class类对象获得类的信息。​

【例19_06使用Class类对象获取类的信息】

Exam19_06.java​

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.Arrays;
//定义可重复注解
@Repeatable (Annos.class)
@interface Anno
@Retention (value=RetentionPolicy.RUNTIME)
@interface Annos
Anno[] value();

//使用4个注解修饰该类
@ SuppressWarnings (value="unchecked")
@ Deprecated
//使用重复注解修饰该类
@Anno
@Anno

class ClassTest
//为该类定义一个私有的构造方法
private ClassTest ()


//定义一个有参数的构造方法
public ClassTest (String name)
System.out.println ("执行有参数的构造方法");

//定义一个无参数的info方法
public void info()
System.out.println( "执行无参数的info方法");

//定义一个有参数的info方法
public void info(String str)
System.out.println("执行有参数的info方法" + ",其str参数值:"+ str);

//定义一个测试用的内部类
class Inner

public class Exam19_06
public static void main(String[] args) throws Exception
//下面代码可以获取ClassTest对应的Class
Class<ClassTest> clazz = ClassTest.class;
//获取该Class对象所对应类的全部构造方法
Constructor[] ctors = clazz . getDeclaredConstructors ();
System.out.println ("ClassTest的全部构造方法如下: ");
for (Constructor c : ctors)
System.out.println(c);
//获取该Class对象所对应类的全部public构造方法
Constructor[] publicCtors = clazz. getConstructors();
System.out.println ("ClassTest的全部public构造方法如下: ");
for(Constructor c : publicCtors)
System.out.println(c);

//获取该Class对象所对应类的全部public方法
Method[] mtds = clazz .getMethods();
System.out.println ("ClassTest的全部public方法如下: ");
for (Method md : mtds )
System.out.println (md);

//获取该Class对象所对应类的指定方法
System.out.println("ClassTest里带一个字符串参数的info方法为:"+clazz.getMethod("info",String.class));
//获取该Class对象所对应类的全部注解
Annotation[] anns = clazz.getAnnotations();
System.out.println ("ClassTest的全部Annotation如下: ");
for(Annotation an : anns)
System.out.println(an);

System.out.println ("该Class元素上的@SuppressWarnings注解为: "+ Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
System.out.println("该Class元素上的@Anno注解为: " + Arrays.toString (clazz.getAnnotationsByType(Anno.class)));
//获取该Class对象所对应类的全部内部类
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println ("ClassTest的全部内部类如下: ");
for (Class c : inners)
System. out.println(c);

//使用Class. forName ()方法加载ClassTest的Inner内部类
Class inClazz = Class.forName ("ClassTest$Inner");
//通过getDeclaringClass ()访问该类所在的外部类
System.out.println ("inClazz对应类的外部类为: " +inClazz.getDeclaringClass());
System.out.println ("ClassTest的包为: " + clazz.getPackage());
System.out.println ("ClassTest的父类为: "+ clazz.getSuperclass());

【例19_06】中Annotation表示注解,关于注解的知识将在19.4小节讲解。【例19_06】的运行结果如图19-6所示。​

第十九章《类的加载与反射》第3节:反射_Java

图19-6【例19_06】运行结果​

从图19-6可以看出:getMethods()方法不仅仅能够获得这个类中定义的方法,还可以获得这个类从父类中继承过来的方法。需要注意:虽然定义ClassTest类时使用了@SuppressWarnings注解,但程序运行时无法分析出该类里包含的该注解,这是因为@SuppressWarmings使用了@Retention(value=SOURCE)修饰,这表明@SuppesWarnings只能保存在源代码级别上,而通过ClassTest.class获取该类的运行时Class对象,所以程序无法访问到@SuppressWarnings注解。​

19.3.3方法参数反射

Java 8在java.lang.reflect包下新增了一个Executable抽象类,这个类代表所有方法,它有两个子类,分别是代表构造方法的Constructor和代表普通方法的Method。Executable类提供了大量方法来获取修饰该方法的注解信息,还提供了isVarArgs()方法用于判断该方法是否包含数量可变的形式参数,以及通过getModifiers()方法来获取该方法的修饰符。除此之外,Executable提供了如表19-3所示的两个方法来获取该方法的参数个数及参数名称。​

表19-3 Executable类获取参数个数及参数名称的方法​

方法

功能

int getParameterCount()

获取该方法的参数个数

Parameter[] getParameters()

获取该方法的所有参数

表19-3中,Parameter类就表示方法的形式参数。Parameter也提供了大量方法来获取声明该参数的各项信息,如表19-4所示。​

表19-4 Parameter类的常用方法​

方法

功能

getModifiers()

获取修饰该参数的修饰符

String getName()

获取参数名称

Type getParameterizedType()

获取带泛型的参数类型

Class<?> getType()

获取参数类型

boolean isNamePresent()

判断该方法返回该类的class文件中是否包含了方法的参数名称信息

boolean isVarArgs()

判断该参数是否为个数可变的形参

需要指出:IDEA编译Java 源文件时,默认生成的class文件并不包含方法的参数名称信息,因此调用isNamePresent()方法将会返回false,调用getName0方法也不能得到该参数的形参名。如果希望编译Java源文件时可以保留参数名称信息,需要对IDEA进行编译参数的设置,具体步骤是:以打开“File”菜单,选择“Settings”菜单项,在弹出的对话框中按照“Build,Execution,Deployment”->“Compiler”->“Java Compiler”的顺序选择选项,并在“Additional command line parameters”后面填上“-parameters”,如图19-7所示。​

第十九章《类的加载与反射》第3节:反射_Java_02

图19-7 设置编译参数​

下面的【例19_07】展示了方法参数反射实现过程。​

【例19_07方法参数反射】

Exam19_07.java​

import java.util.*;
import java.lang.reflect.*;
class ParameterTest
public void rep(String str,List<String> list)

public class Exam19_07
public static void main(String[] args)throws Exception
//获取String的类
Class<ParameterTest> clazz = ParameterTest.class;
//获取String类的带两个参数的replace()方法
Method rep = clazz.getMethod("rep", String.class, List.class);
//获取指定方法的参数个数
System.out.println("rep()方法参数个数: " + rep.getParameterCount ());
//获取replace的所有参数信息
Parameter[] parameters = rep.getParameters();
int index = 1;
//遍历所有参数
for (Parameter p : parameters)
if (p.isNamePresent())
System.out.println("---第" + index++ + "个参数信息---");
System.out.println("参数名: " + p.getName());
System.out.println("形参类型: " + p.getType());
System.out.println("泛型类型:" + p.getParameterizedType());



如果希望能够正确运行【例19_07】,就必须对IDEA设置编译参数,设置完成后还需重新编译项目,具体做法是:打开“菜单”,单击“Rebuild Project”菜单项。完成这一步操作后,运行【例19_07】的结果如图19-8所示。​

第十九章《类的加载与反射》第3节:反射_反射_03

图19-8【例19_07】运行结果​

19.3.4利用反射生成并操作对象

一个Class类对象就代表一个类,程序员不仅仅能够通过这个Class类对象获得对应类的信息,还能通过这个Class类对象创建出对应类的对象。这句话听起来有点绕口,它的意思是:如果一个Class类对象clazz代表A类,那么程序员能够通过clazz来创建出一个A类对象。19.3.3小节曾介绍过:Constructor类代表类的构造方法,而通过反射的方式来生成对象需要先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的对象。下面的【例19_08】展示了使用反射方式创建对象的过程。​

【例19_08 用反射技术创建对象1】

Exam19_08.java​

import java.util.*;
class ObjectPoolFactory
String[] classNames;//存放类名称的字符串数组
//定义一个Map类集合,Map的key是对象名,value是实际对象
Map<String ,Object> objectPool = new HashMap<String ,Object>() ;
//构造方法
public ObjectPoolFactory(String[] classNames)
this.classNames = classNames;

//createObject()方法根据String型参数clazzName生成Java对象
private Object createObject (String clazzName) throws Exception
//根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName (clazzName) ;
//使用clazz对应类的默认构造器创建实例
return clazz. getConstructor().newInstance() ;

//initPool()方法根据一组类的名称生成相应的对象并存入objectPool
public Map<String ,Object> initPool() throws Exception
if(classNames!=null)
for (int i=0;i< classNames.length;i++)
Object o = createObject(classNames[i]);//根据类名创建对象
objectPool.put(classNames[i],o);//把类名和创建出的对象存入objectPool


return objectPool;



public class Exam19_08
public static void main(String[] args) throws Exception
String[] className = "java.text.SimpleDateFormat","java.util.Date";
ObjectPoolFactory opf = new ObjectPoolFactory(className);
Map<String,Object> objectPool = opf.initPool();

以上是关于《类的加载与反射》第3节:反射的主要内容,如果未能解决你的问题,请参考以下文章

阶段1 语言基础+高级_1-3-Java语言高级_09-基础加强_第2节 反射_10_反射_Class对象功能_获取Method成员方法

Java学习注解和反射超详细笔记

慕课网_反射——Java高级开发必须懂的

类加载器与反射

类加载机制与反射

第4节:Java基础 - 必知必会(中)