反射机制简介
动态语言是在运行时可以改变其结构的语言,例如新的函数、对象、甚至其代码可以被引进,已有函数可以被删除或是其它结构上的变化。通俗来说就是在运行时代码可以根据某些条件改变自身结构。如:C#、JavaScript、PHP、Python,而静态语言是和动态语言相对的,运行时不可改变结构,如:Java、C、C++注意:Java不是动态语言,但是Java可以称之为“准动态语言”。即Java是有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。
Java Reflection(Java反射机制)是Java被视为“准动态语言”的关键,反射机制允许程序在执行时期借助Reflection API获取任何类的内部信息,并能直接操作任意对象的内部属性和方法。可以通过多种方法获取任一对象的Class类型对象(一个类只有一个Class对象),这个Class类型对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象称之为反射。
Java反射机制主要提供以下功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时可以获取、调用以及修改任意一个类具有的成员变量和方法
- 在运行时可以获取泛型信息
- 在运行时处理注解
- ........
Class类
在Object类中定义了public final Class getClass()方法,此方法将被所有子类继承。该方法返回的类型是一个Class类,Class类是java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。对象对照镜子后可以得到很多信息:某个类的属性、方法和构造器、某个类实现了那些接口、对于每个类而言,JRE都为其保留一个不变的Class类型的对象,一个Class对象包含了特定的某个结果结构的有关信息。
- Class本身也是一个类,Class对象只能由系统创建
- 一个加载的类在JVM中只会有一个Class实例,即在内存中存在唯一的哈希值
- 一个Class对象对用的是一个加载到JVM的一个.class文件
- 通过Class可以完整的获取一个类中的所有被加载的结构
- Class类是Java反射的根源,针对任何想要动态加载、运行的类,唯有先获得相应的Class对象
java反射机制提供三种方法获取一个类的Class:
(1)如已经知道具体的类,通过类的class属性可获取,该方法最安全可靠,程序性能更高。语法:Class c=类名.class;
(2)如已经知道某个类的实例对象,调用该实例对象的getClass方法获取Class对象。语法:Class c=实例对象.getClass();
(3)如已经知道一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,该方法可能会抛出ClassNotFoundException异常,需要处理异常。语法:Class c=Class.forName("全类名");
如:
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:通过类名.class获取
Class c1=Person.class;
//方式三:通过forName获取,需要抛出异常或try-catch
Class c2=Class.forName("MyPackage_2.Person");
//方式二:通过对象的getClass方法获取
Person person=new Person();
Class c3=person.getClass();
}
}
获取运行时类的完整结构
1.获取类的名字
使用Class类的getName方法可以获取类的全限定名(包名+类名),使用getSimpleName方法则可以获取简单类名(不包括包名)
package MyPackage_2;
public class Test03 {
public static void main(String[] args) {
Person person=new Person();
Class c1=person.getClass();
//获取完整的类名
String name=c1.getName();
//获取简化的类名
String simpleName= c1.getSimpleName();
System.out.println(c1);
System.out.println(name);
System.out.println(simpleName);
}
}
运行结果:
2.获取类的属性
- Field[] getFields():获取类中所有被public修饰的所有变量
- Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
- Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
- Field getDeclaredField(String name):根据变量名获取类中的某个变量,无法获取继承下来的变量
3.获取类的方法
- Method[] getMethods():获取类中被public修饰的所有方法
- Method getMethod(String name, Class...<?> paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰
- Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法
- Method getDeclaredMethod(String name, Class...<?> paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法
如:
Class[] classes = new Class[]{String.class};
Method method = User.class.getMethod("setName", classes);
4.获取类的构造器
- Constuctor[] getConstructors():获取类中所有被public修饰的构造器
- Constructor getConstructor(Class...<?> paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
- Constructor[] getDeclaredConstructors():获取类中所有构造器
- Constructor getDeclaredConstructor(class...<?> paramTypes):根据参数类型获取对应的构造器
如:
User user = new User(1, "阿离", "123654");
Constructor<User> constructor=User.class.getConstructor(Integer.class,String.class,String.class);
5.注意事项
有Declared修饰的方法:可以获取该类内部包含的所有变量、方法和构造器,但无法获取继承来的信息
无Declared修饰的方法:可以获取该类中public修饰的变量、方法和构造器,可获取继承下来的信息
如:
public class Father {
public String f_name;
private String f_id;
}
public class Son extends Father{
private String s_name;
private String s_id;
}
@Test
public void test() {
Son son=new Son();
Class<? extends Son> clazz = son.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
}
@Test
public void test() {
Son son=new Son();
Class<? extends Son> clazz = son.getClass();
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
}
可以看到getDeclaredFields获取了s_name和s_id,但是继承下来的属性没有一个获取到了;而getFields则没有获取到全部为private修饰的子类属性,反而是获取了继承下来的f_name(public修饰)属性。
getFields可以获取本类和父类的public属性,但不能获取private和protected的属性;getDeclaredFields可以获取本类所有的属性(包括private和protected),但无法获取父类的任何属性。因此无论使用哪种方法或者两者一起用也都无法获取包括继承下来的全部属性(public、protected、private)。因此我们只能子类使用getDeclaredFields,然后再父类使用getDeclaredFields,这样才能获取类的全部属性(包括继承的属性),而我们可以调用子类的Class类getSuperclass方法获取父类的Class,直到超类Object,如:
@SpringBootTest
class ApplicationTests {
@Test
public void test() {
List<Field> fields = getFields(new Son());
for (Field field : fields) {
System.out.println(field);
}
}
public static List<Field> getFields(Object obj) {
if (obj == null) {
return null;
}
Class<?> clazz = obj.getClass();
List<Field> fields = new ArrayList<>();
for (Class<?> c = clazz; c != Object.class; c = c.getSuperclass()) {
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}
}
调用运行时类的完整结构
1.通过反射构造对象
有了Class对象就可以创建这个类的对象了,反射机制中有两种构造对象的方法:Class类.newInstance()和构造器.newInstance(参数)。前者调用的是无参构造器,后者调用的是有参构造器,参数是有参构造器需要的参数。
@Test
public void test() {
User user = null;
try {
user = User.class.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(user);
}
@Test
public void test() {
try {
Constructor<User> constructor=User.class.getConstructor(Integer.class,String.class,String.class);
User user = constructor.newInstance(6, "阿离", "123456");
System.out.println(user);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
如果使用反射来创建数组,那么参数依次需要数组类型和数组长度,Class类则是Array类:
@Test
public void test() throws ClassNotFoundException {
Class<?> clazz = Class.forName("java.lang.String");
Object array = Array.newInstance(clazz, 25);
//往数组里添加内容
Array.set(array, 0, "Scala");
Array.set(array, 1, "Java");
Array.set(array, 2, "Groovy");
Array.set(array, 3, "Scala");
Array.set(array, 4, "Clojure");
//获取某一项的内容
System.out.println(Array.get(array, 3));
}
2.通过反射调用方法
Class类除了可以创建对象,还可以调用指定的方法:可以先通过Class类的getDeclaredMethod方法获取指定的方法,并设置需要的参数,然后使用invoke方法进行激活该方法,并向该方法中传递要设置的obj对象的参数。Method类的invoke方法的语法:“方法(Method).invoke(Object obj , Object[] args)”。如果调用的方法是静态方法,那么obj设置为null;如果没有方法参数,则args设置为null。
如果调用的Method是private修饰的,在使用invoke方法之前最好先调用setAccessible方法,我们可以使用方法(Method).setAccessible(true)来关闭安全检查,如:
public static void test02() throws Exception{
User user = new User(1,"阿狸","123654");
Class<? extends User> clazz = user.getClass();
Method method = clazz.getDeclaredMethod("getName", null);
method.setAccessible(true);
Object result = method.invoke(user, null);
System.out.println(result);
}
注意这里的Method类调用setAccessible(true)是取消安全检查,并不是说设置为false就不能访问,取消安全检查的目的是为了提高反射效率。
3.通过反射调用属性
Class类可以调用对象的属性:先通过getDeclaredField获取对象的属性,然后如果调用的属性是private的话,就需要使用setAccessible方法(参数设置为true)来获取权限(使用setAccessible方法还可以提高反射的运行速率)。
语法:使用“属性(Field).set(对象obj,“属性值”)”来改变属性值;使用“属性(Field).get(对象obj)”来访问属性。
通常的po类对象都是有getXxx、setXxx方法的,可以通过getXxx、setXxx访问属性,但是如果没有这些方法,就可以使用反射来访问类的属性了。
@Test
public void test() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
User user = new User(1, "阿离", "123654");
Class<? extends User> clazz = user.getClass();
Field field=clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(user,"小阿狸");
Object result = field.get(user);
System.out.println(result);
}
如果访问的是private属性,并且不调用setAccessible(true)则会抛出异常:
反射的优缺点
优点:
可以实现动态创建对象和编译,体现出极高的灵活性,很多框架(如:Spring、MyBatis)的底层都是反射。
缺点:
首先,对性能有影响,使用反射机制基本上是一种解释机制,我们可以告诉JVM(Java虚拟机)希望它做什么并且满足我们的要求,这类操作总是慢于直接执行相同的操作。
其次,反射可以获取类中被private
修饰的变量、方法和构造器,这违反了面向对象的封装特性,因为被 private 修饰意味着不想对外暴露,只允许本类访问,而setAccessable(true)
可以无视访问修饰符的限制,外界可以强制访问。