1. Java 中的动态特性有哪些
- 反射、注解、动态代理、类加载器等
2. Java 中的这些动态特性的作用和使用场景有
- 作用:利用 Java 的动态特性,可以优雅地实现一些灵活通用的功能,它们经常用于各种框架、库和系统程序中
- 场景
- Jackson,利用反射和注解实现了通用的序列化机制
- 有多种库(如 Spring MVC、Jersey)用于处理 Web 请求,利用反射和注解,能方便地将用户的请求参数和内容转换为 Java 对象,将 Java 对象转变为响应内容
- 有多种库(如 Spring、Guice)利用 Java 的动态特性实现了对象管理容器,方便程序员管理对象的生命周期以及其中复杂的依赖关系
- 应用服务器(如 Tomcat)利用类加载器实现不同应用之间的隔离,JSP 技术利用类加载器实现修改代码不用重启就能生效的特性
- 面向切面的编程 AOP(Aspect Oriented Programming) 将编程中通用的关注点(如日志记录、安全检查等)与业务的主体逻辑相分离,减少冗余代码,提高程序的可维护性,AOP 需要依赖上面的这些特性来实现
3. 怎样理解反射
- Java 是强类型语言,一般在操作数据的时候都是要依赖于数据类型的,编译器也是根据类型进行代码的检查编译的
- 反射不一样,反射是在运行时,而非编译时,动态获取类型的信息,比如接口信息、成员信息、方法信息、构造方法信息等,根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等
- 反射的入口是名称为
Class
的类
4. 怎样理解 Class
类?不同数据类型的 Class
是怎样的
- 每个已加载的类在内存都有一份类信息,每个对象都有指向它所属类信息的引用
- Java 中,类信息对应的类就是
java.lang.Class
。注意不是小写的class
,class
是定义类的关键字 - 所有类的根父类
Object
有一个方法,可以获取对象的Class
对象:public final native Class<?> getClass()
,Class
是一个泛型类
5. 除了实例对象方法外,还有哪些方法可以获取 Class
对象
获取
Class
对象不一定需要实例对象,如果在写程序时就知道类名,可以使用类名.class获取Class
对象,比如:Class<Date> cls = Date.class;
接口也有
Class
对象,且这种方式对于接口也是使用的,比如:Class<Comparable> cls = Comparable.class;
枚举也有对应的
Class
,比如1
2
3
4
5enum Size {
SMALL, MEDIUM, BIG
}
Class<Size> cls = Size.class;
6. 基本数据类型怎样获取 Class
对象
基本类型没有
getClass()
方法,但也都有对应的Class
对象,类型参数为对应的包装类型。比如1
2
3
4Class<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
对象,每个维度都有一个,即一维数组有一个,二维数组有一个不同的类型。比如1
2
3
4
5
6String[] strArr = new String[10];
int[][] twoDimArr = new int[3][2];
int[] oneDimArr = new int[10];
Class extends String[][]> strArrCls = strArr.getClass(); //TODO: 类型参数有待商榷
Class extends int[][]> twoDimArrCls = twoDimArr.getClass();
Class extends int[]> oneDimArrCls = oneDimArr.getClass();Class
有一个静态方法forName()
,可以根据类名直接加载Class
,获取Class
对象,比如1
2
3
4
5
6try {
Class<?> cls = Class.forName("java.util.HashMap"); //forName() 方法可能抛出 ClassNotFoundException 异常
System.out.println(cls.getName());
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
7. Class
中可以获取与名称有关的信息的方法有哪些
public String getName()
:返回的是 Java 内部使用的真正的名称public String getSimpleName()
:返回的名称不带包信息public String getCanonicalName()
:返回的名称更友好public Package getPackage()
:返回的是包信息
8. 填写下面表格并解释说明
Class 对象 | getName() | getSimpleName() | getCanonicalName() | getPackage() |
---|---|---|---|---|
int.class | int | int | int | null |
int[].class | [I | int[] | int[] | null |
int[][].class | [[I | int[] | int[] | null |
String.class | java.lang.String | String | java.lang.String | java.lang |
String[].class | [Ljava.lang.String; | String[] | java.lang.String[] | null |
HashMap.class | java.util.HashMap | HashMap | java.util.HashMap | java.util |
Map.Entry.class | java.util.Map$Entry | Entry | java.util.Map.Entry | java.util |
- 需要说明的是数组类型的
getName()
返回值,它使用前缀[
表示数组,有几个[
表示是几维数组 - 数组的类型用一个字符表示,
I
表示int
,L
表示类或接口,其他类型与字符的对应关系为:boolean(Z)
、byte(B)
、char(C)
、double(D)
、float(F)
、long(J)
、short(S)
- 对于引用类型的数组,注意最后有一个分号
;
9. Class
中获取字段信息的方法有
- 类中定义的静态和实例变量都被称为字段,用类
Field
表示,位于包java.lang.reflect
下public Field[] getFields()
:返回所有的public
字段,包括其父类的,如果没有字段,返回空数组public Field[] getDeclaredFields()
:返回本类声明的所有字段,包括非public
的,但不包括父类的public Field getField(String name)
:返回本类或父类中指定名称的public
字段,找不到抛出NoSuchFieldException
异常public Field getDeclaredField(String name)
:返回本类中声明的指定名称的字段,找不到抛出NoSuchFieldException
异常
10. Field
类中和字段有关的方法有哪些
public String getName()
:获取字段的名称public boolean isAccessible()
:判断当前程序是否有该字段的访问权限public void setAccessible(boolean flag)
:flag
设为true
表示忽略 Java 的访问检查机制,以允许读写非public
的字段- 对于
private
字段,直接调用get()/set()
会抛出IllegalAccessException
异常,应该先调用setAccessible(true)
以关闭 Java 的检查机制
- 对于
public Object get(Object obj)
:获取指定对象obj
中该字段的值public void set(Object obj, Object value)
:将指定对象obj
中该字段的值设为value
- 在
get()/set()
方法中,对于静态变量,obj
被忽略,可以为null
- 如果字段值为基本类型,
get()/set()
会自动在基本类型与对应的包装类型间进行转换
- 在
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();
11. Field
类的 getModifiers()
方法 Demo
Demo
1
2
3
4
5
6
7
8
9
10
11
12//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));输出
1
2
3
4
5public static final
isPublic: true
isStatic: true
isFinal: true
isVolatile: false
12. Class
中获取方法信息的方法有
- 类中定义的静态和实例方法都被称为方法,用类
Method
表示public Method[] getMethods()
:返回所有的public
方法,包括其父类的,如果没有方法,返回空数组public Method[] getDeclaredMethods()
:返回本类声明的所有方法,包括非public
的,但不包括父类的public Method getMethod(String name, Class<?> ... parameterTypes)
:返回本类或父类中指定名称和参数类型的public
方法,找不到的话抛出NoSuchMethodException
异常public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
:返回本类中声明的指定名称和参数类型的方法,找不到抛出NoSuchMethodException
异常
13. Method
的基本方法有
public String getName()
:获取方法的名称public void setAccesible(boolean flag)
:flag
设为true
表示忽略 Java 的访问检查机制,以允许调用非public
的方法public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
:在指定对象obj
上调用Method
代表的方法,传递的参数列表为args
- 如果
Method
为静态方法,obj
被忽略,可以为null
,args
可以为null
,也可以为一个空的数组,方法调用的返回值被包装为Object
返回 - 如方法调用抛出异常,异常被包装为
InvocationTargetException
异常重新抛出,可以通过getCause()
方法得到原异常
- 如果
Method
还有很多方法,可以获取其修饰符、参数、返回值、注解等信息
14. Class
中有关创建对象和构造方法的方法有
public T newInstance() throws InstantiationException, IllegalAccessException
创建对象,会调用类的默认构造方法(即无参
public
构造方法),如果类没有该构造方法,会抛出InstantiationException
异常Demo
1
2Map<String, Integer> map = HashMap.class.newInstance();
map.put("hello", 123);newInstance()
只能使用默认构造方法
Class
还有一些方法,可以获取所有的构造方法public Constructor<?>[] getConstructors()
:获取所有的public
构造方法,返回值可能为长度为 0 的空数组public Constructor<?>[] getDeclaredConstructors()
:获取所有的构造方法,包括非public
的public Constructor<T> getConstructor(Class<?>... parameterType)
:获取指定参数类型的public
构造方法,没找到抛出NoSuchMethodException
异常public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
:获取指定参数类型的构造方法,包括非public
的,没找到抛出NoSuchMethodException
异常
类
Constructor
表示构造方法,通过它可以创建对象,方法为:public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
Demo
1
2Constructor<StringBuilder> constructor = StringBuilder.class.getConstructor(new Class[] {int.class});
StringBuilder sb = constrcutor.newInstance(100);
除了创建对象,
Constructor
类还有很多方法,可以获取关于构造方法的很多信息,包括参数、修饰符、注解等
15. Class
类中关于类型检查和转换的方法有
instanceof
关键字的作用:判断变量指向的实际对象类型instanceof
后面的类型是在代码中确定的,如果要检查的类型是动态的,可以使用Class
类的本地方法:public native boolean isInstance(Object obj)
下面两段代码的输出是相同的
1
2
3if(list instanceof ArrayList) {
System.out.println("array list");
}1
2
3
4
5
Class cls = Class.forName("java.util.ArrayList");
if(cls.isInstance(list)) {
System.out.println("array list");
}之前在强制类型转换时,强转的类型是在写代码的时候就知道的,如果是动态的,可以使用
Class
类的cast()
方法:public T cast(Object obj)
isInstance()/cast()
描述的都是对象和类之间的关系。Class
还有一个方法,可以判断Class
之间的关系:public native boolean isAssignableFrom(Class<?> cls)
:检测参数类型cls
能否赋给当前Class
类型的变量Object.class.isAssignableFrom(String.class); //true
String.class.isAssignableFrom(String.class); //true
List.class.isAssignableFrom(ArrayList.class); //true
16. 对于 Class
的类型信息,下面说法:Class
代表的类型既可以是普通的类,也可以是内部类,还可以是基本类型、数组等。是否正确
- 正确
17. 对于一个给定的 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()
:是否是本地类,本地类定义在方法内,不是匿名类
18. Class
类中有关类的声明信息的方法有
public native int getModifier()
:获取修饰符,返回值可通过Modifier
类进行解读public native Class<? super T> getSuperClass()
:获取父类,如果为Object
,父类为null
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)
:检查指定类型的注解,包括继承得到的
19. 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
数组1
2
3String name = "java.lang.String;";
Class cls = Class.forName(name);
System.out.println(cls == String[].class);
需要注意的是,基本类型不支持
forName()
方法。即,如下写法:Class.forName("int");
会抛出ClassNotFoundException
异常
20. 接上题,如何根据原始类型的字符串构造 Class
对象呢
可以对
Class.forName()
进行一下包装,比如1
2
3
4
5
6
7public 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
,它也不会执行类的初始化
- 参数
21. Class
类中有关反射与数组的方法有
对于数组类型,有一个专门的方法,可以获取它的元素类型:
public native Class<?> getComponentType()
比如
1
2String[] 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)
:创建多维数组public static native Object get(Object array, int index)
:获取数组array
指定的索引位置index
处的值public static native void set(Object array, int index, Object value)
:修改数组array
指定的索引位置index
处的值为value
public static native int getLength(Object array)
:返回数组的长度
22. 在 Array
类中,数组是用 Object
而非 Object[]
表示的,为什么
这是为了方便处理多种类型的数组,
int[]
、String[]
都不能与Object[]
相互转换,但可以与Object
相互转换,比如1
2int[] 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);
23. Class
类中有关反射与枚举的方法有
public T[] getEnumConstants()
:获取所有的枚举常量