Java编程的逻辑 (84) - 反射

本系列文章经补充和完善,已修订整理成书《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,注意不是小写的class,class是定义类的关键字,所有类的根父类Object有一个方法,可以获取对象的Class对象:

public final native Class<?> getClass()

Class是一个泛型类,有一个类型参数,getClass()并不知道具体的类型,所以返回Class<?>。

获取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;

void作为特殊的返回类型,也有对应的Class:

Class<Void> 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
}Class<Size> 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表示int,L表示类或接口,其他类型与字符的对应关系为: 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的检查机制。

看段简单的示例代码:

List<String> 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(Class<T> 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被忽略,可以为null,args可以为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(Class<T> annotationClass)
//获取方法参数的注解信息
public Annotation[][] getParameterAnnotations() 

创建对象和构造方法

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

public T newInstance() throws InstantiationException, IllegalAccessException

它会调用类的默认构造方法(即无参public构造方法),如果类没有该构造方法,会抛出异常InstantiationException。看个简单示例:

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

很多利用反射的库和框架都默认假定类有无参public构造方法,所以当类利用这些库和框架时要记住提供一个。

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

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

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

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

比如:

Constructor<StringBuilder> 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(Class<T> 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, 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)

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(Class<A> 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=张三, age=18, score=89.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 TypeVariable<Class<T>>[] getTypeParameters()

Field有如下方法:

public Type getGenericType()

Method有如下方法:

public Type getGenericReturnType()
public Type[] getGenericParameterTypes()
public Type[] getGenericExceptionTypes()

Constructor有如下方法:

public Type[] getGenericParameterTypes() 

Type是一个接口,Class实现了Type,Type的其他子接口还有:

  • TypeVariable:类型参数,可以有上界,比如:T extends Number
  • ParameterizedType:参数化的类型,有原始类型和具体的类型参数,比如:List<String> 
  • WildcardType:通配符类型,比如:?, ? extends Number, ? super Integer

我们看一个简单的示例:

public class GenericDemo {static class GenericTest<U extends Comparable<U>, V> {U u;V v;List<String> 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.Comparable<U>]
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编程及计算机技术的本质。用心原创,保留所有版权。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/258876.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

灰度图像的8位平面分解

所谓灰度图像&#xff0c;即指8位256颜色的图像。将图像的每一位分别取出来&#xff0c;我们就可以将一幅图像分解开来&#xff0c;形成8幅图像。下面我们分别介绍使用matlab分解图像与使用halcon/c分解图像的方法。 matlab8位分解 clc; clear all; A imread(lena.tif); % 显…

css 横线_atom.css正式发布,从此跟CSS框架说拜拜。

atom.css 大家好&#xff0c;我写了一个css库atom.css&#xff0c;蛮好用的&#xff0c;所以忍不住分享给大家。(https://github.com/MatrixAge/atom.css)起因写HTML几年了&#xff0c;再到如今的JSX&#xff0c;最大的感受不是枯燥&#xff0c;而是眼花。写样式的时候&#xf…

halcon模板匹配学习(一) Matching 初印象

什么是模板匹配呢&#xff1f;简单而言&#xff0c;就是在图像中寻找目标图像&#xff08;模板&#xff09;&#xff0c;或者说&#xff0c;就是在图像中寻找与模板图像相似部分的一种图像处理技术。依赖于选择的方法不同&#xff0c;模板匹配可以处理各种情形下的变换&#xf…

第五章 面向方面编程___AOP入门

上一篇讲了 AOP 和 OOP 的区别&#xff0c;这一次我们开始入门 AOP 。实现面向方面编程的技术&#xff0c;主要分为两大类&#xff1a; 一是 采用动态代理技术&#xff0c;利用截取消息的方式&#xff0c;对该消息进行装饰&#xff0c;以取代原有对象行为的执行&#xff1b; 二…

java将xml中的标签名称转为小写_深入学习Java Web(七): JSTL标签库

本文转自与博客园一杯凉茶的博客.在之前我们学过在JSP页面上为了不使用脚本&#xff0c;所以我们有了JSP内置的行为、行为只能提供一小部分的功能&#xff0c;大多数的时候还是会用java脚本&#xff0c;接着就使用了EL表达式&#xff0c;基本上EL表达式看似能满足我们的要求&am…

halcon模板匹配学习(二) 准备模板

如下&#xff0c;我们将介绍匹配的第一个操作&#xff1a;准备模板 初始时刻&#xff0c;我们准备好参考图像&#xff0c;并对其做一定的处理&#xff0c;然后我们需要从参考图像中导出模板&#xff0c;也就是将参考图像裁剪成所谓的模板图像。获取模板图像可以通过设置ROI来完…

揭秘继承技术之虚函数

虚函数 调用虚函数时函数行为将根据对象所属类的不同而变化。 父类指针或引用指向子类对象时&#xff0c;可访问子类重写方法&#xff08; virtual函数&#xff09;但无法访问在父类中没有定义的子类方法和数据成员。 #include <iostream>using namespace std;class Supe…

js 数组移除_2020前端面试--常见的js面试题

&#xff08;答案持续更新...&#xff09; 1.简述同步和异步的区别js是一门单线程语言&#xff0c;所谓"单线程"&#xff0c;就是指一次只能完成一件任务。如果有多个任务&#xff0c;就必须排队&#xff0c;前面一个任务完成&#xff0c;再执行后面一个任务&#xf…

spring-自动加载配置文件\使用属性文件注入

在上一篇jsf环境搭建的基础上 , 加入spring框架 , 先看下目录结构 src/main/resources 这个source folder 放置web项目所需的主要配置,打包时,会自动打包到WEB-INF下 首先看下pom.xml,需要引入一些依赖项: 1 <project xmlns"http://maven.apache.org/POM/4.0.0" x…

pygame碰撞检测

最近在学Pygame,花一段时间做了一个异常简陋版的"打砖块". 这次重点说一下困扰我比较长时间的碰撞检测(个人太菜..). 按照网上教程比较普遍的方法(也可能是我没看见别的),碰撞检测依次计算移动物体与被碰撞物体各个边之间坐标是否相交.例如下列代码,检测小球与窗口的…

2017-5-4 进程

进程&#xff1a;一个应用程序就是一个进程开启某个进程Process.Start("文件缩写名");通过绝对路径开启某个进程Process p new Process();p.StartInfo new ProcessStartInfo("要打开的程序绝对路径");p.Start(); 获取全部开启的进程&#xff1a;Process.…

c++分治法求最大最小值实现_程序员:算法导论,分治法、归并排序,伪代码和Java实现...

分治法我们首先先介绍分治法。分治法的思想&#xff1a;将原问题分解为几个规模较小但类似于原问题的子问题&#xff0c;递归地求解这些子问题&#xff0c;然后在合并这些子问题的解来解决原问题的解。还是拿扑克牌举例子&#xff0c;假设桌上有两堆牌面朝上的牌(牌面朝上&…

halcon相关的链接

论坛、培训 halcon学习网&#xff1a;http://www.ihalcon.com/鸟叔机器视觉&#xff1a;http://bbs.szvbt.com/forum.php 博客 韩兆新的博客园majunfuLife and Codingzhaojun的博客風韻無聲骑蚂蚁上高速的博客小马_xiaoLV2小新识图程序园-程序员的世界章柯渊的博客 注&…

python opencv图像处理程序_Python-OpenCV学习(四):基本图像处理

转载请注明出处&#xff1a;danscarlett的博客园 参考资料&#xff1a; 目录&#xff1a; 读取 imread 显示 imshow 存储 imwrite 缩放 resize 加边框 copyMakeBorder 裁剪 img[x_start:x_end,y_start:y_end] 1.图像读取&#xff1a; cv2.imread(fileName,flagsNone) 函数功能&…

分针网——怎么轻松学习JavaScript

js给初学者的印象总是那么的“杂而乱”&#xff0c;相信很多初学者都在找轻松学习js的途径。我试着总结自己学习多年js的经验&#xff0c;希望能给后来的学习者探索出一条“轻松学习js之路”。js给人那种感觉的原因多半是因为它如下的特点&#xff1a;A&#xff1a;本身知识很抽…

python时间序列分析航空旅人_用python做时间序列预测一:初识概念

利用时间序列预测方法&#xff0c;我们可以基于历史的情况来预测未来的情况。比如共享单车每日租车数&#xff0c;食堂每日就餐人数等等&#xff0c;都是基于各自历史的情况来预测的。 什么是时间序列&#xff1f; 时间序列&#xff0c;是指同一个变量在连续且固定的时间间隔上…

[Logstash-input-redis] 使用详解

2019独角兽企业重金招聘Python工程师标准>>> Redis插件参数配置详解 工作流程 logstash启动redis插件redis插件获取参数&#xff0c;进行校验工作判断监听模式(list,channel,pattern_channel等)&#xff0c;根据不同的监听模式创建监听任务创建redis实例&#xff0c…

雅可比旋转求解对称二维矩阵的特征值和特征向量

问题描述&#xff1a; 给定一个矩阵&#xff0c;如下&#xff1a; A[a11a21a12a22]A=\begin{bmatrix} a_{11}&a_{12}\\ a_{21}& a_{22} \end{bmatrix} 其中满足a12a21.也就是所谓的 对称矩阵。那么如何求解此矩阵的特征值以及特征向量呢&#xff1f;这里我们要用到 …

python画图数据的平均值怎么算的_Python气象数据处理与绘图(2):常用数据计算方法...

对于气象绘图来讲&#xff0c;第一步是对数据的处理&#xff0c;通过各类公式&#xff0c;或者统计方法将原始数据处理为目标数据。 按照气象统计课程的内容&#xff0c;我给出了一些常用到的统计方法的对应函数&#xff1a; import numpy as np 平均值 在计算气候态&#xff0…

Linux下nginx安装与配置

部分Linux发布版的默认安装已经集成了nginx&#xff0c;查看方法ls /usr/local&#xff0c;若已有nginx文件夹说明已集成。nginx依赖库pcre与zlib&#xff0c;且pcre依赖于gcc与gcc-c&#xff0c;因此安装步骤为&#xff1a;安装gcc与gcc-c库安装pcre库安装zlib库安装nginx详细…