网站建设后怎么写,入侵WordPress网站,长沙本土网站制作公司,lamp 网站建设论文本系列文章经补充和完善#xff0c;已修订整理成书《Java编程的逻辑》#xff0c;由机械工业出版社华章分社出版#xff0c;于2018年1月上市热销#xff0c;读者好评如潮#xff01;各大网店和书店有售#xff0c;欢迎购买#xff0c;京东自营链接#xff1a;http://…本系列文章经补充和完善已修订整理成书《Java编程的逻辑》由机械工业出版社华章分社出版于2018年1月上市热销读者好评如潮各大网店和书店有售欢迎购买京东自营链接http://item.jd.com/12299018.html 上节介绍完了并发从本节开始我们来探讨Java中的一些动态特性包括反射、类加载器、注解和动态代理等。利用这些特性可以以优雅的方式实现一些灵活和通用的功能经常用于各种框架、库和系统程序中比如 在63节介绍的实用序列化库Jackson利用反射和注解实现了通用的序列化/反序列化机制有多种库如Spring MVC, Jersey用于处理Web请求利用反射和注解能方便的将用户的请求参数和内容转换为Java对象将Java对象转变为响应内容有多种库如Spring, Guice利用这些特性实现了对象管理容器方便程序员管理对象的生命周期以及其中复杂的依赖关系应用服务器比如Tomcat利用类加载器实现不同应用之间的隔离、JSP技术也利用类加载器实现修改代码不用重启就能生效的特性面向方面的编程(AOP - Aspect Oriented Programming)将编程中通用的关注点比如日志记录、安全检查等与业务的主体逻辑相分离减少冗余代码提高程序的可维护性AOP需要依赖上面的这些特性来实现本节先来看反射机制。 在一般操作数据的时候我们都是知道并且依赖于数据的类型的比如 根据类型使用new创建对象根据类型定义变量类型可能是基本类型、类、接口或数组将特定类型的对象传递给方法根据类型访问对象的属性调用对象的方法编译器也是根据类型进行代码的检查编译。 反射不一样它是在运行时而非编译时动态获取类型的信息比如接口信息、成员信息、方法信息、构造方法信息等根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等。这么说比较抽象下面我们会具体来说明反射的入口是名称为Class的类我们来看下。 Class类 获取Class对象 我们在17节介绍过类和继承的基本实现原理我们提到每个已加载的类在内存都有一份类信息每个对象都有指向它所属类信息的引用。Java中类信息对应的类就是java.lang.Class注意不是小写的classclass是定义类的关键字所有类的根父类Object有一个方法可以获取对象的Class对象 public final native Class? getClass() Class是一个泛型类有一个类型参数getClass()并不知道具体的类型所以返回Class?。 获取Class对象不一定需要实例对象如果在写程序时就知道类名可以使用类名.class获取Class对象比如 ClassDate cls Date.class; 接口也有Class对象且这种方式对于接口也是适用的比如 ClassComparable cls Comparable.class; 基本类型没有getClass方法但也都有对应的Class对象类型参数为对应的包装类型比如 ClassInteger intCls int.class;
ClassByte byteCls byte.class;
ClassCharacter charCls char.class;
ClassDouble doubleCls double.class; void作为特殊的返回类型也有对应的Class ClassVoid voidCls void.class; 对于数组每种类型都有对应数组类型的Class对象每个维度都有一个即一维数组有一个二维数组有一个不同的比如 String[] strArr new String[10];
int[][] twoDimArr new int[3][2];
int[] oneDimArr new int[10];
Class? extends String[] strArrCls strArr.getClass();
Class? extends int[][] twoDimArrCls twoDimArr.getClass();
Class? extends int[] oneDimArrCls oneDimArr.getClass(); 枚举类型也有对应的Class比如 enum Size {SMALL, MEDIUM, BIG
}ClassSize cls Size.class; Class有一个静态方法forName可以根据类名直接加载Class获取Class对象,比如 try {Class? cls Class.forName(java.util.HashMap);System.out.println(cls.getName());
} catch (ClassNotFoundException e) {e.printStackTrace();
} 注意forName可能抛出异常ClassNotFoundException。 有了Class对象后我们就可以了解到关于类型的很多信息并基于这些信息采取一些行动Class的方法很多大部分比较简单直接容易理解下面我们分为若干组进行简要介绍。 名称信息 Class有如下方法可以获取与名称有关的信息 public String getName()
public String getSimpleName()
public String getCanonicalName()
public Package getPackage() getSimpleName不带包信息getName返回的是Java内部使用的真正的名字getCanonicalName返回的名字更为友好getPackage返回的是包信息它们的不同可以看如下表格 需要说明的是数组类型的getName返回值它使用前缀[表示数组有几个[表示是几维数组数组的类型用一个字符表示I表示intL表示类或接口其他类型与字符的对应关系为: boolean(Z), byte(B), char(C), double(D), float(F), long(J), short(S)对于引用类型的数组注意最后有一个分号;。 字段(实例和静态变量)信息 类中定义的静态和实例变量都被称为字段用类Field表示位于包java.util.reflect下后文涉及到的反射相关的类都位于该包下Class有四个获取字段信息的方法 //返回所有的public字段包括其父类的如果没有字段返回空数组
public Field[] getFields()
//返回本类声明的所有字段包括非public的但不包括父类的
public Field[] getDeclaredFields()
//返回本类或父类中指定名称的public字段找不到抛出异常NoSuchFieldException
public Field getField(String name)
//返回本类中声明的指定名称的字段找不到抛出异常NoSuchFieldException
public Field getDeclaredField(String name) Field也有很多方法可以获取字段的信息也可以通过Field访问和操作指定对象中该字段的值基本方法有 //获取字段的名称
public String getName()
//判断当前程序是否有该字段的访问权限
public boolean isAccessible()
//flag设为true表示忽略Java的访问检查机制以允许读写非public的字段
public void 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的检查机制。 看段简单的示例代码 ListString obj Arrays.asList(new String[]{老马,编程});
Class? cls obj.getClass();
for(Field f : cls.getDeclaredFields()){f.setAccessible(true);System.out.println(f.getName() - f.get(obj));
} 代码比较简单就不赘述了。我们在ThreadLocal一节介绍过利用反射来清空ThreadLocal这里重复下其代码含义就比较清楚了 protected void beforeExecute(Thread t, Runnable r) {try {//使用反射清空所有ThreadLocalField f t.getClass().getDeclaredField(threadLocals);f.setAccessible(true);f.set(t, null);} catch (Exception e) {e.printStackTrace();}super.beforeExecute(t, r);
} 除了以上方法Field还有很多别的方法比如 //返回字段的修饰符
public int getModifiers()
//返回字段的类型
public Class? getType()
//以基本类型操作字段
public void setBoolean(Object obj, boolean z)
public boolean getBoolean(Object obj)
public void setDouble(Object obj, double d)
public double getDouble(Object obj)//查询字段的注解信息
public T extends Annotation T getAnnotation(ClassT annotationClass)
public Annotation[] getDeclaredAnnotations() getModifiers返回的是一个int可以通过Modifier类的静态方法进行解读比如假定Student类有如下字段 public static final int MAX_NAME_LEN 255; 可以这样查看该字段的修饰符 Field f Student.class.getField(MAX_NAME_LEN);
int mod f.getModifiers();
System.out.println(Modifier.toString(mod));
System.out.println(isPublic: Modifier.isPublic(mod));
System.out.println(isStatic: Modifier.isStatic(mod));
System.out.println(isFinal: Modifier.isFinal(mod));
System.out.println(isVolatile: Modifier.isVolatile(mod)); 输出为 public static final
isPublic: true
isStatic: true
isFinal: true
isVolatile: false 关于注解我们下节再详细介绍。 方法信息 类中定义的静态和实例方法都被称为方法用类Method表示Class有四个获取方法信息的方法 //返回所有的public方法包括其父类的如果没有方法返回空数组
public Method[] getMethods()
//返回本类声明的所有方法包括非public的但不包括父类的
public Method[] getDeclaredMethods()
//返回本类或父类中指定名称和参数类型的public方法找不到抛出异常NoSuchMethodException
public Method getMethod(String name, Class?... parameterTypes)
//返回本类中声明的指定名称和参数类型的方法找不到抛出异常NoSuchMethodException
public Method getDeclaredMethod(String name, Class?... parameterTypes) 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, InvocationTargetException 对invoke方法如果Method为静态方法obj被忽略可以为nullargs可以为null也可以为一个空的数组方法调用的返回值被包装为Object返回如果实际方法调用抛出异常异常被包装为InvocationTargetException重新抛出可以通过getCause方法得到原异常。 看段简单的示例代码 Class? cls Integer.class;
try {Method method cls.getMethod(parseInt, new Class[]{String.class});System.out.println(method.invoke(null, 123));
} catch (NoSuchMethodException e) {e.printStackTrace();
} catch (InvocationTargetException e) {e.printStackTrace();
} Method还有很多方法可以获取方法的修饰符、参数、返回值、注解等信息比如 //获取方法的修饰符返回值可通过Modifier类进行解读
public int getModifiers()
//获取方法的参数类型
public Class?[] getParameterTypes()
//获取方法的返回值类型
public Class? getReturnType()
//获取方法声明抛出的异常类型
public Class?[] getExceptionTypes()
//获取注解信息
public Annotation[] getDeclaredAnnotations()
public T extends Annotation T getAnnotation(ClassT annotationClass)
//获取方法参数的注解信息
public Annotation[][] getParameterAnnotations() 创建对象和构造方法 Class有一个方法可以用它来创建对象 public T newInstance() throws InstantiationException, IllegalAccessException 它会调用类的默认构造方法(即无参public构造方法)如果类没有该构造方法会抛出异常InstantiationException。看个简单示例 MapString,Integer map HashMap.class.newInstance();
map.put(hello, 123); 很多利用反射的库和框架都默认假定类有无参public构造方法所以当类利用这些库和框架时要记住提供一个。 newInstance只能使用默认构造方法Class还有一些方法可以获取所有的构造方法 //获取所有的public构造方法返回值可能为长度为0的空数组
public Constructor?[] getConstructors()
//获取所有的构造方法包括非public的
public Constructor?[] getDeclaredConstructors()
//获取指定参数类型的public构造方法没找到抛出异常NoSuchMethodException
public ConstructorT getConstructor(Class?... parameterTypes)
//获取指定参数类型的构造方法包括非public的没找到抛出异常NoSuchMethodException
public ConstructorT getDeclaredConstructor(Class?... parameterTypes) 类Constructor表示构造方法通过它可以创建对象方法为 public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 比如 ConstructorStringBuilder contructor StringBuilder.class.getConstructor(new Class[]{int.class});
StringBuilder sb contructor.newInstance(100); 除了创建对象Constructor还有很多方法可以获取关于构造方法的很多信息比如 //获取参数的类型信息
public Class?[] getParameterTypes()
//构造方法的修饰符返回值可通过Modifier类进行解读
public int getModifiers()
//构造方法的注解信息
public Annotation[] getDeclaredAnnotations()
public T extends Annotation T getAnnotation(ClassT annotationClass)
//构造方法中参数的注解信息
public Annotation[][] getParameterAnnotations() 类型检查和转换 我们在16节介绍过instanceof关键字它可以用来判断变量指向的实际对象类型instanceof后面的类型是在代码中确定的如果要检查的类型是动态的可以使用Class类的如下方法 public native boolean isInstance(Object obj) 也就是说如下代码 if(list instanceof ArrayList){System.out.println(array list);
} 和下面代码的输出是相同的 Class cls Class.forName(java.util.ArrayList);
if(cls.isInstance(list)){System.out.println(array list);
} 除了判断类型在程序中也往往需要进行强制类型转换比如 List list ..
if(list instanceof ArrayList){ArrayList arrList (ArrayList)list;
} 在这段代码中强制转换到的类型是在写代码时就知道的如果是动态的可以使用Class的如下方法 public T cast(Object obj) 比如 public static T T toType(Object obj, ClassT 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) 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() 需要说明下匿名内部类、成员类与本地类的区别本地类是指在方法内部定义的非匿名内部类比如如下代码 public static void localClass(){class MyLocal {}Runnable r new Runnable() {Overridepublic void run(){}};System.out.println(MyLocal.class.isLocalClass());System.out.println(r.getClass().isLocalClass());
} MyLocal定义在localClass方法内部就是一个本地类r的对象所属的类是一个匿名类但不是本地类。 成员类也是内部类定义在类内部、方法外部它不是匿名类也不是本地类。 类的声明信息 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(ClassA annotationClass)
public boolean isAnnotationPresent(Class? extends Annotation annotationClass) 内部类 关于内部类Class有一些专门的方法比如 //获取所有的public的内部类和接口包括从父类继承得到的
public Class?[] getClasses()
//获取自己声明的所有的内部类和接口
public Class?[] getDeclaredClasses()
//如果当前Class为内部类获取声明该类的最外部的Class对象
public Class? getDeclaringClass()
//如果当前Class为内部类获取直接包含该类的类
public Class? getEnclosingClass()
//如果当前Class为本地类或匿名内部类返回包含它的方法
public Method getEnclosingMethod() 类的加载 Class有两个静态方法可以根据类名加载类 public static Class? forName(String className)
public static Class? forName(String name, boolean initialize, ClassLoader loader) ClassLoader表示类加载器后面章节我们会进一步介绍initialize表示加载后是否执行类的初始化代码(如static语句块)。第一个方法中没有传这些参数相当于调用 Class.forName(className, true, currentLoader) currentLoader表示加载当前类的ClassLoader。 这里className与Class.getName的返回值是一致的比如对于String数组 String name [Ljava.lang.String;;
Class cls Class.forName(name);
System.out.println(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);
} 反射与数组 对于数组类型有一个专门的方法可以获取它的元素类型 public native Class? getComponentType() 比如 String[] arr new String[]{};
System.out.println(arr.getClass().getComponentType()); 输出为 class java.lang.String java.lang.reflect包中有一个针对数组的专门的类Array注意不是java.util中的Arrays)提供了对于数组的一些反射支持以便于统一处理多种类型的数组主要方法有 //创建指定元素类型、指定长度的数组
public static Object newInstance(Class? componentType, int length)
//创建多维数组
public static Object newInstance(Class? componentType, int... dimensions)
//获取数组array指定的索引位置index处的值
public static native Object get(Object array, int index)
//修改数组array指定的索引位置index处的值为value
public static native void set(Object array, int index, Object value)
//返回数组的长度
public static native int getLength(Object array) 需要注意的是在Array类中数组是用Object而非Object[]表示的这是为什么呢这是为了方便处理多种类型的数组int[]String[]都不能与Object[]相互转换但可以与Object相互转换比如 int[] intArr (int[])Array.newInstance(int.class, 10);
String[] strArr (String[])Array.newInstance(String.class, 10); 除了以Object类型操作数组元素外Array也支持以各种基本类型操作数组元素如 public static native double getDouble(Object array, int index)
public static native void setDouble(Object array, int index, double d)
public static native void setLong(Object array, int index, long l)
public static native long getLong(Object array, int index) 反射与枚举 枚举类型也有一个专门方法可以获取所有的枚举常量 public T[] getEnumConstants() 应用示例 介绍了Class的这么多方法有什么用呢我们看个简单的示例利用反射实现一个简单的通用序列化/反序列化类SimpleMapper它提供两个静态方法 public static String toString(Object obj)
public static Object fromString(String str) toString将对象obj转换为字符串fromString将字符串转换为对象。为简单起见我们只支持最简单的类即有默认构造方法成员类型只有基本类型、包装类或String。另外序列化的格式也很简单第一行为类的名称后面每行表示一个字段用字符分隔表示字段名称和字符串形式的值。SimpleMapper可以这么用 public class SimpleMapperDemo {static class Student {String name;int age;Double score;public Student() {}public Student(String name, int age, Double score) {super();this.name name;this.age age;this.score score;}Overridepublic String toString() {return Student [name name , age age , score score ];}}public static void main(String[] args) {Student zhangsan new Student(张三, 18, 89d);String str SimpleMapper.toString(zhangsan);Student zhangsan2 (Student) SimpleMapper.fromString(str);System.out.println(zhangsan2);}
} 代码先调用toString方法将对象转换为了String然后调用fromString方法将字符串转换为了Student新对象的值与原对象是一样的输出如下所示 Student [name张三, age18, score89.0] 我们来看SimpleMapper的示例实现(主要用于演示原理在生产中谨慎使用)toString的代码为 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);}
} fromString的代码为 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);}
} 它调用了setFieldValue方法对字段设置值其代码为 private static void setFieldValue(Field f, Object obj, String value) throws Exception {Class? type f.getType();if (type int.class) {f.setInt(obj, Integer.parseInt(value));} else if (type byte.class) {f.setByte(obj, Byte.parseByte(value));} else if (type short.class) {f.setShort(obj, Short.parseShort(value));} else if (type long.class) {f.setLong(obj, Long.parseLong(value));} else if (type float.class) {f.setFloat(obj, Float.parseFloat(value));} else if (type double.class) {f.setDouble(obj, Double.parseDouble(value));} else if (type char.class) {f.setChar(obj, value.charAt(0));} else if (type boolean.class) {f.setBoolean(obj, Boolean.parseBoolean(value));} else if (type String.class) {f.set(obj, value);} else {Constructor? ctor type.getConstructor(new Class[] { String.class });f.set(obj, ctor.newInstance(value));}
} setFieldValue根据字段的类型将字符串形式的值转换为了对应类型的值对于基本类型和String以外的类型它假定该类型有一个以String类型为参数的构造方法。 反射与泛型 在介绍泛型的时候我们提到泛型参数在运行时会被擦除这里我们需要补充一下在类信息Class中依然有关于泛型的一些信息可以通过反射得到泛型涉及到一些更多的方法和类上面的介绍中进行了忽略这里简要补充下。 Class有如下方法可以获取类的泛型参数信息 public TypeVariableClassT[] getTypeParameters() Field有如下方法 public Type getGenericType() Method有如下方法 public Type getGenericReturnType()
public Type[] getGenericParameterTypes()
public Type[] getGenericExceptionTypes() Constructor有如下方法 public Type[] getGenericParameterTypes() Type是一个接口Class实现了TypeType的其他子接口还有 TypeVariable类型参数可以有上界比如T extends NumberParameterizedType参数化的类型有原始类型和具体的类型参数比如ListString WildcardType通配符类型比如?, ? extends Number, ? super Integer我们看一个简单的示例 public class GenericDemo {static class GenericTestU extends ComparableU, V {U u;V v;ListString list;public U test(List? extends Number numbers) {return null;}}public static void main(String[] args) throws Exception {Class? cls GenericTest.class;// 类的类型参数for (TypeVariable t : cls.getTypeParameters()) {System.out.println(t.getName() extends Arrays.toString(t.getBounds()));}// 字段 - 泛型类型Field fu cls.getDeclaredField(u);System.out.println(fu.getGenericType());// 字段 - 参数化的类型Field flist cls.getDeclaredField(list);Type listType flist.getGenericType();if (listType instanceof ParameterizedType) {ParameterizedType pType (ParameterizedType) listType;System.out.println(raw type: pType.getRawType() ,type arguments: Arrays.toString(pType.getActualTypeArguments()));}// 方法的泛型参数Method m cls.getMethod(test, new Class[] { List.class });for (Type t : m.getGenericParameterTypes()) {System.out.println(t);}}
} 程序的输出为 U extends [java.lang.ComparableU]
V extends [class java.lang.Object]
U
raw type: interface java.util.List,type arguments:[class java.lang.String]
java.util.List? extends java.lang.Number 代码比较简单我们就不赘述了。 慎用反射 反射虽然是灵活的但一般情况下并不是我们优先建议的主要原因是 反射更容易出现运行时错误使用显式的类和接口编译器能帮我们做类型检查减少错误但使用反射类型是运行时才知道的编译器无能为力反射的性能要低一些在访问字段、调用方法前反射先要查找对应的Field/Method性能要慢一些 简单的说如果能用接口实现同样的灵活性就不要使用反射。 小结 本节介绍了Java中反射相关的主要类和方法通过入口类Class可以访问类的各种信息如字段、方法、构造方法、父类、接口、泛型信息等也可以创建和操作对象调用方法等利用这些方法可以编写通用的、动态灵活的程序本节演示了一个简单的通用序列化/反序列化类SimpleMapper。反射虽然是灵活通用的但它更容易出现运行时错误所以能用接口代替的时候应该尽量使用接口。 本节介绍的很多类如Class/Field/Method/Constructor都可以有注解注解到底是什么呢 (与其他章节一样本节所有代码位于 https://github.com/swiftma/program-logic位于包shuo.laoma.dynamic.c84下) ---------------- 未完待续查看最新文章敬请关注微信公众号“老马说编程”(扫描下方二维码)从入门到高级深入浅出老马和你一起探索Java编程及计算机技术的本质。用心原创保留所有版权。