zoukankan      html  css  js  c++  java
  • 编写高质量代码:改善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);
  • 相关阅读:
    代理支持
    CGI
    SSI(服务器端嵌入)
    SSL/TLS 配置
    JSPs
    类加载机制
    JDBC 数据源
    安全管理
    Realm 配置
    Js将序列化成Json格式后日期(毫秒数)转成日期格式
  • 原文地址:https://www.cnblogs.com/androidsuperman/p/9466862.html
Copyright © 2011-2022 走看看