我们要了解Java中的反射就要了解基础类Class,一切反射的基石,它也是一个类,类名就是Class,不同于我们自定义类使用的小写class。Java中所有的类都属于同一种事物那就是Class类,就像所有的人一样都属人类,当然一说人那么它就必须满足人类所固有的一些特性,Java中的类也是一样,而用来描述所有Java类的特性的就是Class,所以通过一个Class就可以获取到Java类的所有信息。下面是它的定义,它里面提供了很多方法来获取类的信息,大家可以打开源代码自己看一下。
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
/*
* Constructor. Only the Java Virtual Machine creates Class
* objects.
*/
private Class() {}
}
值得注意一点就是这个类的构造方法是私有的,也就是不能new Class();注释说了这个类的对象只能由Java虚拟机进行创建,那么怎么获取到这个类的对象呢???接着往下看。。。
其实我们平常写的.java文件都是经过了Java 编译器生成.class文件(字节码文件),再由JVM将.class加载到内存,然后我们才可以运行。这里其实每个.class文件就对应一个Class的实例对象。
举个例子:
我们创建一个Person.java文件,把它编译成Person.class文件以后,就可以使用Class classPerson = Person.class 来获取它的字节码文件。
或者我们创建一个Person对象Person person = new Person();然后使用Class classPerson = person.getClass()也可以获取Person的字节码对象。
另外我们还有一种获取字节码的方式就是通过Class类提供一个方法Class classPerson = Class.forName("com.lp.ref.Person");在实际应用中我们使用这种方式也最多(其他两种也用的不少),使用这种方式有两个步骤,当代码运行到这个一行的时候类加载器会检查JVM内存中是否存在Person的字节码文件如果存在就会返回Person的字节码类,如果不存在就会检查硬盘中是否存在是否存在com.lp.ref.Person的Java文件,如果存在则类加载器就会把它加载到JVM内存中,然后返回Person的字节码文件,如果还不存在就会抛出ClassNotFoundException异常
以上就是反射获取类字节码的三种方式,下面我们看个小实验
public class Person {
}
public class RefClass {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Person();
Class personClass = person.getClass();
Class personClass1 = Person.class;
Class personClass2 = Class.forName("com.lp.ref.Person");
System.out.println(personClass == personClass1);
System.out.println(personClass == personClass2);
}
}
我们定义了一个Person类,什么内容都没有,通过三种方式来获取它的字节码,我们打印出来结果为true true,这就说明了不管用哪种方式我们都是获取的同一份字节码对象
接下来我们再看,Class中提供了一个isPrimitive()的方法,看它的注释我们了解到,它是用来判断一个字节码类型是否属于原始类型的Class,在JVM中为我们提供了九种原始类型的Class对象,包括Java的八种基本类型和Void类型,这些JVM提供对象都可以通过他们的包装类.TYPE来获取
/**
* Determines if the specified {@code Class} object represents a
* primitive type.
*
* <p> There are nine predefined {@code Class} objects to represent
* the eight primitive types and void. These are created by the Java
* Virtual Machine, and have the same names as the primitive types that
* they represent, namely {@code boolean}, {@code byte},
* {@code char}, {@code short}, {@code int},
* {@code long}, {@code float}, and {@code double}.
*
* <p> These objects may only be accessed via the following public static
* final variables, and are the only {@code Class} objects for which
* this method returns {@code true}.
*
* @return true if and only if this class represents a primitive type
*
* @see java.lang.Boolean#TYPE
* @see java.lang.Character#TYPE
* @see java.lang.Byte#TYPE
* @see java.lang.Short#TYPE
* @see java.lang.Integer#TYPE
* @see java.lang.Long#TYPE
* @see java.lang.Float#TYPE
* @see java.lang.Double#TYPE
* @see java.lang.Void#TYPE
* @since JDK1.1
*/
public native boolean isPrimitive();
介绍完反射的基本类Class之后,我们再来看看什么是反射?偶尔看到一句经典——反射就是把Java类中的各种成份映射成相应的Java类,类的各种成份包括 成员变量,方法,构造方法,包等等,他们也都可以使用相应的Java类来表示,分别对应Filed,Method,Constructor,Package。表示Java类的Class类中提供了一系列的方法来获取这些成员属性。
Class personClass = Person.class;
Method[] methods = personClass.getMethods();
Annotation[] annotations = personClass.getAnnotations();
Constructor[] constructors = personClass.getConstructors();
Field[] fields = personClass.getFields();
Package personPackage = personClass.getPackage();
上面的代码段就是通过Class提供的方法来获取Person中相应的成份,获取需要的类成份以及使用他们就是反射的要点,也是反射的价值
我们把新增一个Animal类并把 Person类加上几个成员,一下代码希望自己动手在编译器里面运行一下,由更深的体会
public class Animal {
public String head;
private String body;
public void run(){
System.out.println("animal run");
}
}
public class Person extends Animal{
private int age;
public String name;
public static void staticMethod(String flag){
System.out.println("我是静态方法:"+flag);
}
public Person() {
}
public Person(int age) {
this.age = age;
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
private void privateMethod(){
System.out.println("我是私有方法");
}
public void speak(){
System.out.println("你好,我是"+name+" 我的年龄是"+age);
}
public int getAge(){
return this.age;
}
public void setAge(int age){
this.age = age;
}
}
构造函数的反射,及创建对象
public class RefClass {
public static void main(String[] args) throws Exception {
//反射获取获取Person类的字节码对象
Class personClass = Class.forName("com.lp.ref.Person");
//通过字节码对象获取Person类的所有构造函数
Constructor[] constructors = personClass.getConstructors();
//打印出Person有几个构造函数,打印结果
System.out.println(constructors.length);
//通过字节码对象,获取Person的无参构造函数
Constructor constructor = personClass.getConstructor(null);
//通过获取的无参构造函数构造器来创建Person对象
Person instancePerson = (Person) constructor.newInstance();
//测试 打印出 你好,我是null 我的年龄是0
instancePerson.speak();
//通过字节码对象,获取Person的构造函数,其中构造函数的参数有一个并且是int类型
Constructor constructor1 = personClass.getConstructor(int.class);
//传递一个参数给构造器来创建Person对象
Person instancePerson1 = (Person) constructor1.newInstance(8);
//测试 打印出 你好,我是null 我的年龄是8
instancePerson1.speak();
//通过字节码对象,获取Person的构造函数,其中构造函数的参数有两个,且第一个参数是int类型,第二个参数是String类型
Constructor constructor2 = personClass.getConstructor(int.class,String.class);
//传递一个参数给构造器来创建Person对象
Person instancePerson2 = (Person) constructor2.newInstance(8,"lisi");
//测试 打印出 你好,我是lisi 我的年龄是8
instancePerson2.speak();
}
}
成员变量的反射及使用
public class RefClass {
public static void main(String[] args) throws Exception {
//通过new 的方式创建一个对象,传入两个参数,第一个参数的修饰符在Person定义中是私有的,第二个是公有的
Person person = new Person(12, "wang");
//反射获取Person类的字节码对象
Class personClass = Class.forName("com.lp.ref.Person");
//获取该对象中的所有public成员变量,包括继承的父类的public成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
//打印出成员变量的名称,并不打印变量的值 打印结果 name head 并没有获取age和父类body 因为age body 是private的
//field.getName() 获取字段的名称
System.out.println(field.getName());
}
//通过成员属性的名字获取成员属性
//这一行会报错:Exception in thread "main" java.lang.NoSuchFieldException: age
//Field age = personClass.getField("age");
Field name = personClass.getField("name");
//给属性绑定对象,获取绑定的对象相对应的属性值 打印结果 wang
System.out.println((String)name.get(person));
//获取所有声明的成员变量,包括private,不包括父类属性
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
//打印结果 age name
System.out.println("declaredField:"+declaredField.getName());
}
//不会报错
Field age = personClass.getDeclaredField("age");
// Exception in thread "main" java.lang.IllegalAccessException: Class com.lp.ref.RefClass
// can not access a member of class com.lp.ref.Person with modifiers "private"
// 如果不加下面这句会报异常,不能访问 private 的成员变量,加上下面这句 暴力反射 就可以访问private
age.setAccessible(true);
//打印结果 12
System.out.println((int)age.get(person));
for (Field declaredField : declaredFields) {
//获取字段的类型,返回的是类型的字节码对象
if (declaredField.getType() == String.class){
//改变某个对象的某个属性的值
declaredField.set(person,"zhang");
}
//打印结果 zhang
System.out.println((String)name.get(person));
}
}
}
反射方法及调用
public class RefClass {
public static void main(String[] args) throws Exception {
//通过new 的方式创建一个对象,传入两个参数,第一个参数的修饰符在Person定义中是私有的,第二个是公有的
Person person = new Person(12, "wang");
//反射获取获取Person类的字节码对象
Class personClass = Class.forName("com.lp.ref.Person");
//获取所有的public方法,包括继承的父类的public方法,不包括构造方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
//method.getModifiers() 获取修饰符类型 修饰符类型在java.lang.reflect.Modifier 定义 如下,是十六进制
// public static final int PUBLIC = 0x00000001; 转为十进制是 1
// public static final int PRIVATE = 0x00000002; 转为十进制是 2
// public static final int PROTECTED = 0x00000004; 转为十进制是 4
// public static final int STATIC = 0x00000008; 转为十进制是 8
// public static final int FINAL = 0x00000010; 转为十进制是 16
// public static final int SYNCHRONIZED = 0x00000020; 转为十进制是 32
// public static final int VOLATILE = 0x00000040; 转为十进制是 64
// public static final int TRANSIENT = 0x00000080; 转为十进制是 128
// public static final int NATIVE = 0x00000100; 转为十进制是 256
// public static final int INTERFACE = 0x00000200; 转为十进制是 512
// public static final int ABSTRACT = 0x00000400; 转为十进制是 1024
// public static final int STRICT = 0x00000800; 转为十进制是 2048
/**
* getModifiers() 方法返回的值会把所有的修饰符的值都加起来,举个例子
* public final native Class<?> getClass();
* 这个方法前面被 public final native 三种修饰,那么返回的值就是 1 + 16 + 256 = 273
*/
// method.getReturnType() 方法的返回值类型
// method.getName() 获取方法名称
/**
* 打印结果 这里的Person类虽然没有显式使用extends 继承Object 但是JVM 编译的时候会让它继承Object
* 方法的修饰符类型9 返回值类型:void 方法名称:staticMethod
* 方法的修饰符类型1 返回值类型:void 方法名称:setAge
* 方法的修饰符类型1 返回值类型:void 方法名称:speak
* 方法的修饰符类型1 返回值类型:int 方法名称:getAge
* 方法的修饰符类型1 返回值类型:void 方法名称:run
* 方法的修饰符类型17 返回值类型:void 方法名称:wait
* 方法的修饰符类型17 返回值类型:void 方法名称:wait
* 方法的修饰符类型273 返回值类型:void 方法名称:wait
* 方法的修饰符类型1 返回值类型:boolean 方法名称:equals
* 方法的修饰符类型1 返回值类型:class java.lang.String 方法名称:toString
* 方法的修饰符类型257 返回值类型:int 方法名称:hashCode
* 方法的修饰符类型273 返回值类型:class java.lang.Class 方法名称:getClass
* 方法的修饰符类型273 返回值类型:void 方法名称:notify
* 方法的修饰符类型273 返回值类型:void 方法名称:notifyAll
*/
System.out.println("方法的修饰符类型"+method.getModifiers()+" 返回值类型:"+method.getReturnType()+" 方法名称:"+method.getName());
}
//获取Person所有的方法,包括private 但是不包括父类的任何方法
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method method : declaredMethods) {
/** 打印结果
* declared 方法的修饰符类型9 返回值类型:void 方法名称:staticMethod
* declared 方法的修饰符类型2 返回值类型:void 方法名称:privateMethod
* declared 方法的修饰符类型1 返回值类型:void 方法名称:setAge
* declared 方法的修饰符类型1 返回值类型:void 方法名称:speak
* declared 方法的修饰符类型1 返回值类型:int 方法名称:getAge
*/
System.out.println("declared 方法的修饰符类型"+method.getModifiers()+" 返回值类型:"+method.getReturnType()+" 方法名称:"+method.getName());
}
//通过方法名获取public方法对象也可以获取父类中的public方法对象,获取Person类中的speak方法,没有参数
Method speakMethod = personClass.getMethod("speak");
//绑定person对象执行speak方法,执行结果 你好,我是wang 我的年龄是12
speakMethod.invoke(person);
//获取父类public方法对象
Method getClassMethod = personClass.getMethod("getClass");
//绑定person对象执行speak方法,执行结果 你好,我是wang 我的年龄是12
Object invoke = getClassMethod.invoke(person);
//执行结果 class com.lp.ref.Person
System.out.println(invoke);
//获取声明的私有方法
Method privateMethod = personClass.getDeclaredMethod("privateMethod");
/**如果不加下面代码会有如下异常,加上之后,就是我们所说的暴力反射
* Exception in thread "main" java.lang.IllegalAccessException: Class com.lp.ref.RefClass can not access a member of class com.lp.ref.Person with modifiers "private"
* 打印结果 我是私有方法
*/
privateMethod.setAccessible(true);
privateMethod.invoke(person);
//获取带有参数的方法
Method setAgeMethod = personClass.getDeclaredMethod("setAge",int.class);
setAgeMethod.invoke(person,20);
speakMethod.invoke(person);//打印结果 你好,我是wang 我的年龄是20
//获取对象中的静态方法
Method staticMethod = personClass.getDeclaredMethod("staticMethod",String.class);
//静态方法的调用可以不通过对象 打印 我是静态方法:staticMethod
staticMethod.invoke(null,"staticMethod");
//调用程序入口函数main
Class mainClass = Class.forName("com.lp.ref.MainClass");
Method mainMethod = mainClass.getMethod("main", String[].class);
//这个地方有个特殊,需要大家记住,传参的时候需要使用new Object[] 把String[] 包裹一下,这是因为javac会兼任1.5以前的语法,
// 在jdk1.5以前的语法中,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,会把数组打散成为若干个单独的参数
//就会报错 java.lang.IllegalArgumentException: wrong number of arguments
/** 打印结果
* MainClass:aa
* MainClass:bb
* MainClass:cc
*/
// mainMethod.invoke(null,new Object[]{new String[]{"aa","bb","cc"}});
//或者使用下面这种方式 明确告诉编译器,这个数组是一个参数
mainMethod.invoke(null, (Object)new String[]{"aa","bb","cc"});
}
}
class MainClass{
public static void main(String[] args) {
for (String arg : args) {
System.out.println("MainClass:"+arg);
}
}
}
题外话
在Java源码中Class类的注释中有这么一句话
Every array also belongs to a class that is reflected as a {@code Class} object that is shared by all arrays with the same element type and number of dimensions 具有相同元素类型并且维度相同的数组属于同一种Class
/**
* Class 的getName 方法上面有这样的注释 题外话,如果有反编译过 jar 包的话,会看到编译过的代码种会经常出现这种编码符号
* If this class object represents a class of arrays, then the internal form of the name
* consists of the name of the element type preceded by one or more '{@code [}' characters
* representing the depth of the array nesting. The encoding of element type names is as follows:
*
* Element Type Encoding
* boolean Z
* byte B
* char C
* class or interface L<i>classname</i>;
* double D
* float F
* int I
* long J
* short S
*/
int[] intArr = new int[4];
int[] intArr2 = new int[5];
int[][] intArr3 = new int[5][6];
String[] strArr = new String[7];
//打印结果 intArr.getClass()==intArr2.getClass() true
System.out.println("intArr.getClass()==intArr2.getClass() "+(intArr.getClass() == intArr2.getClass()));
System.out.println(intArr.getClass().getName());//打印结果 [I 根据打印结果可以很明显的看出是否属于同一种Class
System.out.println(intArr2.getClass().getName());//打印结果 [I
System.out.println(intArr3.getClass().getName());//打印结果 [[I
System.out.println(strArr.getClass().getName());//打印结果 [Ljava.lang.String;
反射介绍到此结束