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

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

  1. 博友的 编写高质量代码 改善java程序的151个建议

    编写高质量代码 改善java程序的151个建议 http://www.cnblogs.com/selene/category/876189.html

  2. 编写高质量代码改善java程序的151个建议——导航开篇

    2014-05-16 09:08 by Jeff Li 前言 系列文章:[传送门] 下个星期度过这几天的奋战,会抓紧java的进阶学习.听过一句话,大哥说过,你一个月前的代码去看下,慘不忍睹是吧.确实 ...

  3. 编写高质量代码改善java程序的151个建议——[1-3]基础?亦是基础

    原创地址:   http://www.cnblogs.com/Alandre/  (泥沙砖瓦浆木匠),需要转载的,保留下! Thanks The reasonable man adapts himse ...

  4. 编写高质量代码:改善Java程序的151个建议 --[117~128]

    编写高质量代码:改善Java程序的151个建议 --[117~128] Thread 不推荐覆写start方法 先看下Thread源码: public synchronized void start( ...

  5. 编写高质量代码:改善Java程序的151个建议 --[106~117]

    编写高质量代码:改善Java程序的151个建议 --[106~117] 动态代理可以使代理模式更加灵活 interface Subject { // 定义一个方法 public void reques ...

  6. 编写高质量代码:改善Java程序的151个建议 --[78~92]

    编写高质量代码:改善Java程序的151个建议 --[78~92] HashMap中的hashCode应避免冲突 多线程使用Vector或HashTable Vector是ArrayList的多线程版 ...

  7. 编写高质量代码:改善Java程序的151个建议 --[65~78]

    编写高质量代码:改善Java程序的151个建议 --[65~78] 原始类型数组不能作为asList的输入参数,否则会引起程序逻辑混乱. public class Client65 { public ...

  8. 编写高质量代码:改善Java程序的151个建议 --[52~64]

    编写高质量代码:改善Java程序的151个建议 --[52~64] 推荐使用String直接量赋值 Java为了避免在一个系统中大量产生String对象(为什么会大量产生,因为String字符串是程序 ...

  9. 编写高质量代码:改善Java程序的151个建议 --[36~51]

    编写高质量代码:改善Java程序的151个建议 --[36~51] 工具类不可实例化 工具类的方法和属性都是静态的,不需要生成实例即可访 问,而且JDK也做了很好的处理,由于不希望被初始化,于是就设置 ...

随机推荐

  1. Mac 在terminal 上用命令打开sublime

    Step1. 安装Sublime Text编辑器 可直接到以下网址下载dmg安装文件: Sublime Text 3 Step2. 添加命令行别名 打开用户配置文件 vim ~/.bash_profi ...

  2. Arrays.copyOf() 和 System.arrayCopy()分析

    java数组的拷贝四种方法:for.clone.System.arraycopy.Arrays.copyof public class Test1 { public static void main( ...

  3. C++常用宏

    宏是由 #define 定义而来,在预处理阶段进行宏展开,它的格式是: #define N 2 + 2 // 仅仅是字符串替换 #define N (2 + 2) // 也是字符串 ,但是是(2 + ...

  4. 过滤数组中的空数组array_filter()

    手册查询array_filter()的使用细节时,看到了:If no callback is supplied, all entries of input equal to FALSE (see co ...

  5. 初识GetMapping(""),使用方法

    GetMapping("value = /SF/{x_num}")与GetMapping("/SF/{x_num}")通过POSTMAN获得的值一样. 注意:G ...

  6. php2

    session   //将用户的会话数据存储在服务端,通过 session_start()开启session,通过$_SESSION读写session session_start(); //开启ses ...

  7. Linux 下 解压zip文件出现乱码

    网上下载了一个文件,鼠标右键提取出来发现中文文件名全部乱码: 打开命令行  unzip -h  可以看到 -O 参数  制定编码解压: 比如: unzip -O CP936 xxx.zip

  8. 为WebRTC 应用部署Turn Server

    部署WebRTC 或 SIP p2p 方案时经常会遇到p2p 无法穿透的环境, 这时就是TunServer 的用武之地了. 这里我们使用turnserver-0.7.3 下载confuse依赖库 wg ...

  9. Nginx 热部署最版本

    L10 进入nginx里的sbin目录 拷贝原先的做备份 cp nginx nginx.old 然后将已经编译好的nginx二进制文件复制到sbin目录下并覆盖原有的二进制文件 kill -USR2 ...

  10. 前端 -- HTML内容

    HTML介绍 Wed服务本质 import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.listen(5 ...