导读:
本篇是JAVA基础系列的第19篇,今天我们梳理java反射机制的相关知识点。Java反射机制在框架设计中极为广泛,需要深入理解。
1.java的反射机制
Java反射机制是Java语言的一个重要特性。在学习Java反射机制前,大家应该先了解两个概念,编译期和运行期。
-
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在Java中也就是把Java代码编成 class文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
-
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect
包。
-
在运行时判断任意一个对象所属的类。
-
在运行时构造任意一个类的对象。
-
在运行时判断任意一个类所具有的成员变量和方法。
-
在运行时调用任意一个对象的方法。
-
生成动态代理。
2.Java 反射机制的优缺点
优点:
-
能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
-
与Java动态编译相结合,可以实现无比强大的功能。
-
对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
-
反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
-
反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
Java反射机制在一般的Java应用开发中很少使用,即便是 Java EE 阶段也很少使用。
3.Java反射机制API
实现Java反射机制的类都位于java.lang.reflect 包中,java.lang.Class类是Java反射机制API中的核心类。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。
java.lang.reflect 包提供了反射中用到类,主要的类说明如下:
-
Constructor 类:提供类的构造方法信息。
-
Field 类:提供类或接口中成员变量信息。
-
Method 类:提供类或接口成员方法信息。
-
Array 类:提供了动态创建和访问 Java 数组的方法。
-
Modifier 类:提供类和成员访问修饰符信息。
4.Class类对象的获取
在类加载的时候,jvm会创建一个class对象
class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
-
根据类名:类名.class
-
根据对象:对象.getClass()
-
根据全限定类名:Class.forName(全限定类名)
@Test public void test() throws Exception { // 获取Class对象的三种方式 logger.info("根据类名:" + User.class); logger.info("根据对象:" + new User().getClass()); logger.info("根据全限定类名:" + Class.forName("com.tian.entity.User")); }
利用Class类的对象可访问的常用信息
类型 | 访问方法 | 返回值类型 | 说明 |
---|---|---|---|
包路径 | getPackage() | Package 对象 | 获取该类的存放路径 |
类名称 | getName() | String 对象 | 获取该类的名称 |
继承类 | getSuperclass() | Class 对象 | 获取该类继承的类 |
实现接口 | getlnterfaces() | Class 型数组 | 获取该类实现的所有接口 |
构造方法 | getConstructors() | Constructor 型数组 | 获取所有权限为 public 的构造方法 |
构造方法 | getDeclaredContruectors() | Constructor 对象 | 获取当前对象的所有构造方法 |
方法 | getMethods() | Methods 型数组 | 获取所有权限为 public 的方法 |
方法 | getDeclaredMethods() | Methods 对象 | 获取当前对象的所有方法 |
成员变量 | getFields() | Field 型数组 | 获取所有权限为 public 的成员变量 |
成员变量 | getDeclareFileds() | Field 对象 | 获取当前对象的所有成员变量 |
内部类 | getClasses() | Class 型数组 | 获取所有权限为 public 的内部类 |
内部类 | getDeclaredClasses() | Class 型数组 | 获取所有内部类 |
内部类的声明类 | getDeclaringClass() | Class 对象 | 如果该类为内部类,则返回它的成员类,否则返回 null |
5.Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法
获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
static Class<?> | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
Constructor | getConstructor(Class<?>... parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 |
Constructor | getDeclaredConstructor(Class<?>... parameterTypes) | 返回指定参数类型、所有声明的(包括private)构造函数对象 |
Constructor<?>[] | getDeclaredConstructor() | 返回所有声明的(包括private)构造函数对象 |
T | newInstance() | 调用无参构造器创建此 Class 对象所表示的类的一个新实例。 |
public class ConstructionTest implements Serializable { public static void main(String[] args) throws Exception { Class<?> clazz = null; //获取Class对象的引用 clazz = Class.forName("com.tian.entity.User"); //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常 User user = (User) clazz.newInstance(); user.setAge(20); user.setName("Jack"); System.out.println(user); System.out.println("--------------------------------------------"); //获取带String参数的public构造函数 Constructor cs1 =clazz.getConstructor(String.class); //创建User User user1= (User) cs1.newInstance("zhangsan"); user1.setAge(22); System.out.println("user1:"+user1.toString()); System.out.println("--------------------------------------------"); //取得指定带int和String参数构造函数,该方法是私有构造private Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class); //由于是private必须设置可访问 cs2.setAccessible(true); //创建user对象 User user2= (User) cs2.newInstance(25,"lisi"); System.out.println("user2:"+user2.toString()); System.out.println("--------------------------------------------"); //获取所有构造包含private Constructor<?> cons[] = clazz.getDeclaredConstructors(); // 查看每个构造方法需要的参数 for (int i = 0; i < cons.length; i++) { //获取构造函数参数类型 Class<?> clazzs[] = cons[i].getParameterTypes(); System.out.println("构造函数["+i+"]:"+cons[i].toString() ); System.out.print("参数类型["+i+"]:("); for (int j = 0; j < clazzs.length; j++) { if (j == clazzs.length - 1) System.out.print(clazzs[j].getName()); else System.out.print(clazzs[j].getName() + ","); } System.out.println(")"); } } } class User { private int age; private String name; public User() { super(); } public User(String name) { super(); this.name = name; } private User(int age, String name) { super(); this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
Constructor类的常用方法
方法名称 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型 |
getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型 |
newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示 采用默认无参的构造方法 |
setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对 象 |
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数 |
6.Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Field | getDeclaredField(String name) | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
Field | getField(String name) | 获取指定name名称、具有public修饰的字段,包含继承字段 |
Field[] | getField() | 获取修饰符为public的字段,包含继承字段 |
public class FieldTest { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class<?> clazz = Class.forName("com.tian.entity.Student"); //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段, // 否则抛NoSuchFieldException Field field = clazz.getField("age"); System.out.println("field:"+field); //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取 Field fields[] = clazz.getFields(); for (Field f:fields) { System.out.println("f:"+f.getDeclaringClass()); } System.out.println("================getDeclaredFields===================="); //获取当前类所字段(包含private字段),注意不包含父类的字段 Field fields2[] = clazz.getDeclaredFields(); for (Field f:fields2) { System.out.println("f2:"+f.getDeclaringClass()); } //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段 Field field2 = clazz.getDeclaredField("desc"); System.out.println("field2:"+field2); } } class Person{ public int age; public String name; //省略set和get方法 } class Student extends Person{ public String desc; private int score; //省略set和get方法 }
Field类的常用方法
方法名称 | 说明 |
---|---|
getName() | 获得该成员变量的名称 |
getType() | 获取表示该成员变量的 Class 对象 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 类型 |
set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
getlnt(0bject obj) | 获得指定对象 obj 中成员类型为 int 的成员变量的值 |
setlnt(0bject obj, int i) | 将指定对象 obj 中成员变量的值设置为 i |
setFloat(Object obj, float f) | 将指定对象 obj 中成员变量的值设置为 f |
getBoolean(Object obj) | 获得指定对象 obj 中成员类型为 boolean 的成员变量的值 |
setBoolean(Object obj, boolean b) | 将指定对象 obj 中成员变量的值设置为 b |
getFloat(Object obj) | 获得指定对象 obj 中成员类型为 float 的成员变量的值 |
setAccessible(boolean flag) | 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量 |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
7.Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面是Class类获取Method对象相关的方法:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method | getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Method[] | getDeclaredMethod() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
Method | getMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
Method[] | getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
public class MethodTest { public static void main(String[] args) throws Exception { Class clazz = Class.forName("com.tian.entity.Student"); //根据参数获取public的Method,包含继承自父类的方法 Method method = clazz.getMethod("demo",int.class,String.class); System.out.println("method:"+method); //获取所有public的方法: Method[] methods =clazz.getMethods(); for (Method m:methods){ System.out.println("m:"+m); } System.out.println("========================================="); //获取当前类的方法包含private,该方法无法获取继承自父类的method Method method1 = clazz.getDeclaredMethod("demoStudent"); System.out.println("method1:"+method1); //获取当前类的所有方法包含private,该方法无法获取继承自父类的method Method[] methods1=clazz.getDeclaredMethods(); for (Method m:methods1){ System.out.println("m1:"+m); } } } class Shape { public void demo(){ System.out.println("demo"); } public void demo(int count , String name){ System.out.println("demo "+ name +",count="+count); } } class Student extends Shape{ private void demoStudent(){ System.out.println("demoStudent"); } public int getAllCount(){ return 100; } }
Method类的常用方法
静态方法名称 | 说明 |
---|---|
getName() | 获取该方法的名称 |
getParameterType() | 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型 |
getReturnType() | 以 Class 对象的形式获得该方法的返回值类型 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
invoke(Object obj,Object...args) | 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型 |
isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
8.Java中创建对象的5种方式
Java中有5种创建对象的方式
方式 | 是否调用构造函数 |
---|---|
使用new关键字 | } → 调用了构造函数 |
使用Class类的newInstance方法 | } → 调用了构造函数 |
使用Constructor类的newInstance方法 | } → 调用了构造函数 |
使用clone方法 | } → 没有调用构造函数 |
使用反序列化 | } → 没有调用构造函数 |