反射与类操作
在反射机制的处理过程中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成结构操作,任何一个类的基本组成结构:父类(父接口)、包、属性、方法(构造,普通)。
反射获取类结构信息
一个类的基本信息主要包括类所在的包名称、父类的定义、父接口的定义。
想要获得类的基本信息,可以使用Class类的如下方法:
- 获取包名称
public Package getPackage();
;Person.class.getPackage().getName()
- 获取继承父类
public Class<? super T> getSuperclass();
;Person.class.getSuperclass().getName();
- 获取实现父接口
public Class<?>[] getInterfaces();
;-
Class<?> clazz[] =cls.getInterfaces(); for(Class<?> temp : clazz) { System.out.println(temp.getName()); }
-
获取了一个类的Class对象之后就意味着这个对象可以获取类中的一切继承结构信息。
反射调用构造方法
在一个类中除了有继承关系之外,最为重要的就是类中的结构处理了,而类中的结构里面首先需要观察的就是构造方法的使用问题,实际上在之前通过反射实例化对象的时候就已经接触过构造方法的问题了:
- 实例化方法替代:
clazz.getDeclaredConstructor().newInstance();
所有类的构造犯法的获取都可以通过直接通过Class类来完成,该类中定义有如下的几个方法:
- 获取所有构造方法:
public Constructor<?>[] getDeclaredConstructors() throws SecurityException;
- 获取指定构造方法:
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException;
- 获取所有构造方法:
public Constructor<?>[] getConstructors() throws SecurityException;
- 获取指定构造方法:
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException;
获取构造
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class; //取指定类Class对象
Constructor<?>[] constructors = cls.getConstructors();
for(Constructor<?> temp: constructors) {
System.out.println(temp);
}
}
此时获取的是类之中的全部构造方法,但是也可以获取一个指定参数的构造,例如:现在Person类中提供两个构造方法,打算调用有残构造方法进行Person类对象的实例方法,此时必须指明要调用的构造而后通过Constructor提供的实例化方法操作:public T newInstance(Object ... initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetgetException
通过指定构造实例化对象
import java.lang.reflect.Constructor;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class; //取指定类Class对象
Constructor<?> constructors = cls.getConstructor(String.class, int.class);
Object obj = constructors.newInstance("lyz",18);
System.out.println(obj);
}
}
虽然程序本身允许开发者调用有参构造本身,但是如果从实际开发来讲所有使用反射的类中最好提供有无参构造,这样的实例化可以达到统一性。
反射调用普通方法
在进行反射处理时也可以通过反射获取类之中的全部方法,但是如果想要通过反射调用这些方法必须有一个前提条件:类之中要提供实例化对象。
在Class类中提供如下操作可以获取方法对象:
- 获取全部方法:
public Method[] getMethods() throws SecurityException
- 获取指定方法:
public Method getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException
- 获取本类全部方法:
public Method[] getDeclaredMethods() throws SecurityException
- 获取本类指定方法:
public Method getDeclaredMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException,SecurityException
获取全部方法
package Basic;
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class; //取指定类Class对象
{ //获取全部方法(包括父类中的方法)
Method methods[] = cls.getMethods();
for(Method temp:methods) {
System.out.println(temp);
}
}
System.out.println("-------------------------------");
{ //获取本类方法
Method methods[] = cls.getDeclaredMethods();
for(Method temp:methods) {
System.out.println(temp);
}
}
}
}
需要注意的是这个事实的方法信息的获取是依靠Method类提供的toString()方法完成的,很多时候也可以由用户自己完成拼凑信息展示形式。
自定义信息展示形式(不是重点)
package Basic;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Person.class; //取指定类Class对象
{ //获取全部方法(包括父类中的方法)
Method methods[] = cls.getMethods();
for(Method temp:methods) {
int mod = temp.getModifiers(); //修饰符
System.out.print(Modifier.toString(mod) + " ");
System.out.print(temp.getReturnType().getName() + " ");
System.out.print(temp.getName() + "("); //方法名
Class<?> params[] = temp.getParameterTypes();
for(int x=0;x<params.length;x++) {
System.out.print(params[x].getName() + " " +"arg-"+x);
if(x<params.length-1) {
System.out.print(",");
}
}
System.out.print(")");
Class<?> exp [] = temp.getExceptionTypes();
if(exp.length > 0) {
System.out.print("throws");
}
for (int x=0;x<exp.length;x++) {
System.out.print(exp[x].getName());
if(x < exp.length - 1) {
System.out.print(",");
}
}
System.out.println();
}
}
System.out.println("-------------------------------");
{ //获取本类方法
Method methods[] = cls.getDeclaredMethods();
for(Method temp:methods) {
System.out.println(temp);
}
}
}
}
只需要清楚可以根据反射获取方法的结构即可,不需要过多深入了解。但是Method中有一个致命的重要方法:public Object invoke(Object obj, Object... args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetgetException
在Person类中为属性增加setter、getter方法。随后通过反射机制实现Person类之中的setter与getter的调用处理。
在不导入指定类开发包的情况下实现属性的配置
package Basic;
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("Basic.Person"); //取指定类Class对象
String value = "lyz"; //要设置的属性内容
//1.任何情况下要想保存类中的属性或者调用类中的方法都必须保证存在实例化对象
Object obj = cls.getDeclaredConstructor().newInstance(); //调用无参构造实例化对象
//2.要想进行方法的调用,一定要先获取方法的名称
String setMethodName = "setName";
Method setMethod = cls.getDeclaredMethod(setMethodName, String.class); //获取指定的方法
setMethod.invoke(obj, value); //等价于:Person对象.setName(value);
String getMethodName = "getName";
Method getMethod = cls.getDeclaredMethod(getMethodName); //getter没有参数
System.out.println(getMethod.invoke(obj)); //等价于Person对象.getName();
}
}
利用此类操作整体的形式不会有明确的类对象产生,一切都是依靠反射机制处理的,这样的处理避免的与某一个类的耦合问题。
反射调用成员
类结构中核心的组成:成员(Field),对于成员信息的获取也是通过Class类完成的,在这个类中提供如下操作方法:
- 获取本类全部成员:
public Field[] getDeclaredFields() throws SecurityException
- 获取本类指定成员:
public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException
- 获取父类全部成员:
public Field[] getFields() throws SecurityException
- 获取父类指定成员:
public Field getField(String name) throws NoSuchFieldException,SecurityException
获取类中成员
package Basic;
import java.lang.reflect.Field;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("Basic.Person"); //取指定类Class对象
{ //获取父类之中公共成员信息
Field fields[] = cls.getFields();
for(Field temp:fields) {
System.out.println(temp);
}
}
System.out.println("-------------------------------");
{ //获取子类中的成员信息
Field fields[] = cls.getDeclaredFields();
for(Field temp:fields) {
System.out.println(temp);
}
}
}
}
在Field中最为重要的操作形式并不是获取全部的成员,而是如下的三个方法:
- 设置属性内容:
public void set(Object obj, Object value) throws IllegalArgumentException,IllegalAccessException
- 获取属性内容:
public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException
- 解除封装:
public void setAccessible(boolean flag)
所有的成员是在对象实例化之后进行空间分配的,所以是一定要先有实例化对象之后才可以进行成员的操作。
直接调用类中的私有成员
package Basic;
import java.lang.reflect.Field;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("Basic.Person"); //取指定类Class对象
Object obj = cls.getConstructor().newInstance(); //实例化对象(分配成员空间)
Field nameField = cls.getDeclaredField("name"); //获取成员对象
nameField.setAccessible(true); //解除封装
nameField.set(obj, "lyz"); //等价于: Person对象.name = "lyz";
System.out.println(nameField.get(obj));
}
}
通过一系列分析可以发现,类中的构造、方法、成员属性都可以通过反射实现调用,但是对于成员的反射嗲用很少这样直接处理,大部分操作都应该通过setter或getter完成,所以对于以上代码只能说是反射的特色,不具备实际的使用的能力。而对于Field类在实际开发之中只有一个方法最为常用:
- 获取成员类型:
public Class<?> getType();
获取类中的成员类型
package Basic;
import java.lang.reflect.Field;
public class test {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("Basic.Person"); //取指定类Class对象
Field nameField = cls.getDeclaredField("name"); //获取成员对象
System.out.println(nameField.getType().getName()); //获取完整类名称
System.out.println(nameField.getType().getSimpleName()); //获取类名称
}
}
在以后开发中进行反射处理时往往会利用Field与Method类实现类中方法的调用。
Unsafe工具类
反射是java的第一大特点,一旦打开反射的大门就有了更加丰富的类设计形式。除了JVM本身支持的反射的处理之外,java中才提供一个Unsafe类,这个类的主要特点是可以利用反射来获取对象,并且直接使用底层的C++来代替JVM执行,即可以绕过JVM的相关的对象的管理机制,如果一旦使用了Unsafe类,那么项目之中将无法使用JVM的内存管理简自豪以及垃圾回收处理。
如果想要使用Unsafe类首先要确认一下这个类中定义的构造方法与常量问题。
- 构造方法:
private Unsafe() {}
- 私有常量:
private static final Unsafe theUnsafe = new Unsafe();
要注意的是,Unsafe类中并未提供static方法,即不能通过类似传统的单例设计模式来进行操作,如果想要获得这个类的对象必须通过反射机制来完成。
获取Unsafe对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe UnsafeObj = (Unsafe)field.get(null); //static属性不需要传递实例化对象
传统开发中,一个程序类必须通过实例化对象才可以调用类中的普通方法,尤其是以单例设计模式为例。
绕过实例化对象管理
package Basic;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class test {
public static void main(String[] args) throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe UnsafeObj = (Unsafe)field.get(null); //static属性不需要传递实例化对象
//利用Unsafe类绕过了JVM管理机制,可以在没有实例化对象的情况下获取一个Singleton对象
Singleton instance = (Singleton)UnsafeObj.allocateInstance(Singleton.class);
instance.print();
}
}
class Singleton{
private Singleton() {
System.out.println("singleton类构造方法");
}
public void print() {
System.out.println("test");
}
}
Unsafe只能说为我们的开发提供一些更加方便的处理机制,但是这种操作不收JVM的管理,所有如果不是必须的情况下,不建议使用,学习这个类的主要目的是巩固对于反射的理解,也是帮助大家在笔试的时候问到单例设计模式的情况下,也可以说一个Unsafe以加深对着一个概念的理解已获得较好的面试效果。