​Java反射详解

时间:2022-06-04
本文章向大家介绍​Java反射详解,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
反射

反射的概念是软件可以在运行时,检查,分析,修改代码。例如:在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>