Java反射详解

1,114 阅读4分钟
原文链接: www.codemore.top
反射

反射的概念是软件可以在运行时,检查,分析,修改代码。例如:在Junit中,使用@Test注解等。 在Java中,通过反射可以在运行时检查字段,方法,类,接口,注解等。无需知道类或者方法是如何调用的,也无需知道参数是如何传递的,这些都可以在运行时通过反射获取。同样也可以通过反射创建实例,调用实例方法。 当然反射也不是万能的,使用反射有如下的缺点:

  • 性能问题。因为反射是在运行时而不是在编译时,所有不会利用到编译优化,同时因为是动态生成,所以会比编译后的代码更慢
  • 安全问题。使用反射可以动态调用方法,动态生成实例等,可能会导致一些意想不到的事情,例如调用未对反序列化进行校验直接运行方法等。
  • 代码问题。反射动态生成代码,所有的逻辑并没有直接暴露给用户,会导致为何代码更加的码放。
使用案例

尽管反射有诸多的限制,但是它在某些场景中仍然是一项强大的工具。例如:

  • IDE重度使用反射,例如代码不全,动态类型,继承结构等。
  • Debugger 使用发射检查动态代码是如何执行的
  • Junit,Mockito等使用反射调用特定方法执行各种测试
  • 代码分析工具使用,Findbug等使用反射分析代码 总之,在实际应用中应该避免使用反射,而在编写框架的时候,反射会是一项强大的工具。
反射的组件和机制

Java中所有的都是类,反射也是这样。java.lang.Class包含了各种方法可以在运行时获取一个类的各种信息 获取一个类:

Class<? extends String> stringGetClass = stringer.getClass();
Class<String> stringClass = String.class;
Class.forName("java.lang.String");

检测其是否是某个实例或者是原生类型

stringGetClass.isInstance("dani");
stringGetClass.isPrimitive();

同样也可以通过Class创建新的实例:

String newInstanceStringClass = stringClass.newInstance();
String otherInstance = (String)Class.forName("java.lang.String").newInstance();

java.lang.Class.newInstance() 只能在有默认无参构造函数的时候使用,如果有参数,可以使用稍后介绍的Constructor来创建。

接口

接口不能实例化,只能暴露方法,对于接口来获取的只能是其基本信息

String interfaceName = InterfaceExample.class.getName();
枚举

枚举本质上是一个不可变的Java类

enum ExampleEnum{
	ONE,TWO,THREE,FOUR
}

同样也有一些枚举专用的反射方法:

  • java.lang.Class.isEnum(): 如果元素是枚举返回true否则返回false。
  • java.lang.Class.getEnumConstants(): 获取给定枚举所有的常量值。
  • java.lang.reflect.Field.isEnumConstant(): 如果字段是一个枚举返回true,否则返回false

对于枚举的用法:

ExampleEnum value = ExampleEnum.FOUR;
System.out.println("isEnum " + value.getClass().isEnum());
ExampleEnum[] enumConstants = value.getClass().getEnumConstants();
for(ExampleEnum exampleEnum: enumConstants){
	System.out.println("enum constant " + exampleEnum)
}

Field[] flds = value.getCalss().getDelcaredFields();
for(Field f: flds){
    System.out.println(f.getName() + " " + f.isEnumConsntats());
}

执行结果:

isEnum true
enum constant ONE
enum constant TWO
enum constant THREE
enum constant FOUR
ONE true
TWO true
THREE true
FOUR true
ENUM$VALUES false
原生类型

在Java中共有8个原生类型,byte,char,short,int,long,float,double,boolean。对于原生类型,可以获取其反射类,也可以检测一个反射类是否是原生类型,但是无法使用newInstance()创建原生类型实例,如果需要创建,可以使用它的包装类代替原生类使用。

Class<?> intClass = int.class;
System.out.println("is primiteve: " + intClass.isPrimitive());
字段

处理字段的主要类和方法:

  • java.lang.Class.getDeclaredFileds() 获取所有字段包括private字段
  • java.lang.Class.getFileds() 获取所有可以访问的字段,包括父类的
  • java.lang.Class.getFiled(String) 根据名字获取字段,包括其父类,如果不存在抛出异常
  • java.lang.getDeclaredFiled(String) 根据名字其声明获取字段,如果名字不存在抛出异常
  • java.lang.reflect.Field 该类是处理字段的主要类

示例:

String stringer = "this is a String called stringer";
Class<? extends String> stringGetClass = stringer.getClass();
Class<String> stringclass = String.class;
Field[] fields = stringclass.getDeclaredFields();
for( Field field : fields )
{
System.out.println( "*************************" );
System.out.println( "Name: " + field.getName() );
System.out.println( "Type: " + field.getType() );
// values
if( field.isAccessible() )
{
    System.out.println( "Get: " + field.get( stringer ) );
    // depending on the type we can access the fields using these methods
    // System.out.println( "Get boolean: " + field.getBoolean( stringer ) )
    // System.out.println( "Get short: " + field.getShort( stringer ) );
    // ...
}
System.out.println( "Modifiers:" + field.getModifiers() );
System.out.println( "isAccesible: " + field.isAccessible() );
}
// stringclass.getField( "hashCode" );//exception
Field fieldHashCode = stringclass.getDeclaredField( "hash" );// all fields can be
// fieldHashCode.get( stringer ); // this produces an java.lang.IllegalAccessException
// we change the visibility
fieldHashCode.setAccessible( true );
// and we can access it
Object value = fieldHashCode.get( stringer );
int valueInt = fieldHashCode.getInt( stringer );
System.out.println( value );
System.out.println( valueInt );

运行结果如下:

*************************
Name: value
Type: class [C
Modifiers:18
isAccesible: false
*************************
Name: hash
Type: int
Modifiers:2
isAccesible: false
*************************
Name: serialVersionUID
Type: long
Modifiers:26
isAccesible: false
*************************
Name: serialPersistentFields
Type: class [Ljava.io.ObjectStreamField;
Modifiers:26
isAccesible: false
*************************
Name: CASE_INSENSITIVE_ORDER
Type: interface java.util.Comparator
Modifiers:25
isAccesible: false
0
0
方法

处理方法的主要使用:

  • java.lang.Class.getMethods() 获取所有的方法,包括继承的方法
  • java.lang.Class.getDeclaredMethods() 获取所有其声明的方法
  • java.lang.Class.getMethod(String) 根据名字获取方法,包括继承方法
  • java.lang.Class.getDeclaredMethod(String) 根据名字获取其声明的方法
  • java.lang.reflect.Method 处理方法的主要类
Class<String> stringclass = String.class;
Method[] methods = stringclass.getMethods();
Method methodIndexOf = stringclass.getMethod( "indexOf", String.class );
// All methods for the String class
for( Method method : methods )
{
System.out.println( "****************************************************" );
System.out.println( "name: " + method.getName() );
System.out.println( "defaultValue: " + method.getDefaultValue() );
System.out.println( "generic return type: " + method.getGenericReturnType() );
System.out.println( "return type: " + method.getReturnType() );
System.out.println( "modifiers: " + method.getModifiers() );
// Parameters
Parameter[] parameters = method.getParameters();
System.out.println( parameters.length + " parameters:" );
// also method.getParameterCount() is possible
for( Parameter parameter : parameters )
{
    System.out.println( "parameter name: " + parameter.getName() );
    System.out.println( "parameter type: " + parameter.getType() );
}
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println( parameterTypes.length + " parameters:" );
for( Class<?> parameterType : parameterTypes )
{
    System.out.println( "parameter type name: " + parameterType.getName() );
}

// Exceptions
Class<?>[] exceptionTypes = method.getExceptionTypes();
System.out.println( exceptionTypes.length + " exception types: " );
for( Class<?> exceptionType : exceptionTypes )
{
    System.out.println( "exception name " + exceptionType.getName() );
}
System.out.println( "is accesible: " + method.isAccessible() );
System.out.println( "is varArgs: " + method.isVarArgs() );
}
构造函数

构造函数相关的类和方法:

  • java.lang.Class.getDeclaredConstructors() getConstructors()获取所有的构造函数
  • java.lang.Class.getDeclaredConstructor(String) getConstructr(String) 根据名字获取构造函数
  • java.lang.reflect.Constroctor 处理构造函数主要的类

示例:

// get all visible constructors
Constructor<?>[] constructors = stringGetClass.getConstructors();
//all constructors
Constructor<?>[] declaredConstructors = 	stringclass.getDeclaredConstructors();
for( Constructor<?> constructor : constructors )
{
	int numberParams = constructor.getParameterCount() ;
	System.out.println( "constructor " + constructor.getName() );
	System.out.println( "number of arguments " + numberParams);
	// public, private, etc.
	int modifiersConstructor = constructor.getModifiers();
	System.out.println( "modifiers " + modifiersConstructor );
	// array of parameters, more info in the methods section
	Parameter[] parameters = constructor.getParameters();
	// annotations array, more info in the annotations section
	Annotation[] annotations = constructor.getAnnotations();

}
// can be used to create new instances (no params in this case)
String danibuizaString = (String)constructor.newInstance(  );
Getters 和 Setters

getters和setters和其他的方法没有区别,只是他们是访问私有函数的主要方法。

例如:

public class Car
{

	private String name;
	private Object price;
	private Object year;

	public Car( String name, String year, String price )
	{
	this.name = name;
	this.price = price;
	this.year = year;
	}

	public String getName()
	{
	return name;
	}

	public void setName( String name )
	{
	this.name = name;
	}

	public Object getPrice()
	{
	return price;
	}

	public void setPrice( Object price )
	{
	this.price = price;
	}

	public Object getYear()
	{
	return year;
	}

	public void setYear( Object year )
	{
	this.year = year;
	}

}
Car car = new Car( "vw touran", "2010", "12000" );

Method[] methods = car.getClass().getDeclaredMethods();

// all getters, original values
for( Method method : methods )
{
	if( method.getName().startsWith( "get" ) )
	{
		System.out.println( method.invoke( car ) );
	}
}

// setting values
for( Method method : methods )
{

	if( method.getName().startsWith( "set" ) )
	{
		method.invoke( car, "destroyed" );
	}
}

// get new values
for( Method method : methods )
{
	if( method.getName().startsWith( "get" ) )
	{
		System.out.println( method.invoke( car ) );
	}
}

运行结果:

vw touran
2010
12000
destroyed
destroyed
destroyed
静态元素

静态的类,方法,字段和实例类,方法,字段完全不一样,因为它无需初始化类就可以直接使用。 例如:

public class StaticReflection
{

   	static class StaticExample
   	{
       int counter;
   	}
}
// 1 access static class
System.out.println( "directly " + StaticExample.class.getName() );
//2 using for name directly throws an exception
Class<?> forname = Class.forName("com.danibuiza.javacodegeeks.reflection.StaticReflection.StaticExample" );
//3 using $ would work but is not that nice    
Class<?> forname = Class.forName("com.danibuiza.javacodegeeks.reflection.StaticReflection$StaticExample" );
// 4 another way iterating through all classes declared inside this class
Class<?>[] classes = StaticReflection.class.getDeclaredClasses();
for( Class<?> class1 : classes )
{
	System.out.println( "iterating through declared classes " + class1.getName() );
}
数组

类java.lang.reflect.Array提供了各种方法处理数组的反射:

  • java.lang.reflect.Array.newInstance(Class,int) 创建一个数组
  • java.lang.reflect.Array.set(Object,int,Object) 设置数组元素值
  • java.lang.reflect.Array.getLength(Object) 返回数组大小
  • java.lang.reflect.Array.get(Object,int) 获取数组元素
  • java.lang.reflect.Array.getXxx(Object,int) 获取数组元素,Xxx代表原生类型,int,long等。

示例:

// using the Array class it is possible to create new arrays passing the type and the length via reflection
String[] strArrayOne = (String[])Array.newInstance( String.class, 10 );

// it contains utility methods for setting values
Array.set( strArrayOne, 0, "member0" );
Array.set( strArrayOne, 1, "member1" );
Array.set( strArrayOne, 9, "member9" );

// and for getting values as well
System.out.println( "strArrayOne[0] : " + Array.get( strArrayOne, 0 ) );
System.out.println( "strArrayOne[1] : " + Array.get( strArrayOne, 1 ) );
System.out.println( "strArrayOne[3] (not initialized) : " + Array.get( strArrayOne, 3 ) );
System.out.println( "strArrayOne[9] : " + Array.get( strArrayOne, 9 ) );

// also methods to retrieve the lenght of the array
System.out.println( "lenght strArrayOne: " + Array.getLength( strArrayOne ) );

// primitive types work as well
int[] intArrayOne = (int[])Array.newInstance( int.class, 10 );

Array.set( intArrayOne, 0, 1 );
Array.set( intArrayOne, 1, 2 );
Array.set( intArrayOne, 9, 10 );

// and specific getters and setters for primitive types
for( int i = 0; i < Array.getLength( intArrayOne ); ++i )
{
	System.out.println( "intArrayOne[" + i + "] : " + Array.getInt( intArrayOne, i ) );
}
// retrieve the class from an instance
Class<String[]> stringArrayClassUsingInstance = String[].class;
System.out.println( "stringArrayClassUsingInstance is array: " + stringArrayClassUsingInstance.isArray() );

// using class for name and passing [I
Class<?> intArrayUsingClassForName = Class.forName( "[I" );
System.out.println( "intArrayUsingClassForName is array: " + intArrayUsingClassForName.isArray() );

// or [Ljava.lang.String
Class<?> stringArrayClassUsingClassForName = Class.forName( "[Ljava.lang.String;" );
System.out.println( "stringArrayClassUsingClassForName is array: "
	+ stringArrayClassUsingClassForName.isArray() );

// this has no much sense in my opinion since we are creating an array at runtime and
// getting the class to create a new one...
Class<? extends Object> stringArrayClassUsingDoubleLoop = Array.newInstance( String.class, 0 ).getClass();
System.out.println( "stringArrayClassUsingClassForName is array: " + stringArrayClassUsingDoubleLoop.isArray() );   
Collection

对于反射来说,集合并没有特别之处,例如:

private static void reflectionCollections( Object ref )
{
//check is collection	
	if( ref instanceof Collection )
	{
		System.out.println( "A collection:  " + ref.getClass().getName() );
		@SuppressWarnings( "rawtypes" )
		// not nice
		Iterator items = ( (Collection)ref ).iterator();
		while( items != null && items.hasNext() )
		{
			Object item = items.next();
			System.out.println( "Element of the collection:  " + item.getClass().getName() );
		}
	}
	else
	{
		System.out.println( "Not a collection:  " + ref.getClass().getName() );
	}
}
Map<String, String> map = new HashMap<String, String>();
map.put( "1", "a" );
map.put( "2", "b" );
map.put( "3", "c" );
map.put( "4", "d" );

reflectionCollections( map );
reflectionCollections( map.keySet() );
reflectionCollections( map.values() );

List<String> list = new ArrayList<String>();
list.add( "10" );
list.add( "20" );
list.add( "30" );
list.add( "40" );

reflectionCollections( list );
reflectionCollections( "this is an string" );

输出:

Not a collection:  java.util.HashMap
A collection:  java.util.HashMap$KeySet
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
A collection:  java.util.HashMap$Values
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
A collection:  java.util.ArrayList
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Element of the collection:  java.lang.String
Not a collection:  java.lang.String
注解

关于注解,会有另一片文章详细说明,现在只是简单介绍下它的用法:

Class<ReflectableClass> object = ReflectableClass.class;
// Retrieve all annotations from the class
Annotation[] annotations = object.getAnnotations();
for( Annotation annotation : annotations )
{
System.out.println( annotation );
}
// Checks if an annotation is present
if( object.isAnnotationPresent( Reflectable.class ) )
{
	// Gets the desired annotation
	Annotation annotation = object.getAnnotation( Reflectable.class );

	System.out.println( annotation + " present in class " + 			object.getClass() );// java.lang.class
	System.out.println( annotation + " present in class " + 	object.getTypeName() );// 	com.danibuiza.javacodegeeks.reflection.ReflectableClass

}
泛型

泛型是在java1.5之后引入的。通过注解,可以得到参数,返回值是否是一个泛型值,对于泛型将会有另一篇文章详细解释。这里只给出简单示例:

Method getInternalListMethod = GenericsClass.class.getMethod( "getInternalList", null );

// we get the return type
Type getInternalListMethodGenericReturnType = getInternalListMethod.getGenericReturnType();

// we can check if the return type is parameterized (using ParameterizedType)
if( getInternalListMethodGenericReturnType instanceof ParameterizedType )
{
	ParameterizedType parameterizedType = (ParameterizedType)getInternalListMethodGenericReturnType;
	// we get the type of the arguments for the parameterized type
	Type[] typeArguments = parameterizedType.getActualTypeArguments();
	for( Type typeArgument : typeArguments )
	{
		// warning not nice
		// we can work with that now
		Class typeClass = (Class)typeArgument;
		System.out.println( "typeArgument = " + typeArgument );
		System.out.println( "typeClass = " + typeClass );
	}
}
类加载器

Java中类的加载是通过ClassLoader加载到内存的。 获取一个ClassLoader

ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader classClassLoader = ReflectableClass.class.getClassLoader();

加载一个类:

Class<?> reflectableClassInstanceLoaded = systemClassLoader
                .loadClass( "com.danibuiza.javacodegeeks.reflection.ReflectableClass" );
Class<?> reflectableClassInstanceForName = Class
                .forName( "com.danibuiza.javacodegeeks.reflection.ReflectableClass", true, systemClassLoader ); 
动态代理

动态代理可以动态的改变一个函数的行为,增强一个函数的功能,例如Spring中的AOP功能等。 示例:

public class HandlerImpl implements InvocationHandler
{

    @Override
    public Object invoke( Object obj, Method method, Object[] arguments ) throws Throwable
    {
        System.out.println( "using proxy " + obj.getClass().getName() );
        System.out.println( "method " + method.getName() + " from interface " + method.getDeclaringClass().getName() );

        // we can check dynamically the interface and load the implementation that we want
        if( method.getDeclaringClass().getName().equals( "com.danibuiza.javacodegeeks.reflection.InformationInterface" ) )
        {
            InformationClass informationImpl = InformationClass.class.newInstance();
            return method.invoke( informationImpl, arguments );
        }

        return null;
    }
}
// an invocation handler for our needs
InvocationHandler myHandler = new HandlerImpl();

// we can create dynamic proxy clases using the Proxy class
InformationInterface proxy = InformationInterface)Proxy.newProxyInstance(InformationInterface.class.getClassLoader(),  new Class[] {InformationInterface.class },  myHandler);

// all calls to the proxy will be passed to the handler -> the handler 	implementation can be
// decided on runtime as well
System.out.println( proxy.getInfo() ); 
Java 8 反射功能

Java 8在编译时增加了 -parameters 的参数,可以保留函数的参数名,如果不加这个参数,那么Java会将函数的参数名全部替换为arg0,arg1...这种形式。 使用

javac -parameters <class>

对于Maven

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>3.1</version>
 <configuration>
		<source>1.8</source>
		<target>1.8</target>
	 <compilerArgument>-parameters</compilerArgument>
 </configuration>
</plugin>