概述
Java反射机制是java语言的一个重要特性,首先我们要了解两个概念:编译期和运行期。
编译期
编译期是指把源代码交给编译器编译成计算机可以执行的文件的过程。在Java中,也就是把Java代码编译成class文件的过程,编译器只是做了一些翻译功能,并没有把代码放在内存中运行起来。
运行期
是指把编译后的文件交给计算机执行,直到程序结束,所谓运行期就是把在磁盘中的代码放在内存中运行起来。
而Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
Java反射机制主要提供了以下功能,这些功能都基于java.lang.reflect包:
①在运行时判断任意一个对象所属的类
②在运行时构造任意一个类的对象
③在运行时判断任意一个类所具有的成员变量和方法
④在运行时调用任意一个对象的方法
⑤生成动态代理
class类
想要知道一个类的属性和方法,必须先获取到该类的字节码文件(.class)对象。class的本质是数据类型,class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,就会将其加载进内存。每加载一中class,JVM就为其创建一个class类型的实例,并关联起来:
注意:这里的class类型是一个名叫Classde类:
//final声明不允许继承
public final class Class{
//私有的构造方法
private Class(){}
}
由于JVM为每个加载class创建了对应的class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、成员变量等,因此,如果获取了某个class实例,我们就可以通过class实例获取到该实例对应的class信息。这种通过class实例获取class信息的方法称为反射(reflect)。
那么如何获取一个class的class实例呢?有三种:
方法一:直接通过一个class的静态变量class获取:
Class cls=String.class;
方法二:如果有一个实例变量,可以通过getClass()方法获取:
String s="Hello";
Class cls=s.getClass();
方法三:如果知道一个class的完整类名,可以通过静态方法class.forName()获取:
Class cls=Class.forName("java.lang.String");
但是由于class实例在JVM中是唯一的,所以,上述三个方法获取的class实例是同一个实例:
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:通过类名
Class stringClass1=String.class;
//方式二:通过Class类的forName()方法
Class stringClass2=Class.forName("java.lang.String");
//方式三:通过对象调用getClass()方法
Class stringClass3="".getClass();
System.out.println(stringClass1.hashCode());
System.out.println(stringClass2.hashCode());
System.out.println(stringClass3.hashCode());
}
}
输出结果:
如果获取到了一个class实例,我们就可以通过该class实例来创建对应类型的实例,可以用来代替:Order o1=new Order():
//用反射的方式创建对象
String className="com.ape.Order";
//获取类信息
Class classInfo=Class.forName(className);
//创建对象
Order o2=(Order)classInfo.newInstance();
虽然通过newInstance()可以创建实例,但是它的局限是:只能调用public修饰的无参构造方法。值得注意的是,采用硬编码的方式创建对象(Order o1=new Order();)是在编译期进行的;而用反射的方式创建对象是在运行期进行的。
Class常用方法
getPackage()
返回值为Package对象,用来获取该类的存放路径(包路径):
Class clz=Class.forName("java.util.HashMap");
System.out.println("package包名:"+clz.getPackage());
运行结果:package java.util, Java Platform API Specification, version 1.8
getName()
返回值为String对象,用来获取该类的名称:
①getName():获取完全限定名
②getSimpleName():简单类名
Class clz=Class.forName("java.util.HashMap");
System.out.println("完全限定名:"+clz.getName());
System.out.println("简单类名:"+clz.getSimpleName());
运行结果:
完全限定名:java.util.HashMap
简单类名:HashMap
getSuperclass()
返回值为class对象,获取该类继承的类:
Class clz=Class.forName("java.util.HashMap");
System.out.println("父类:"+clz.getSuperclass());
运行结果:父类:class java.util.AbstractMap
getInterface()
返回值为class型数组,获取该类实现的所有接口:
System.out.println("实现的接口:"+clz.getInterfaces());
结果:实现的接口:[Ljava.lang.Class;@15db9742
getMethods()
1、getMethods()
返回值为Methods数组,获取public修饰的成员方法:
Method[] mthodArray=clz.getMethods();
System.out.println("成员方法:");
for(Method m:mthodArray) {
System.out.println(m.getName());
}
运行结果:
成员方法:
remove
remove
get
put
values
clone
clear
isEmpty
replace
replace
replaceAll
size
entrySet
putAll
putIfAbsent
forEach
keySet
compute
computeIfAbsent
computeIfPresent
containsKey
containsValue
getOrDefault
merge
equals
toString
hashCode
wait
wait
wait
getClass
notify
notifyAll
2、getDeclaredMethods()
返回值为Methos对象,获取当前对象的所有方法:
Method[] mthodArray=clz.getDeclaredMethods();
System.out.println("成员方法:");
for(Method m:mthodArray) {
System.out.println(m.getName());
}
运行结果:
成员方法:
remove
remove
get
put
hash
values
clone
clear
isEmpty
replace
replace
replaceAll
size
entrySet
putAll
putIfAbsent
readObject
writeObject
forEach
keySet
compute
computeIfAbsent
computeIfPresent
containsKey
containsValue
getOrDefault
loadFactor
merge
capacity
afterNodeAccess
afterNodeInsertion
afterNodeRemoval
comparableClassFor
compareComparables
getNode
internalWriteEntries
newNode
newTreeNode
putMapEntries
putVal
reinitialize
removeNode
replacementNode
replacementTreeNode
resize
tableSizeFor
treeifyBin
getConstructors()
1、getConstructors()
返回值为Constructors型数组,获取public修饰的构造方法:
Constructor[] constructorArray1=clz.getConstructors();
2、getDeclaredConstructors()
返回值为Constructors对象,获取所有构造方法:
Constructor[] constructorArray2=clz.getDeclaredConstructors();
getFields()
1、getFields()
Field[] fileArray=clz.getFields();
System.out.println("成员变量(字段):");
for(Field f:fileArray) {
System.out.println(f.getName());
}
1、getDeclaredFields()
Field[] fileArray=clz.getDeclaredFields();
System.out.println("成员变量(字段):");
for(Field f:fileArray) {
System.out.println(f.getName());
}
运行结果:
成员变量(字段):
serialVersionUID
DEFAULT_INITIAL_CAPACITY
MAXIMUM_CAPACITY
DEFAULT_LOAD_FACTOR
TREEIFY_THRESHOLD
UNTREEIFY_THRESHOLD
MIN_TREEIFY_CAPACITY
table
entrySet
size
modCount
threshold
loadFactor
动态加载机制
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次用到class时才加载
调用构造方法
为了可以调用任意的构造方法,Java的反射API提供了Constructor对象,它包含了一个构造方法的所有信息,可以创建一个实例。Class类为我们提供了以下几个方式来获取构造方法:
①Constructor[ ] getConstructors():获取所有public修饰的构造器
②Constructor[ ] getDeclaredConstructors():获取所有构造器
③Constructor getConstructor(name):获取Public修饰的指定构造器
④Constructor getDeclaredConstructor(name):获取任意指定构造器
无参构造方法
//首先准备一个Document类:
package date_0909;
public class Document {
private int size;
public String fileName;
//静态方法
public static void dosth() {
System.out.println("执行静态方法");
}
//构造方法
public Document() {
super();
}
public Document(int size) {
super();
this.size = size;
}
public Document(int size, String fileName) {
super();
this.size = size;
this.fileName = fileName;
}
public Document(String fileName) {
super();
this.fileName = fileName;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
@Override
public String toString() {
return "Document [size=" + size + ", fileName=" + fileName + "]";
}
}
//获取无参构造方法
Constructor constructor1=clz.getDeclaredConstructor();
//执行构造器(构造方法),创建对象
Object obj1=constructor1.newInstance();
System.out.println(obj1);
运行结果:Document [size=0, fileName=null]
有参构造方法
案例1:
//获取有参构造方法
Constructor constructor2=clz.getDeclaredConstructor(int.class);
//执行有参构造方法
Object obj2=constructor2.newInstance(123);
System.out.println(obj2);
运行结果:Document [size=123, fileName=null]
案例2:
Constructor constructor3=clz.getDeclaredConstructor(int.class,String.class);
Object obj3=constructor3.newInstance(233,"陇西");
System.out.println(obj3);
运行结果:Document [size=233, fileName=陇西]
getDeclaredConstructors()和getConstructors()
我们将Document类中的两个构造方法改为私有:
//构造方法
public Document() {
super();
}
private Document(int size) {
super();
this.size = size;
}
public Document(int size, String fileName) {
super();
this.size = size;
this.fileName = fileName;
}
private Document(String fileName) {
super();
this.fileName = fileName;
}
当我们使用getConstructors()来获取构造方法时,只能获取到public 修饰的构造方法,那么当我们想通过getConstructors()获取private修饰的构造方法时,会产生报错:
Class clz=Class.forName("date_0909.Document");
//获取一组构造器
Constructor[] constructorArray1=clz.getConstructors();
//获取指定构造器
Constructor constructor2=clz.getConstructor(String.class);
System.out.println(constructor2);
但是 getDeclaredConstructor()可以获取到任意构造方法,并且可以通过调用构造器来创建对象:
//获取指定构造器
Constructor constructor=clz.getDeclaredConstructor(String.class);
System.out.println(constructor);
//调用私有构造方法,必须设置他的访问权限为true
constructor.setAccessible(true);
//调用构造器,创建对象
Object obj=constructor.newInstance("小团子");
System.out.println(obj);
运行结果:
private date_0909.Document(java.lang.String)
Document [size=0, fileName=小团子]
访问成员变量
对于任意一个实例,只要我们获取了它的Class,就可以获取它的一切信息。比如通过一个Class实例获取字段信息,class类提供了以下几个方法来获取成员变量:
①Field getField(name):根据字段名获取当前类中某个public修饰的成员变量。
②Field getDeclaredField(name):根据字段名获取当前类中定义的某个成员变量。
③Field[ ] getFields():获取所有public修饰的成员变量。
④Field[ ] getDeclaredFields():获取所有成员变量。
我们通过反射,访问并使用成员变量,可用来代替传统的硬编码的形式,用来操作成员变量:
Document doc1=new Document();
doc1.fileName("wyj");
doc1.size(165);
通过反射的方式:
Class clz=Class.forName("date_0909.Document");
//创建对象
Object obj=clz.newInstance():
//获取指定名称的成员变量
Field fileName2=clz.getDeclaredField("fileName");
Field size2=clz.getDeclaredField("size");
//访问私有的成员变量时需要设置访问权
size2.setAccessible(true);
//使用成员变量,存入数据
fileName2.set(obj,"wyj");
size2.set(obj, 165);
System.out.println(obj);
运行结果:Document [size=165, fileName=wyj]
那么,如果我们可以使用反射来获取private字段的值,那我们类的封装有什么意义?正常情况下,我们都会使用doc.fielNAme来访问Document的fileName字段,编译器会根据public、private、protected决定是否允许访问该字段,这就是封装的目的。而反射是一种非常规的用法,它会破坏对对象的封装。它的代码复杂,它更多的是给工具或底层架构来使用,目的是在不知道目标实例的任何信息情况下,获取特定字段。
调用方法
我们可以通过class实例获取所有方法(Method类型的对象),Class提供了以下几个方法来获取Method:
①Method getMethod(name):根据字段名获取当前类中某个public修饰的成员方法。
②Method getDeclaredMethod(name):根据字段名获取当前类中定义的某个成员方法。
③Method [ ] getMethods():获取所有public修饰的成员方法。
④Method [ ] getDeclaredMethod s():获取所有成员方法
我们通过反射,访问并使用成员方法,可用来代替传统的硬编码的形式,用来操作成员方法:
Document doc1=new Document();
doc1.setFileName("海底两万里");
doc1.setSize(666);
通过反射的方法:
Class clz=Class.forName("date_0909.Document");
//创建对象
Object obj1=clz.newInstance();
//获取指定方法和参数
Method setNameMethod=clz.getMethod("setFileName",String.class);
Method setSizeMethod=clz.getMethod("setSize", int.class);
//执行方法
setNameMethod.invoke(obj1, "海底两万里");
setSizeMethod.invoke(obj1, 10034);
System.out.println(obj1);
运行结果:Document [size=10034, fileName=海底两万里]
调用静态方法
//案例一:
//硬编码的方式
Document.dosth();
//反射的方式
Class clz=Class.forName("date_0909.Document");
Method dosthMethod=clz.getMethod("dosth");
dosthMethod.invoke(null);
//案例二:
//硬编码的方式
String ret1=String.format("HashMap的默认初始容量是%d,加载因子是%f", 16,.75f);
//反射的方式
Class clz=String.class;
Method formatMethod=clz.getMethod("format", String.class,Object[].class);
String ret2=formatMethod.invoke(null,"HashMap的默认初始容量是%d,加载因子是%f", new Object[] {16,0.75f}).toString();
System.out.println(ret2);
运行结果:HashMap的默认初始容量是16,加载因子是0.750000