java开发手册笔记

来源:互联网 发布:网络购物系统设计 编辑:程序博客网 时间:2024/06/06 00:42

概念

Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念

http://www.open-open.com/lib/view/open1450427478266.html

可变参数

适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。

public static int add(int x,int ...args){    int sum=x;    for(int i=0;i<args.length;i++){        sum+=args[i];    }    return sum;}

hashCode和equals

http://www.cnblogs.com/lulipro/p/5628750.html

悲观锁和乐观锁

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

Double-Checked Locking is Broken” Declaration

双重锁定的问题主要在,执行第四步时,有可能其他线程正在初始化对象执行第七步。创建对象的过程分为三步:分配内存、初始化对象、将instance指向内存地址。但是多线程的过程中,有可能第二步和第三步的位置互换,由于并不违反intra-thread semantics(允许那些在单线程内,不会改变单线程程序执行结果的重排序)。所以会造成第四步instance不是null,但是没有初始化对象,如果直接使用这个Instance,会造成错误。声明对象为volatile后,重排序的问题在多线程下会被禁止。

public class DoubleCheckedLocking {                      //1    private static Instance instance;                    //2    public static Instance getInstance() {               //3        if (instance == null) {                          //4:第一次检查            synchronized (DoubleCheckedLocking.class) {  //5:加锁                if (instance == null)                    //6:第二次检查                    instance = new Instance();           //7:问题的根源出在这里            }                                            //8        }                                                //9        return instance;                                 //10    }                                                    //11}                                                        //12     

参考:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
补充:
可见性:多个线程对一个共享变量进行操作时,如果线程1操作之后及时将工作线程中的副本更新到主内存,线程2及时将主内存中的变量复制到自己的工作内存中,那么这个共享变量对于线程2就是可见的,反之不可见。
原子性:代表代码不可分割,同一时刻只能一个线程进行操作,比如用synchronized修饰的代码段。
synchronized:线程解锁前,必须把共享变量的最新值刷新到主内存中。线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁需要是同一把锁)
volatile:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。(注意:使用该关键字的变量的写入操作不依赖其当前值,该变量没有包含在具有其他变量的不变式中:a

命名风格

中括号是数组类型的一部分,数组定义如下:String[] args;

接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。 正例:接口方法签名:void f(); 接口基础常量表示:String COMPANY = “alibaba”; 反例:接口方法定义:public abstract void f(); 说明:JDK8中接口允许有默认实现,那么这个default方法,是对所有实现类都有价值的默认实现。

常量定义

long或者Long初始赋值时,必须使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。 说明:Long a = 2l; 写的是数字的21,还是Long型的2?

代码格式

单行字符数限不超过 120 个,超出需要换行时 遵循如下原则: 1) 第二行相对一缩进 4个空格,从第三行开始不再继续缩进参考示例。 2) 运算符与下文一起换行。 3) 方法调用的点符号与下文一起换行。 4) 在多个参数超长,在逗号后换行。 5) 在括号前不要换行,见反例。

OOP规约

所有的覆写方法,必须加@Override注解。 说明:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。 说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程) 正例:public User getUsers(String type, Integer... ids) {...}

Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。 正例: “test”.equals(object); 反例: object.equals(“test”); 说明:推荐使用java.util.Objects#equals (JDK7引入的工具类)

所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。 说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。说明:反编译出的字节码文件显示如果使用“+”,每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。

集合处理

关于hashCode和equals的处理,遵循如下规则: 1) 只要重写equals,就必须重写hashCode。 2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。 3) 如果自定义对象做为Map的键,那么必须重写hashCode和equals。 说明:String重写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用。

使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。
说明:使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。参考:String[] array = new String[list.size()];array = list.toArray(array);

不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

Iterator<String> it = a.iterator();while (it.hasNext()) {    String temp = it.next();    if (删除元素的条件) {        it.remove();    }}

集合初始化时,指定集合初始值大小。 说明:HashMap使用HashMap(int initialCapacity)初始化, 正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意 负载因子(即loader factor)默认为0.75,如果暂时无法确定初始值大小,请设置为16。反例:HashMap需要放置1024个元素,由于没有设置容量初始大小,随着元素不断增加容量7次被迫扩大,resize需要重建hash表,严重影响性能。

使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。 说明:keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。而entrySet只是遍历了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法。 正例:values()返回的是V值集合,是一个list集合对象;keySet()返回的是K值集合,是一个Set集合对象;entrySet()返回的是K-V值组合集合。

Map map = new HashMap();Iterator iter = map.entrySet().iterator();while (iter.hasNext()) {    Map.Entry entry = (Map.Entry) iter.next();    Object key = entry.getKey();    Object val = entry.getValue();}

JDK8

map.forEach((k,v)->System.out.println("key : " + k + "; value : " + v));map.forEach((k,v)->{    System.out.println("Item : " + k + " Count : " + v);    if("E".equals(k)){        System.out.println("Hello E");    }});

利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。

并发处理

并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。

在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患(可参考 The “Double-Checked Locking is Broken” Declaration),推荐问题解决方案中较为简单一种(适用于JDK5及以上版本),将目标属性声明为volatile型。

public class SafeDoubleCheckedLocking {    private volatile static Instance instance;    public static Instance getInstance() {        if (instance == null) {            synchronized (SafeDoubleCheckedLocking.class) {                if (instance == null)                    instance = new Instance();//instance为volatile,现在没问题了            }        }        return instance;    }}

控制语句

下列情形,需要进行参数校验: 1) 调用频次低的方法。 2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。 3) 需要极高稳定性和可用性的方法。 4) 对外提供的开放接口,不管是RPC/API/HTTP接口。5) 敏感权限入口。

下列情形,不需要进行参数校验: 1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。 2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般DAO层与Service层都在同一个应用中,部署在同一台服务器中,所以DAO的参数校验,可以省略。 3) 被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。

MySQL

表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint( 1表示是,0表示否)。说明:任何字段如果为非负数,必须是unsigned。
主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名。 说明:pk_ 即primary key;uk_ 即 unique key;idx_ 即index的简称。

小数类型为decimal,禁止使用float和double。 说明:float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过decimal的范围,建议将数据拆成整数和小数分开存储。

在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明:1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。

更新数据表记录时,必须同时更新记录对应的gmt_modified字段值为当前时间。(MySQL对于更新数据会自动更新时间,但是如果更新的内容和原先的一致,更新时间不会改变,所以建议手动更新)

工程结构

DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。

DTO(Data Transfer Object):数据传输对象,Service和Manager向外传输的对象。

BO(Business Object):业务对象。可以由Service层输出的封装业务逻辑的对象。

Query:数据查询对象,各层接收上层的查询请求。注:超过2个参数的查询封装,禁止使用Map类来传输。

VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。

原创粉丝点击