建议的采用顺序是 List 中泛型顺序依次为 T,?,Object
(1),List 是确定的某一个类型
List 表示的是 List 集合中的元素都为 T 类型, 具体类型在运行期决定; List<?>表示的是任意类型, 与 List 类似, 而 List 则表示 List 集合中的所有元素为 Object 类型, 因为 Object 是所有类的父类, 所以 List 也可以容纳所有的类类型, 从这一字面意义上分析, List 更符合习惯: 编码者知道它是某一个类型, 只是在运行期才确定而已.
(2)List 可以进行读写操作
List 可以进行诸如 add,remove 等操作, 因为它的类型是固定的 T 类型, 在编码期不需要进行任何的转型操作.
List 是只读类型的, 不能进行增加, 修改操作, 因为编译器不知道 List 中容纳的是什么类型的元素, 也就无法校验类型是否安全了, 而且 List<?>读取出的元素都是 Object 类型的, 需要主动转型, 所以它经常用于泛型方法的返回值. 注意 List<?>虽然无法增加, 修改元素, 但是却可以删除元素, 比如执行 remove,clear 等方法, 那是因为它的删除动作与泛型类型无关.
List 也可以读写操作, 但是它执行写入操作时需要向上转型(Up cast), 在读取数据的时候需要向下转型, 而此时已经失去了泛型存在的意义了.
严格限定泛型类型采用多重界限
List 接口的 toArray 方法可以把一个集合转化为数组, 但是使用不方便, toArray()方法返回的是一个 Object 数组, 所以需要自行转变. 当一个泛型类 (特别是泛型集合) 转变为泛型数组时, 泛型数组的真实类型不能是泛型的父类型(比如顶层类 Object), 只能是泛型类型的子类型(当然包括自身类型), 否则就会出现类型转换异常. 通过反射类 Array 声明了一个 T 类型的数组, 由于我们无法在运行期获得泛型类型的参数, 因此就需要调用者主动传入 T 参数类型. List 转数组:
- public static <T> T[] toArray(List<T> list,Class<T> tClass) {
- // 声明并初始化一个 T 类型的数组
- T[] t = (T[])Array.newInstance(tClass, list.size());
- for (int i = 0, n = list.size(); i <n; i++) {
- t[i] = list.get(i);
- }
- return t;
- }
注意 Class 类的特殊性
class 类的特殊性:
无构造函数: Java 中的类一般都有构造函数, 用于创建实例对象, 但是 Class 类却没有构造函数, 不能实例化, Class 对象是在加载类时由 Java 虚拟机通过调用类加载器中的 difineClass 方法自动构造的. 可以描述基本类型: 虽然 8 个基本类型在 JVM 中并不是一个对象, 它们一般存在于栈内存中, 但是 Class 类仍然可以描述它们, 例如可以使用 int.class 表示 int 类型的类对象. 其对象都是单例模式: 一个 Class 的实例对象描述一个类, 并且只描述一个类, 反过来也成立. 一个类只有一个 Class 实例对象
获得 Class 对象的三种途径:
类属性方式: 如 String.class 对象的 getClass 方法, 如 new String().getClass() forName 方法加载: 如 Class.forName("java.lang.String") 获得了 Class 对象后, 就可以通过 getAnnotations()获得注解, 通过 getMethods()获得方法, 通过 getConstructors()获得构造函数等
适时选择 getDeclaredXXX 和 getXXX
getXXX 法获得的是所有 public 访问级别的方法, 包括从父类继承的方法, 而 getDeclaredXXX 获得的是自身类的方法, 包括公用的 (public) 方法, 私有 (private) 方法, 而且不受限于访问权限. 如果需要列出所有继承自父类的方法, 该如何实现呢? 简单, 先获得父类, 然后使用 getDeclaredMethods, 之后持续递归即可.
反射访问属性或方法时将 Accessible 设置为 true
通过反射执行一个方法的过程如下:
获取一个方法对象;
然后根据 isAccessible 返回值确定是否能够执行, 如果返回值为 false 则需要调用 setAccessible(true);
最后再调用 invoke 执行方法
- Method method= ...;
- // 检查是否可以访问
- if(!method.isAccessible()){
- method.setAccessible(true);
- }
- // 执行方法
- method.invoke(obj, args);
AccessibleObject 源码:
- public class AccessibleObject implements AnnotatedElement {
- // 定义反射的默认操作权限 suppressAccessChecks
- static final private java.security.Permission ACCESS_PERMISSION =
- new ReflectPermission("suppressAccessChecks");
- // 是否重置了安全检查, 默认为 false
- boolean override;
- // 构造函数
- protected AccessibleObject() {}
- // 是否可以快速获取, 默认是不能
- public boolean isAccessible() {
- return override;
- }
- }
AccessibleObject 是 Filed,Method,Constructor 的父类, 决定其是否可以快速访问而不进行访问控制检查, 在 AccessibleObject 类中是以 override 变量保存该值的, 但是具体是否快速执行时在 Method 的 invoke 方法中决定的:
- public Object invoke(Object obj, Object... args)
- throws IllegalAccessException, IllegalArgumentException,
- InvocationTargetException
- {
- // 是否可以快速获取, 其值是父类 AccessibleObject 的 override 变量
- if (!override) {
- // 不能快速获取, 执行安全检查
- if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
- Class<?> caller = Reflection.getCallerClass(1);
- checkAccess(caller, clazz, obj, modifiers);
- }
- }
- MethodAccessor ma = methodAccessor; // read volatile
- if (ma == null) {
- ma = acquireMethodAccessor();
- }
- // 直接执行方法
- return ma.invoke(obj, args);
- }
Accessible 属性只是用来判断是否需要进行安全检查的, 如果不需要则直接执行, 这就可以大幅度的提升系统性能了(当然了, 取消了安全检查, 也可以运行 private 方法, 访问 private 属性的). 经过测试, 在大量的反射情况下, 设置 Accessible 为 true 可以提高性能 20 倍左右. Accessible 属性决定 Field 和 Constructor 是否受访问控制检查. 我们在设置 Field 或执行 Constructor 时, 务必要设置 Accessible 为 true.
使用 forName 动态加载类文件
动态加载 (Dynamic Loading) 是指在程序运行时加载需要的类库文件, 对 Java 程序来说, 一般情况下, 一个类文件在启动时或首次初始化时会被加载到内存中, 而反射则可以在运行时再决定是否需要加载一个类. 举个栗子:
- public class Client103 {
- public static void main(String[] args) throws ClassNotFoundException {
- // 动态加载
- Class.forName("com.study.advice103.Utils");
- }
- }
- class Utils{
- // 静态代码块
- static{
- System.out.println("Do Something.....");
- }
- }
结果: Do Something.....
forName 只是把一个类加载到内存中, 并不保证由此产生一个实例对象, 也不会执行任何方法, 之所以会初始化 static 代码, 那是由类加载机制所决定的, 而不是 forName 方法决定的. 也就是说, 如果没有 static 属性或 static 代码块, forName 就是加载类, 没有任何的执行行为.
动态加载不适合数组
数组是一个非常特殊的类, 虽然它是一个类, 但没有定义类类路径.
- String [] strs = new String[10];
- Class.forName("java.lang.String[]");
会产生 bug:
- Exception in thread "main" java.lang.ClassNotFoundException: java/lang/String[]
- at java.lang.Class.forName0(Native Method)
动态加载数组:
- // 动态创建数组
- String[] strs = (String[]) Array.newInstance(String.class, 8);
- // 创建一个多维数组
- int[][] ints = (int[][]) Array.newInstance(int.class, 2, 3);
来源: https://www.cnblogs.com/androidsuperman/p/9466862.html