《阿里巴巴 Java开发手册》读后感

前言

只有光头才能变强

前一阵子一直在学Redis,结果在黄金段位被虐了,暂时升不了段位了,每天都拿不到首胜(好烦)。

趁着学校校运会,合理地给自己放了一个小长假,然后就回家了。回到家才发现当时618买了一堆书,这堆书还有没撕包装的呢....于是我翻出了最薄的一本《阿里巴巴 Java开发手册》

这本书一共就90多页,一天就可以通读完了,看完之后我又来博文了。

注意:

  • 书上很多的规范是可以用IDE来避免的,也有很多之前已经知道的了。
  • 所以,这篇文章只记录我认为比较重要,或者说是我之前开发时没有注意到的一些规范(知识点)。
  • 该文章的内容肯定没有书上写得那么全的,如果感兴趣的同学可以去买一本来读一下~

PDF官方地址:

一、Java相关

  1. POJO是DO/DTO/BO/VO的统称,禁止命名为xxxPOJO
  2. 获取多个对象的方法中list作为前缀
  3. 获取统计值的方法用count作为前缀
  4. POJO类中的布尔类型(Boolean)的变量都不要加is前缀,否则部分框架解析会引起序列化错误
    • 如果你的变量名带is的话,比如isActive,框架解析的时候可能就当成active了。
  5. 如果是形容能力的接口名称,取对应的形容词为接口名(通常是-able的形式)
  6. 不允许任何魔法值(未经预先定义的常量)直接出现在代码中
  7. Object的euqals方法容易抛出空指针异常,应使用常量或者有值的对象来调用equals。推荐使用java.util.Object#equals工具类
  8. 所有POJO类的属性全部使用包装数据类型,RPC的返回值和参数必须使用包装数据类型,所有的局部变量都使用基本数据类型。定义VO/DTO/DO等POJO类时,不要设定任何属性的默认值
    • 如果你的类属性使用int这样的基本数据类型,默认值是0。一般情况下该变量没有赋值,一般想表达的是不存在(null),而不是0。
  9. 构造方法禁止加入任何的业务逻辑,如果初始化逻辑可以放在init方法中。set/get方法也不要增加业务逻辑。
    • 如果set/get方法放入业务逻辑,有时候排查问题就变得很麻烦了
  10. 工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。比如说add、clear、remove
  11. 在JDK7以及以上版本中,Comparator要满足三个条件,不然调用Arrays.sort()或者Collections.sort()会报异常。
    • x,y 的比较结果和 y,x 的比较结果相反
    • 传递性:x>y并且y>z,那么x一定大于z
    • 对称性:x=y,则 x,z 比较结果和y,z比较结果相同
  12. 使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历
    • keySet遍历了两次,一次是转成Iterator对象,一次是从hashMap中取出key所对应的value,如果JDK8可以使用Map.foreach方法
  13. 线程资源必须由线程池提供,不允许在应用中自行显示创建线程。线程池不允许用Executors创建,通过ThreadPoolExecutor的方式创建,这样的处理方式能够让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。
  14. SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类
    • 如果是JDK8应用,可以使用Instant(针对时间统计等场景)代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat
  15. 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降
    • 在JDK7之后,可以直接使用API ThreadLocalRandom,而在JDK7 之前,需要编码保证每个线程持有一个实例。
  16. 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /**内容*/ 格式,不得使用 //xxx 方式
  17. 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。所有的类都必须添加创建者和创建日期
  18. 对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码的上方,使用三个斜杠///来说明注释代码的理由
  19. 保证单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试之间不能互相调用,也不能依赖执行的先后顺序
  20. 高并发服务器建议调小TCP协议的time_await超时时间,调大最大事件句柄数(fd),

1.1值得说明的点

一、不允许任何魔法值(未经预先定义的常量)直接出现在代码中

例子:


    Negative example:
    //Magic values, except for predefined, are forbidden in coding.
    if (key.equals("关注公众号:Java3y")) {
        //...
    }

    Positive example:
    String KEY_PRE = "关注公众号:Java3y";
    if (KEY_PRE.equals(key)) {
        //...
    }

ps:我猜是把先常量定义出来,后续引用/修改的时候就很方便了。



二、Object的euqals方法容易抛出空指针异常,应使用常量或者有值的对象来调用equals。推荐使用java.util.Object#equals工具类

java.util.Object#equals的源码(已经判断null的情况了)


    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }


三、工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。

因为返回的ArrayList是一个内部类,并没有实现集合的修改方法。后台的数据仍是数组,这里体现的是适配器模式。



四、在JDK7以及以上版本中,Comparator要满足自反性,传递性,对称性,不然调用Arrays.sort()或者Collections.sort()会报异常。

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)

The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.

Finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.

  • 1) x,y 的比较结果和 y,x 的比较结果相反。
  • 2) 传递性:x>y,y>z,则 x>z。
  • 3) 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:


new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId() > o2.getId() ? 1 : -1;
    }
}


使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历

首先我们来看一下使用keySet是如何遍历HashMap的:


    public static void main(String[] args) throws InterruptedException {

        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("关注公众号:", "Java3y");
        hashMap.put("坚持原创", "Java3y");
        hashMap.put("点赞", "关注,转发,分享");

        // 得到keySet,遍历keySet得到所有的key
        Set<String> strings = hashMap.keySet();
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()) {

            // HashMap的每个key
            String key = iterator.next();

            // 通过key可以获得对应的value,如果有看过HashMap的同学知道get方法的时间复杂度是O(1)
            System.out.println("key = " + key + ", value = " + hashMap.get(key));
        }

    }

再来看一下源码:


// 1. 得到keySet,如果不存在,则创建
public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

// 2.初始化ks (实际上就是Set集合[HashMap的内部类],在初始化时需要顺便初始化iterator)
ks = new AbstractSet<K>() {
    public Iterator<K> iterator() {
        return new Iterator<K>() {
            private Iterator<Entry<K,V>> i = entrySet().iterator();

            public boolean hasNext() {
                return i.hasNext();
            }

            public K next() {
                return i.next().getKey();
            }

            public void remove() {
                i.remove();
            }
        };
    }

};

再来看一下entrySet,可以直接拿到key和value,不用再使用get方法来得到value,所以比keySet更加推荐使用


    public static void main(String[] args) throws InterruptedException {

        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("关注公众号:", "Java3y");
        hashMap.put("坚持原创", "Java3y");
        hashMap.put("点赞", "关注,转发,分享");

        // 得到entrySet,遍历entrySet得到结果
        Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
        }
    }

如果是JDK8的话,推荐直接使用Map.forEach()就好了,我们也来看看用法:


public static void main(String[] args) throws InterruptedException {

    HashMap<String, String> hashMap = new HashMap<>();
    hashMap.put("关注公众号:", "Java3y");
    hashMap.put("坚持原创", "Java3y");
    hashMap.put("点赞", "关注,转发,分享");

    // forEach用法
    hashMap.forEach((key, value) -> System.out.println("key = " + key + ", value = " + value));
}

其实在源码里边我们可以发现,forEach实际上就是封装了entrySet,提供forEach给我们可以更加方便地遍历Map集合



    // forEach源码
    default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }


五、SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。

有以下的例子可以正确使用SimpleDateFormat:


// 1. 在方法内部使用,没有线程安全问题
private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
public String getFormat(Date date){
    SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);
    return dateFormat.format(date);
}

// 2. 每次使用的时候加锁
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void getFormat(){
    synchronized (SIMPLE_DATE_FORMAT){
    SIMPLE_DATE_FORMAT.format(new Date());
    ….;
}

// 3. 使用ThreadLocal,每个线程都有自己的SimpleDateFormat对象,互不干扰
private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    }
};

// 4. 使用DateTimeFormatter(This class is immutable and thread-safe.)

    DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    System.out.println(timeFormatter.format(LocalDateTime.now()));

如果是JDK8应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat。


二、数据库相关

  1. 表达是否概念的字段,必须使用isxxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)
  2. 小数类型用decimal,禁止使用float和double。
  3. varchar是可变字符串,不预选分配存储空间的话,长度不要超过5000个字符。如果超过则用text,独立一张表,用主键对应,避免影响到其他字段的索引效率
  4. 表必备的三个字段:id(类型是unsigned bigint),gmt_create(创建时间),gme_modified(修改时间)
  5. 字段允许适当冗余,以提高查询性能,但必须考虑数据一致性。冗余的字段必须不是频繁修改的字段,不是varhar超长字段(更不能是text字段)
  6. 单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表(如果预计三年都达不到这个数据量,不要在创建表的时候就分库分表!)
  7. 超过三个表禁止使用join,需要join的字段,数据类型必须保持一致,当多表关联查询时,保证被关联的字段需要有索引
  8. 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,页面搜索严禁左模糊或者全模糊,如果需要则通过搜索引擎来解决。
    • 充分利用好最左前缀匹配特性!
  9. 利用延迟关联或者子查询优化超多也分场景。
  10. 如果有全球化需要,均以utf-8编码。如果需要存储表情,选择utf8mb4进行存储。

2.1值得说明的点

一、利用延迟关联或者子查询优化超多也分场景。

MySQL并不是跳过 offset行,而是取 offset+N行,然后返回放弃前offset行,返回N行,那当 offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

例子:


// 优化前

SELECT id, cu_id, name, info, biz_type
    , gmt_create, gmt_modified, start_time, end_time, market_type
    , back_leaf_category, item_status, picuture_url
FROM relation
WHERE biz_type = ‘0‘
    AND end_time >= ‘2014-05-29‘
ORDER BY id ASC
LIMIT 149420, 20;

// 优化后

SELECT a.*
FROM relation a, (
        SELECT id
        FROM relation
        WHERE biz_type = ‘0‘
            AND end_time >= ‘2014-05-29‘
        ORDER BY id ASC
        LIMIT 149420, 20
    ) b
WHERE a.id = b.id

解释:其实这里就是通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。这样就是充分利用了索引


三、未解决的问题

在看《手册》的时候还有一些知识点没看过、没实践过、涉及到的知识点比较多的,在这里先mark一下,后续再遇到或者有空的时候再回来补坑~

  • 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。说明: 注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
  • 对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
  • 使用JDK8的Optional类来防止NPE问题。

当然了,如果你有比较好的资料阅读,也可以在评论区告诉我。我也会mark住好好看看。

比如说:“3y,我发现Optional类有篇文章写得很不错,url是xxxx(书籍的名称是xxx)



由于现在没有一定的经验积累,所以以下的章节得回头看:

  • 《手册》中的“日志规约”,“工程结构”、“设计规范”

最后

看我上面写的内容就知道,除了一些规范外,还有很多实用的小技巧,这些对我们开发是有帮助的。我这个阶段也有一些没怎么接触过的("日志","设计","二方库"),这些都需要我在成长中不断的回看才行

  • ps:我会回来补坑的。

引用书上的一句话:

很多编程方式客观上没有对错之分,一致性很重要,可读性很重要,团队沟通效率很重要。程序员天生需要团队协作,而协作的正能量要放在问题的有效沟通上。个性化应尽量表现在系统架构和算法效率的提升上,而不是在合作规范上进行纠缠不休的讨论、争论,最后没有结论。

作者(孤尽)在知乎回答的一句话:

翻完了不代表记住了,记住了不代表理解了,理解了不代表能够应用上去,真正的知识是实践,实践,实践

如果你觉得我写得还不错,了解一下:

原文地址:https://www.cnblogs.com/Java3y/p/9969760.html

时间: 2024-10-12 20:09:14

《阿里巴巴 Java开发手册》读后感的相关文章

大道至简第五章读后感

第五章 失败的过程也是过程 今天照样老师带领着我们阅读了大道至简第五章,阅读了<大道至简>的第五章,这章在前面的基础上又进了一步,有了技术和团队,加上有效的沟通,接下来就要接项目做工程. “虚有其表耳”,本章以<明皇实录>中的一句话来告诉我们一个深刻的道理:不要只求外表,只做形象工程,而是要透过表象,力求实质. 失败了不要紧,没有失败也就找不到自己的不足,也就不会发现自己的问题,更不用谈改进了.我们的前辈们就是在不断的失败中才总结出了“瀑布模型”“螺旋模型”等模型,方便了我们.但是

大道至简 第五章读后感

第五章 失败的过程也是过程 以得失而论,在瀑布模型与RUP模型之间,学习前者而不成,可思过程的本质:学习后者而不成,可得文字的架子. 如果懂得了所谓的模型原本都演化自那个简单的瀑布,那么文档是按XP写还是按RUP写,也就可以应时.应需,因地置宜,择善而从了. 越是简单的东西,往往越是接近于本质. 项目经理的工作,就是要去组织这个工程中的各个角色,使得分工明确,步调一致,共同地完成这个项目.四川有句地方话叫“做过场”,也有说成“走过场”的.“过场”是舞台术语,意思是角色从舞台一端出场,再走到另一端

大道至简 第五章 失败的过程也是过程 读后感

今天该写一写大道至简第五章读后感了. 首先是“做过程不是做工程”,过程是为了实现某种目的而经历的一些事情,过程有很多种,虽然经历了某种过程,但不一定能实现某种功能.做完过程的每一个阶段,并不等于做工程.做过程不是做工程的精义,也不是最终目的. 然后是“做过场”,做过场就好像是一种形式一样,做了没必要做的事情,就是浪费时间. 我们为什么做工程,不要忘了最终目的.目的,是实现客户的要求,工程只是一种实现的途径.最初做开发的前辈们,不用什么工程或者过程,也一样编出了程序,也一样解决了问题,也一样实现了

大道至简第七章读后感

大道至简第七章读后感——现实中的软件工程 “王不如远交而近攻,得寸,则王之寸:得尺,亦王之尺也.”——<战国策.秦策> 1:大公司手中的算盘 文中列举了IBM,Borland和Microsoft的一些体系,来说明大公司眼中的世界. 大公司们在标准.理论.语言上的争来夺去,未必全然出于“软件实现”的考虑.对统一理论.统一工具.统一过程的企图,其最终目的是在整个软件工程体系中的全面胜出.算 盘 上 的 绝 大 多 数 人 , 只 是 用 于 计 算 胜 负 的 一 枚 算子.所谓编程语言,只不过是

大道至简第五章阅读感想

第五章失败的过程也是过程 今天王建民老师依旧带领着我们阅读了大道至简第五章,第五章是失败的过程也是过程.通过前面的技术.团队和沟通,这章主要讲了关于做工程的问题. 文章开篇以一句<明皇实录>中的“虚有其表耳”来说明一个很重要的问题就是:不能只求外表,而是要透过表象,力求实质. 第五章的整体思想是让我们注重过程,因为有很多人从来不注重过程,只注重结果.然而过程对于一个编程人员也是非常重要,如果一个好的编程员从来不在乎程序的过程,只是关心最后程序是否能够实现,那么这个编程员一定不是一个好的编程员.

大道至简 第六章 读后感

说点什么呢,今天看了看大道至简第六章<从编程到工程>. 文章以<列子·说符>的“得其精而忘其粗,在其内而忘其外:见其所见,不见其所不见,视其所视,而遗其所不视.”为题记.第一节讲了“语言只是工具”,作者讲述了他曾经对一些编程语言的看法.他曾经也热衷于讨论语言的优劣,但是他现在不这样了,他已经不再专注于语言, 正如他在第一章中写到的一样:成天讨论这门语言好,或者那门语言坏的人,甚至是可悲的.确实,程序的好坏不在于语言,在于算法. 第二节又写了“程序”,程序=算法+结构,编程的精义于此

《大道至简》第一章读后感

经常听见有人抱怨编程太难,说自己不是学软件的料,那么他们真该好好看看<大道至简>这本书,相信他们看完这本书后会有很大收获. <大道至简>第一章引用了一个很简单的故事“愚公移山”,用这个故事很好的概述了我们在完成一个项目时所要进行的步骤.听上去“愚公移山”和编程简直是风马牛不相及,但是看过作者的叙述又有原来如此的感觉.其实编程并没有什么难懂的,就和我们日常生活一样,发现问题,分析问题,提出解决问题的方案,实施,和后续的验收.例如某天我们突然发现家里放不出水了,这就是发现问题,我们会观

大道至简第三章读后感

---恢复内容开始--- 大道至简第三章的是团队的问题.我们知道,随着人们生活水平的不断提高,用户对计算机软件的功能要求也日趋上升.这样一来,计算机软件就变得越来越复杂,规模变得越来越庞大,源代码的量也越来越多.在这种市场需求和自身发展的共同要求之下,一个团结而高效的开发团队的作用就不言而喻了.那么如何打造一支强有力.听指挥.能干活的开发团队呢?这一章作者就这个问题和我们展开了讨论. 作者着重的强调了项目经理在开发团队中的作用.首先声明一点,这并不是说团队的开发人员不重要,作者从始至终都认为编程

一切都是为了实现-大道至简第六章读后感

大道至简第六章的内容比较多,也比较深.或者说这一章作者是从一个更高的层次.更开阔的视野.更独特的角度来解读软件工程这四个字的具体含义的. 作者的这些肺腑之言都是作者在软件行业工作了多年之后总结出来的.开发技术对一个软件产品质量的好坏和最终的成功的影响并虽然不能说是一点也没有,但也不是很大.真正起到决定性因素的不是那些技术细节,而是一个高度过程化.通晓方法论.拥有大量工具的开发团队或者是开发公司.在这个团队里面,无论是对项目经理还是开发经理甚至是一个普通的开发人员的要求都是很高的.团队内的每个人必

《大道至简》第一章读后感和伪代码

阅读了<大道至简>第一章,感到作者对编程的精义分析非常具体形象,引用<愚公移山>的故事,说明了编程的本质.又将他们扮演的管理者,技术人员,程序分析师众多形象展现出来.又在困惑人们的"我能不能学会编程"这一问题做出回答,作者列举生活实例,给出了肯定的答案,将很多抽象的东西,简单化,通过最常见的生活中的实例介绍"大道". import java.大道至简.*; public class.yishan.*; { public static void