编写高质量代码:改善Java程序的151个建议 --[98~105]

建议的采用顺序是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);

 

posted on 2018-08-13 11:02 西北野狼 阅读(...) 评论(...) 编辑 收藏