但行好事
莫论前程❤

Java基础学习总结-特性-反射机制

​ 这里我们来探讨Java中的一些动态特性,包括反射,注解,动态代理,类加载器等.利用这些特性我们可以优雅地实现一些灵活通用的功能,他们经常用于各种框架,库和系统程序中,比如:

  • Jackson,利用反射和注解实现了通用的序列化机制.
  • 有多种库(SpringMVC等)用于处理Web请求,利用反射和注解,能方便的将用户的请求参数和内容转换为Java对象.将java对象转换为响应内容
  • 有多种库(Spring)利用这些特性实现了对象管理容器,方便程序员管理对象的生命周期以及其中复杂的依赖关系.
  • 应用服务器(Tomcat)利用类加载器实现不同应用之间的隔离,JSP技术利用类加载器实现修改代码不用重启就能生效的特性.
  • 面向切面的编程AOP(Aspect Oriented Programming) 将编程中通用的关注点(如日志记录,安全检查等)与业务的主题逻辑相分离,减少冗余代码,提高程序的可维护性,AOP需要依赖上面的这些特性来实现.

本章节主要讲解

反射

例子:

public class ReflectTest {
    public static Car initByDefaultConst() throws Throwable {

//        1. 通过类装载器获取car类对象
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class clazz = loader.loadClass("com.smart.reflect.Car");

//        2. 获取类的默认构造器对象,并通过它实例化Car
        Constructor cons = clazz.getDeclaredConstructor((Class[]) null);
        Car car = (Car) cons.newInstance();

//      3. 通过反射方法设置属性,
        Method setBrand = clazz.getMethod("setBrand",String.class);
        setBrand.invoke(car,"奥迪A6");
        Method setColor = clazz.getMethod("setColor",String.class);
        setColor.invoke(car,"黑色");


//       4.通过反射访问类字段,赋值
        Field maxSpeed = clazz.getDeclaredField("maxSpeed");
//      在访问private或protected成员变量和方法时,需要通过setAccessible(Boolean boolean)方法取消Java语言检查.
        maxSpeed.setAccessible(true);
        maxSpeed.set(car,12);
        return car ;
    }

    public static void main(String[] args) throws Throwable {
        Car car = initByDefaultConst();
        car.introduce();
    }
}

​ 在一般操作数据的时候,我们都是知道并且依赖于数据类型的,比如:

​ 1) 根据类型使用new创建对象.

2) 根据类型定义变量,类型可能是基本类型,类,接口或数组.

​ 3) 将特定类型的对象传递给方法

​ 4) 根据类型访问对象的属性,调用该对象的方法

编译器也是根据类型进行代码的检查编译的

​ 而反射不一样,它是在运行时,而非编译时,动态获取类型的信息,比如接口信息,成员信息,方法信息,构造方法信息等,根据这些动态获取到的信息创建对象,访问/修改成员,调用方法等.

反射的入口是Class类.

Class类

​ 每个已加载的类在内存都有一份类信息,每个对象都有指向它所属类信息的引用.Java中,类信息对应的类是java.lang.Class.

​ 所有类的根父类Object有一个方法,可以获取对象的Class对象

public final native Class<?> getClass()

获取Class对象不一定需要实例对象,如果在写程序时就知道类名,可以使用<类型>.class获取Class对象.比如:

Class<Date> cls = Date.class;

接口也有Class对象,且这种方式对于接口也是适用的,比如:

Class<Comparable> cls = Comparable.class;

基本数据类型没有getClass()方法.但也都有对应的Class对象,类型参数为对应的包装类型.

Class<Integer> intCls = int.class;
Class<Byte>  byteCls = Byte.class;
Class<Character>  charCls = char.class;
Class<Double> doubleCls = double.class;

Class有一个静态方法forName,可以根据类名直接加载Class,获取Class对象.比如:

try{
    Class<?> cls = Class.forName("java.util.HashMap");
}catch(ClassNotFoundException e){
    e.printStackTrace();
}

Class类有很多方法

  1. 名称信息
    public String getName();
    public String getSimpleName();
    public String getCanonicalName();
    public Package getPackage();
    
Class对象getNamegetSimpleNamegetCanonicalNamegetPackage
int.classintintintnull
String.classjava.lang.StringStringjava.lang.Stringjava.lang
String[].class[Ljava.lang.StringString[]java.lang.String[]null
HashMap.classjava.util.HashMapHashMapjava.util.HashMapjava.util
Map.Entry.classjava.util.Map$EntryEntryjava.util.Map.Entryjava.util

​ 需要说明的是数组类型的getName返回值.它适用前缀[表示数组.有几个[表示是几维数组;数组的类型 用一个字符表示,I表示 int. L表示类或者接口,其他类型与字符的对应关系为:boolean (Z) , byte (B), char (C), double (D), float (F), long (J), short (S) .对于引用类型的数组,注意最后有一个分号.

  1. 字段信息 Field对象表示当前字段

    ​ 类中定义的静态和实例变量都被称为字段,用类Field表示.位于包java.lang.reflect下. reflect(反射).

    后面涉及到的反射相关的类都位于该包下. Class有4个获取字段信息的方法:

    //返回所有的public字段,包括其父类的.如果没有字段,返回空数组
    public Field[] getFields();
    
    //返回本类声明的所有字段,包括非public的,但不包括父类的
    public Field[] getDeclaredFields();         //   declared 声明的
    
    //返回本类或父类中指定名称的public字段,找不到抛出异常  NoSuchFieldException
    public Field getField(String name);      
    
    //返回本类中指定名称的public字段,找不到抛出异常  NoSuchFieldException
    public Field getDeclaredField(String name);      
    
    

    Field也可以获取字段的信息.也可以通过Field访问和操作指定对象中该字段的值,基本方法有:

    //获取字段的名称
    public String getName();
    
    //判断当前程序是否有该字段的访问权限
    public boolean isAccessible();
    
    //flag设为true表示忽略Java的访问检查机制,以允许读写非public的字段.
    public voud setAccessible(boolean flag);
    
    //获取指定对象obj中该字段的值
    public Object get(Object obj);
    
    //将指定对象obj中该字段的值设置为value
    public void set(Object obj,Object value);
    

    ​ 注: 在get/set方法中,对于静态变量,obj被忽略,可以为null.如果字段是基本类型,get/set会自动在基本类型与对应的包装类型间进行转换;对于private字段,直接调用get/set会抛出非法访问异常,IllegalAccessException.应该先调用setAccessible(true)已关闭Java的检查机制,代码示例如下:

    List<String> obj = Arrays.asList(new String[]{"老马","编程"});
    Class<?> cls = obj.getClass;
    for(Field f :cls.getDeclaredFields()){
       f.setAccessible(true);
       System.out.print(f.getName() + " - " + f.get(obj));
    }
    

    Field其他方法

    //返回字段的修饰符
    public int getModifiers()
    //返回字段的类型
    public Class<?> getType()
    //以基本类型操作字段
    public void  setBoolean(Object obj,boolean z) 
    public boolean getBoolean(Object obj)
    public void  setBouble(Object obj,double d) 
    public boolean getBouble(Object obj)
    //查询字段的注解信息
    public <T extends Annotation> T  getAnnotation(Class<T> annotationClass);
    public Annotation[] getDeclaredAnnotations()
    

    getModifiers的返回值是一个int,可以通过Modifier类的静态方法进行解读.

    代码示例:

    public static final int MAX_NAME_LEN = 255;
    
    //可以这样查看该字段的修饰符:
    Field f = Student.class.getField(MAX_NAME_LEN);
    int mod = f.getModifiers();
    Sysm(Modifier.toString(mod));
    Sysm("isPublic: " + Modifier.isPublic(mod));
    Sysm("isStatic: " + Modifier.isStatic(mod));
    Sysm("isFinal: " + Modifier.isFinal(mod));
    Sysm("isVolatile: " + Modifier.isVolatile(mod));
    

    输出为:

    public static final 
    isPublic : true 
    isStatic : true 
    isFinale : true 
    isVolatile : false    //不稳定
    
  2. 方法信息

    类中定义的静态和实例方法都被称为方法.用类Method表示:.Class有如下相关方法:

    //返回所有的public方法,包括其父类的,如果没有方法,返回空数组
    public Method[] getMethods();
    
    //返回本类声明的所有方法,包括非public的,但不包括父类的
    public Method[] getDeclaredMethods();
    
    //返回本类或父类中指定名称和参数类型的public方法,找不到抛出异常  NoSuchMethodException
    public Method getMethod(String name,Class<?>... paramterTypes);      
    
    //返回本类中指定名称和参数类型的方法,找不到抛出异常  NoSuchMethodException
    public Field getDeclaredField(String name,Class<?>... paramterTypes);        
    

    通过Method可以获取方法的信息,也可以通过Method调用对象的方法.

    //获取方法的名称
    public String getName();
    
    //flag设置为true 表示忽略Java的访问检查机制,以允许调用非public方法.
    public void setAccessible(boolean flag);
    
    //在指定对象obj上调用Method代表的方法,传递的参数列表为args
    public Object invoke(Object obj,Object...args) throws IllegalAccessException,IllegalArgumentException,InvovationTargetException
    

    对invoke方法,如果Method为静态方法,obj被忽略,可以为null,args可以为null,也可以为一个空的数组,方法调用的返回值被包装为Object返回,如果实际方法调用抛出异常,异常被包装为InvocationTargetException重新抛出,可以通过getCause方法得到原异常。

    示例代码

    Class<?> cls = Integer.class;
    try{
       Method method = cls.getMethod("parseInt",new Class[]{String.class});
       Sysm(method.invoke(null,"123"));
    }catch(NosuchMethodException e){
       e.printStackTrace();
    }catch(InvocationTargtException e){
       e.printStackTrace();
    }
    
  3. 创建对象和构造方法

    Class有一个方法,可以用它来创建对象.

    public T newInstance() throws InstantiationException ,IllegalAccessException
    

    它会调用类的默认构造方法(即无参public构造方法),如果类没有该构造方法,会抛出异常InstantiationException.

    代码示例:

    Map<String,Integer> map = HashMap.class.newInstance();
    map.put.("hello",123);
    

    newInstance只能使用默认构造方法.Class还有一些方法,可以获取所有的构造方法:

    //获取所有的public构造方法,返回值可能为长度为0的空数组
    public Constructor<?>[] getConstructor();
    
    //获取所有的构造方法,包括非public的
    public Constructor<?>[]  getDeclaredConstructors();
    
    //获取指定参数类型的public构造方法,没找到抛出异常 NoSuchMethodException
    public Constructor<T>[] getConstructor(Class<?>...paramterTypes)
    
    //获取指定参数类型的构造方法,包括非public的,没找到抛出异常 NoSuchMethodException
    public Constructor<T>[] getDeclaredConstructor(Class<?>...paramterTypes)
    

    类Constructor表示构造方法,通过它可以创建对象,方法为:

    public T newInstance(Object...initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetException
    

    代码示例如下:

    Constructor<StringBuilder> constructor = StringBuilder.class.getConstructor(new Class[]{int.class});
    StringBuilder sb = constructor.newInstance(100);
    
  4. 类型检查和转换

    我们之前介绍过instanceof关键字,它可以用来判断变量指向的实际对象类型.Instanceof后面的类型是在代码中确定的,如果要检查的类型是动态的,可以使用Class类的如下方法:

    public native boolean isInstance(Object obj)
    

    也就是说,代码如下:

    if(list instanceof ArrayList){
       Sysm("array List");
    }
    

    和下面代码的输出是相同的:

    Class cls = Class.forName("java.util.ArrayList");
    if(cls.isInstance(List)){
       Sysm.("array List");
    }
    

    除了判断类型,在程序中也往往需要进行强制类型转换

    List list = ..
       if(list instanceof ArrayList){
           ArrayList arrList = (ArrayList)list;
       }
    

    ​ 上述代码中,强制转换到的类型是在写代码时就知道的.如果是动态的,可以使用Class的如下方法:

    public T cast(Object obj)
    

    比如:

    public static <T> toType(Object obj,Class<T> cls){
       return cls.cast(obj);
    }
    

    isInstance/cast描述的都是对象和类之间的关系,Class还有一个方法,可以判断Class之间的关系;

    //检查参数类型cls能否赋给当前Class类型的变量
    public native boolean isAssignableFrom(Class<?> cls);
    //比如,如下表达式的结构都为true
    Object.class.isAssignableFrom(String. class)
    String.class.isAssignableFrom(String. class) 
    List.class.isAssignableFrom(ArrayList. class)
    
  5. Class的类型信息

    Class代表的类型既可以是普通的类,也可以是内部类还可以是基本类型,数组等.对于一个给定的Class对象,它到底是什么类型呢.?可以通过以下方法进行检查

    public native boolean isArray() //是否是数组
    public native boolean isPrimitive() //是否是基本类型
    public native boolean isInterface() //是否是接口
    public boolean isEnum() //是否是枚举
    public boolean isAnnotation() //是否是注解
    public boolean isAnonymousClass() //是否是匿名内部类 
    public boolean isMemberClass() //是否是成员类,成员类定义 在方法外,不是匿名类 
    public boolean isLocalClass() //是否是本地类,本地类定义在 方法内,不是匿名类 
    

    native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。

    Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI本机接口(Java Native Interface)调用其他语言来实现对底层的访问。

  6. 类的声明信息

    Class还有很多方法:可以获取累的声明信息.如修饰符,父类,接口,注解等.

    //获取修饰符,返回值可通过Modifier类进行解读
    public native int getModifiers() 
    //获取父类,如果为Object,父类为null
    public native Class<? super T> getSuperclass() 
    //对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类继承的
    public native Class<?>[] getInterfaces();
    //自己声明的注解
    public Annotation[] getDeclaredAnnotations()
    //所有的注解,包括继承得到的
    public Annotation[] getAnnotations() //获取或检查指定类型 的注解,包括继承得到的
    public <A extends Annotation> A getAnnotation(Class< A> annotationClass) 
    public boolean isAnnotationPresent( Class<? extends Annotation> annotationClass)
    
  7. 类的加载

    Class有两个静态方法.可以根据类名加载类

    public static Class<?> forName(String className)
    public static Class<?> forName(String name,boolean initialize,ClassLoader loader)
    

    ClassLoader表示类加载器,initialize表示加载后,是否执行类的初始化代码(如static语句块).第一个方法中没有传这些参数,相当于调用

    Class.forName(className,true,currwntLoader)
    

    currentLoader表示加载当前类的ClassLoader.

    ​ 这里className与Class.getName的返回值是一致的.

    比如,对于String数组:

    String name = "[java.lang.String;";
    Class cls = Class.forName(name);
    Sysm(cls == String[].class);
    

    注意:基本类型不支持forName方法,也就是说Class.forName(“int”);

    会抛出异常ClassNotFoundException.那如何根据原始类型的字符串 构造Class对象呢? 可以对Class.forName进行一下包装

    public static Class<?> forName(String className)throws ClassNotFoundException{
       if("int".equals(className){
           return int.class;
       }
       return Class.forName(className);
    }
    

    其中JAVA 9 还有一个forName方法,用于加载指定模块中指定名称的类

    public static Class<?> forName(Module module,String name);
    

    参数module表示模块,这是java 9 引入的类,当找不到类的时候,它不会抛出异常,而是返回null.不会执行类的初始化.

序列化中反射的应用

public static String toString(Object obj){
    try{
        Class<?> cls = obj.getClass();
        StringBuilder sb = new StringBuilder();
        sb.append(cls.getName() + "\n");
        for(Field f : cls.getDeclaredFields()){
            if(! f.isAccessible()){
                f.setAccessible(true);
            }
            sb.append(f.getName() + "=" + f.get(obj).toString() + "\n");
        }
        return sb.toString();
    }catch(IllegalAccessException e){
        throw new RuntimeException(e);
    }
}
public static Object fromString(String str){
    try{
        String[] lines = str.split("\n");
        if(lines.length < 1){
            throw new IllegalArgumentException(str);
        }
        Class<?> cls = Class.forName(lines[0]);
        Object obj = cls.newInstance();
        if(lines.length > 1){
            for(int i = 1;i < lines.length; i++){
                String[] fv = lines[i].split("=");
                if(fv.length != 2){
             throw new IllegalArgumentException(lines[i]);
                }
                Field f = cls.getDeclaredField(fv[0]);
                if(! f.isAccessible()){
                     f.setAccessible(true);
                }
                setFieldValue(f,obj,fv[1]);
            }
        }
        return obj;
    }catch(Exception e){
        throw new RuntimeException(e);
}
赞(2) 打赏
未经允许不得转载:刘鹏博客 » Java基础学习总结-特性-反射机制
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏