Guava的设计哲学

本文是自己学习Guava类库的一些学习心得,主要来自Guava的官方文档,并夹杂了一些自己的理解。
Guava是Google内部使用的核心Java类库的开源版本,这些核心实用类都是Google内部员工平时编码中经常使用的类库,已经在Google的生产环境验证,因此,我们可以放心的使用他们。
Guava的定位是使得用Java语言工作更加愉快和更加高效。JDK自带的utilities类(比如Collections类)被广泛应用并极大的简化了Java代码,Guava的目标就是要继续这个“传说”。
Effective Java第47条,“了解并使用类库”,告诉我们,使用类库比自己写一个实用工具类更可取。(个人觉得,如果为了学习,这条可以忽略,相反,自己写一个类库并且跟现有的优秀的类库比较,反而更利于个人成长。)
【总之,不要重复造轮子。如果你需要一些看起来很通用的功能,那么可能已经存在相应的类库实现了你的功能。如果存在,那么就使用它;如果你不知道,那么可以去搜下。总之,使用类库大部分情况比自己实现要更好。这个无关你作为一个程序员的能力,因为通用的类库集聚了大部分程序员的努力,肯定比你自己实现这个功能投入的精力要多。】(摘自effective java)
所以,不得不说:
  • Guava在Google的生产环境经过了实战测试。
  • Guava经过了很多的单元测试。自动化的测试,并且覆盖率很高,尤其是collect子包。
  • Guava开发很活跃,用户参与也很积极。
  • 总之,很牛逼。
Guava在增加特性方面偏保守,在决定是否增加一个特性时,会考虑是否‘实用’并且‘普遍存在’。‘实用’表现在:可以节省你大量的代码;避免让你写出很难调试或者易于出错的代码;提高可读性;提高速度。‘普遍存在’就是在代码中竟然被使用,这一项可以通过Google内部的代码库里扫描统计出来。
相对于增加特性方面的保守,Guava在去除不需要的特性方面很果断,会把这些特性标为deprecate继而删除该特性。因此,使用@Beta注解的类或者方法要谨慎使用。
Guava的实现针对一些通用的设计原则,包括:
  • 针对特定的场景,这些api是最优的解决方案;
  • 类或者方法的语义是可以从他们的签名上很明显很直观的看出来。实现可以很smart,但是名字要很直观。
  • 鼓励Guava的用户有好的代码习惯,而Guava它自己的代码本身也是好代码习惯的典范。(比如快速失败,拒绝null)
  • 不要试图单独解决每一种场景,相反提供通用的工具以解决我们可能没遇到过的场景。
  • 强调可维护性,并留出未来重构的空间。(结果:大部分暴露出去的类应该是final的,就像effective java 17条说的;把AbstractXXX类暴露出去也应该很谨慎。)

Maven报AetherClassNotFound异常

maven 3.3 执行mvn dependency:tree时,会报出如下异常:

Error injecting: org.apache.maven.shared.dependency.graph.internal.Maven3DependencyGraphBuilder java.lang.NoClassDefFoundError: org/sonatype/aether/graph/Dependency

原因是因为,在Maven 3.1-alpha-1 从Sonatype Aether迁移到了Eclipse Aether,而这个变更对于某些插件(比如maven-dependency-plugin, android-maven-plugin, maven-shade-plugin, maven-site-plugin)是不兼容的。所以会报出找不到相关的类。

解决方法是:使用Maven 3.1.*之前的版本,比如3.0.5。或者根据如下链接,升级对应的插件的版本:https://cwiki.apache.org/confluence/display/MAVEN/AetherClassNotFound

早晨从中午开始

知道路遥这个人是因为路遥知马力,后来知道路遥是个作家,他的《平凡的世界》很出名。那时候,读高中,知道很多名著的名字,但是仅限于知道,并不曾读完这些大部头的书,毕竟大部分时间都用来对付考试。

后来读完了《平凡的世界》,久久不能平静。设想着自己是故事的主人公,也想着通过努力去改变自己的命运,而内心的浪漫情怀却也总被命运的无奈所触动。

看完《平凡的世界》后,觉得不过瘾,于是继续去搜索路遥的其他作品,又看了他的中篇小说《人生》,感叹个人命运在时代背景下的无力与无奈。

看完这两本后,再次搜寻路遥的作品,发现了一本书名很有意思的书,就是最近读完的《早晨从中午开始》,不得不说,起个好名字,真的很重要。

这本书收藏了很久,一直没有读。一方面是近几年来专业书看的相对多点,另一方面是因为总有比这本更想看的书要读。于是,本来早晨的事儿,拖着拖着就到了中午。

这书的名字很有趣,尤其是对于爱睡懒觉的人儿,大抵上也是从中午开始一天的。而这本书要表达的应该也有这个意思,因为路遥的一天也是从中午开始的,每天要凌晨2-3才睡下。另外一个意思则是,“当生命进入正午的时候,工作却要求我像早晨的太阳一样充满青春的朝气投身于其间……”。

《早晨从中午开始》是作者路遥的写作随笔集,其中的一篇,名字叫“早晨从中午开始”,详细记录了作者在《人生》发表后,创作《平凡的世界》的过程和自己的心路历程。作者从1982年《人生》获奖后开始,到88年创作完成平凡的世界,经历了6年的时间,其中三年准备材料,三年写作。看完之后,才明白什么是拿生命在创作。以下为部分摘录:

【“我渴望重新投入一种沉重。只有在无比沉重的劳动中,人才会活得更为充实。 是的,只要不丧失远大的使命感,或者说还保持着较为清醒的头脑,就决然不能把人生之船长期停泊在某个温暖的港湾,应该重新扬起风帆,驶向生活的惊涛骇浪中,以领略其间的无限风光。人,不仅要战胜失败,而且还要超越胜利。”这段表达了自己在《人生》获得全国大奖之后的思考,是在圈子里混还是继续创作。

“只有初恋般的热情和宗教般的意志,人才有可能成就某种事业” 继续给自己打气。

“人是一个非常复杂的矛盾体。为了不受干扰地工作,常常要逃避世俗的热闹;可一旦长期陷入孤镜,又感到痛苦,又感到难以忍受。”创作的孤独。

“体力在迅速下降,有时候累得连头也抬不起来。抽烟太多,胸脯隐隐作疼。眼睛发炎一直未好,痛苦不堪。”长期的创作导致的身体状态越来越差,期间甚至一度中断写作。

“几乎不是思想的支配,而是不知出于一种什么原因,我从桌前站起来所做的第一件事,就是把手中的那支圆珠笔从窗户里扔了出去。”创作完成后,作者将笔直接从窗户扔了出去,如释重负。】

有时候自己在想,作家应该是什么样子,作家的文学作品应该要起到什么作用。在这本书中,路遥给出了自己的答案,文学作品,应该反映时代和社会发展,大家能从小说中看的到整个社会的缩影和突出问题。“作家的劳动绝不仅仅是为了取悦当代,而更重要的是给历史一个深厚的交代。”,我对这个答案很认同。不知道当代文学作品和现在的作家是个什么样子?!在这方面,韩*有一点影子,而郭*却差太远。作家应该要有社会责任感。

路遥是我个人非常喜欢的作家,而《平凡的人生》非常值得再读,这本《早晨从中午开始》算是平凡的世界的“饭后甜点”吧。

读《简洁之美》

书的标题叫《简洁之美》,副标题叫”软件设计之道”。
英文原版书名叫:Code Simplicity: The Science of Software Development.
个人感觉英文名字更明了,更简洁。

老外尤其是老美喜欢给软件开发这件事情戴帽子,比如有人把软件开发说成是一门手艺,而本书则说软件开发是一门科学,是有法可依的。
呵呵,这也反应了软件开发这个事情,老美的专业化程度还是比较高的。

软件的质量和代码的质量息息相关,因此,本书的目的就是要总结提高代码质量的”道”,做到软件开发有章可依。

作者认为,软件的目标就是要帮助人,任何的软件决策都应该以这个为出发点。不仅要能帮助人,还要尽可能多的帮助人,更甚至于要持续的尽可能多的帮助人,于是,需要软件开发后维护的成本要低,这样才能可持续。

既然是科学,当然要列个方程式,软件改变的合意度(可行性),正比于软件当前价值和未来价值之和,反比于实现成本和维护成本之和。所以,如果软件要使用很长时间(持续为人提供帮助)的话,那么,降低维护成本就显得越来越重要。

变化是不可避免的,大部分程序员也都知道这点,但是为了处理变化,程序员也会进入以下设计误区:1)编写不必要的代码。常常会给后来的维护者带来很大的误解,不知道这段代码有何用,因为根本没用,所以,不要编写不是必需的代码,并删除不用的代码。2)代码难以修改。主要原因有对未来做过多的假设,不仔细设计就开始编码。为避免此问题,在设计时应该根据现在确切知道的需求,而不是你认为未来会出现的需求。同时要考虑到未来会变化,保持可扩展性。3)过分追求通用。因为未来是一定会变化的,而怎么变化我们又无法预测,因此,即使设计的再通用,也可能会出现不能满足的情况。如果你的设计让事情变得复杂而不是简单,那么你可能就过度工程了。当然,渐进式的设计和开发可以避免上述的三个误区。

程序中新增缺陷的可能性和代码修改量成正比,所以好的设计应该是能满足外界的变化,但是自身变化要少。于是,就有如下规则:永远不要修正任何东西,除非他真的有问题,而且有证据表明问题确实存在。附加一条,任何系统里的任何信息,理想情况下应该只存在一份,DRY原则。

软件越简洁越容易维护,为保持简洁,尽量保持一致(命名,方法定义等),代码排版格式,名字足够长,完整表达意义但又不要太长影响阅读。要做到简洁,通常需要设计。如果遇到了不可避免的复杂,那么在外面包一层,让别人易于理解。

本书只有100页左右,主要列出了一些原则和可以遵守的条例,跟书名比较符合,非常简单。可以作为日常软件开发的一些指导,只有在实践中不断尝试,才能利用好这些原则。另外,感觉里面的东西,在《代码大全》里都有涉及,不愧的大全啊,那本书值得多读多实践。所以,相比本书,更推荐直接去读《代码大全》那本书。

不习惯法则

之前用惯了eclipse,后来部门里都用IntelliJ IDEA,并且大家都说IDEA更好用,然后自己也去试用了下,发现了一些优点,但是仍然感觉不如eclipse好用,然后又切换回去了。
切换回eclipse之后,又开始想念IDEA的某些好处,再次试用,如此反复,来回几个回合,然后发现两个都有优点,又都有用不惯的地方。

最开始用电脑的时候,肯定是用的Windows,后来周围的人都在用MBP,业界对MBP的评价也比较好,于是体验了Mac系统,不习惯,但是还是感受到了一些优点,比如MBP的触模板,以及笔记本本身的设计感。但是老是想念用鼠标点点的感觉,尤其是macOS的Finder出奇的难用,于是,在Windows和Mac之间来来回回切换了无数次。最后感觉找不到一个各方面都合适的电脑了。

后来,来回折腾,耗费了大量的时间,心也累。索性不切换了,选了业界大部分人认为好的选项,IDEA和MBP。到现在,已经习惯了这两个东西,并且越来越觉得好用。当然也容忍了他们的缺点,而且也觉得所谓的缺点可能并非缺点,或者没那么重要。

结论:
1)改变习惯肯定会觉得不习惯,但是当你度过了这个不习惯的阶段,你会迎来一个新的天地。
2)大多数情况下,大家的感觉还是准的,选择大家都觉得好的一般没错。
3)没有完美的工具,选一个差不多好的工具,用熟练它,会大大提高你的效率。不要在工具上耗费太多的时间,尤其是同类的工具。

后记:
近视,配了一副眼镜,刚开始带不习惯,所以,很少带。最近感觉不带看不清了,于是带的时间久了,感觉也没那么不适了。

一点金融小知识

以下文字只在特定条件下成立,不可轻信:

国内银行卡之间是不能转账外币的,除非是直系亲属;
海外个人银行卡可以转账美元给(本人/非本人)的内地银行卡,要收费的;
海外个人银行卡可以直接结汇人民币到同名的内地账户,免费的。

自驾回家过年

记录下今年自驾回家过年的经历和感受。

拿到驾照买车后,没怎么开车上高速,一直在市区上下班开开。今年回老家过年,虽然老妈和老婆都不希望我开车,因为离老家实在太远了,有800多公里,还带着小孩,而且是只能我自己开。但是在我的坚持下,最终还是同意了。

由于之前没上过高速,因此,这次也是做足了准备,在今日头条关注了好多上高速的注意事项,并且在出发之前去4s店做了保养,没想到还真查出轮胎慢撒气,扎了钉子,于是补胎。后来,为了安全,又花了1000多大洋,装了一个胎压监测。因此,以后上高速,最好做个保养或者全车检查,尤其是轮胎。

腊月25一大早8点半我们一行四人就出发了,车上装的满满的,恨后备箱不够大啊,宝宝的推车都没装下,而这也间接造成了后来的老婆在家抱孩子不小心闪了腰,囧rz。第一次独自上高速还是蛮激动的,不过毕竟已经在市区开了一年多了,因此开的也算稳稳当当。开了一个多小时,就遇到了前方事故,堵车,正好又早上喝水多了,于是就进了服务区,休息了一会,继续上路。后来又去了两次服务区,吃个午饭,大约堵了2个小时,本来9个小时的路程,用了12多个小时,到晚上9点才到家。自己一个人开,还可以,稍微有点累,不过,毕竟第一次开这么久,兴奋大于疲惫,哈哈,一路还算顺利。

回到家后,发现老家虽然是乡下,但是比上次回来,汽车明显多了起来,好多人家的门口都停了小汽车。不过配套还是不行,之前修的路太窄了,基本只能通行一辆车,一旦对向两辆车,就要让着过了。

大年初五,从家里开始往回走,不过回来的路就没那么顺利了。早晨8点半出发的,走到高速路口就被告知封闭了,不让上高速,需要换到另一个高速入口上高速,要多跑几十公里的路。后来上了高速,发现车流量很大,再后来就堵在高速上了,特别的堵,到晚上9点,才走了1/3的路,光在加油站加油排了一个多小时的队,终于在晚上9点再次上路,流量相对好点,不是堵着不动了,但是也快不起来。一路开着,后来遇上了大雾,基本上能见度不到10米,开着双闪,根本没法走,老婆吓得要下高速,自己当时心里也是有点发憷,毕竟根本看不清前面的路,还怕后面车追尾,最终硬着头皮开过了那段大雾的路段。再往后就遇到了下雨,还有闪电,不过比之前的大雾好多了,毕竟能看清路了。最后路段,自己感觉太疲劳了,基本上靠自己掐大腿和打脸来保持清醒,老婆在副驾驶上也不断的提醒我。所幸,凭借着坚强的意志,平平安安的开到了家,到家的时候,凌晨3点多,一共开了19个小时。自己当时的感觉是,以后再也不这么开了。现在想想,有些后怕,在那种情况下,大雾+疲劳+天黑,太过冒险。提醒自己,下不为例。

值得欣慰的时候,来回路上,宝宝很乖,没怎么闹腾,远远好于我们之前的担心。赞一个。但是,为了宝宝,以后也不能这么开了,大人小孩都受罪。

这次回家过年,老婆闪了腰,自己喝酒后收了风寒,都没怎么好好过年。不过欣慰的是,我们回去,老爸很开心。老妈在我们这里帮着看孩子,老爸自己在家很辛苦,自己还是不能很好的照顾自己,这是让我很不舒服的。今年尽量争取有空多回去看看。

以上就是今年回家过年时自驾的见闻和感受。

Java NIO – Buffer

Buffer是一块将要写到Channel里的数据,或者刚从Channel里读出来的数据。它是用来保存数据的一个对象,并且作为NIO Channel的其中一个端点(另一端可能是网络或者文件等)。Buffer提供了一种访问数据的机制,并且跟踪数据读写流程。

Buffer是Java 老的IO和NIO的主要不同之一。以前老的IO直接从流中读或者写。现在数据是从Buffer中都数据或者将写的数据写入Buffer中。在NIO中,Channel就相当于以前的Stream。了解更多NIO Channel可以访问这里。

Buffer特点:
1 Buffer是NIO的基本构建块。
2 提供固定大小的容器来读写数据。
3 每一个Buffer都是可读的,但是选中的Buffer才可以写。
4 Buffer是Channel的其中一个端点。
5 在只读Buffer中数据内容是不可变的,但是Buffer的mark,position,limit是可以变的。
6 默认Buffer不是线程安全的。

Buffer类型:
每一个原生类型都有对应的Buffer类型。所有的Buffer类型都实现了Buffer接口。最常用的Buffer类型是ByteBuffer。在Java NIO包中包含如下的Buffer类型:ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer,MappedByteBuffer。

Buffer Capacity:
Buffer是固定大小的类型。如果Buffer满了,那么必须先clear,然后才能继续写入。一旦Capacity被设置就不能再改。

Buffer Limit:
在写模式中,Buffer的limit和capacity相同。在读模式中,limit是最后写入数据的索引加1。随着Buffer不断写入,Limit会保持增长。0 <= limit <= capacity。

Buffer Position:
position指向Buffer的当前位置。当Buffer刚被新建的时候,position指向0,当读或者写的时候,position增加到下一个索引。Position在0和Limit之间。

Buffer Mark:
Mark就像对Buffer中的位置做了一个书签。当调用mark()方法的时候,当前的position会被记录。调用reset()方法可以恢复到之前mark的position。

Buffer flip,clear,rewind:

flip():调用此方法,使得Buffer准备开始读或者开始一系列新的写。设置limit=position,position=0.
clear():调用此方法,使得Buffer准备开始写或者启动一系列的新的读。设置limit=capacity,position=0.
rewind():想要从头开始读时,调用此方法,会把position设成0.

从Buffer中读数据示例代码:

ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.flip(); //Flip the buffer to prepare for read operation.
int numberOfBytes = fileChannel.read(byteBuffer);
char c = (char)byteBuffer.get();

向Buffer中写数据:

ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.put((byte) 0xff);

在NIO中,如果你要向文件中写数据,先把数据写入到Buffer,然后用Channel把Buffer写入到文件。与此同理,如果要从文件读数据,用Channel把数据从文件读到Buffer,然后从Buffer中取数据。

package com.javapapers.java.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class BufferExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("temp.txt");
        write(path);
        read(path);
    }

    private static void write(Path path) throws IOException {
        String input = "NIO Buffer Hello World!";
        byte[] inputBytes = input.getBytes();
        ByteBuffer byteBuffer = ByteBuffer.wrap(inputBytes);
        FileChannel channelWrite = FileChannel.open(path,
                StandardOpenOption.CREATE, StandardOpenOption.WRITE);
        channelWrite.write(byteBuffer);
        channelWrite.close();
    }

    private static void read(Path path) throws IOException {
        FileChannel channelRead = FileChannel.open(path);
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        channelRead.read(byteBuffer);
        byte[] byteArray = byteBuffer.array();
        String fileContent = new String(byteArray).trim();
        System.out.println("File Content: " + fileContent);
        channelRead.close();
    }
}

本文翻译自 http://javapapers.com/java/java-nio-buffer/,仅供参考和学习。

Java NIO – Channel

在Java NIO中,Channel被用来I/O传输。Channel就像一个用来在Buffer和另外一头的实体(比如文件)之间传输数据的管道。一个Channel从一个实体里读取数据,并且把数据放到缓冲区(Buffer)里供别人(一般是我们的程序)消费,类似的,我们应该把数据写入到缓冲区里,然后Channel会把缓冲区里的数据传输到I/O的另一端。

Channel是Java NIO提供的用来访问原生的I/O机制的一种途径,我们在编程时应该使用Buffer来和Channel交互,所以,Channel更像是I/O两端实体的桥梁。所谓的Buffer,是Channel用来发送和接收数据的端点。

JavaNIOChannel.png

Channel的特点:

  • 与传统的流(stream)相比,Channel是双向的,既可以写,也可以读。
  • Channel可以把数据读进Buffer,也可以从Buffer里写数据。
  • Channel可以执行异步(asynchronous)的读写操作。
  • Channel可以支持阻塞(blocking)或者非阻塞(non-blocking)模式。
  • 非阻塞的Channel不会使调用的线程进入sleep模式。
  • 基于流的Channel(比如Socket)只能处于非阻塞模式。
  • 数据可以在Channel和Channel之间传输,前提是其中一个Channel是FileChannel。

Java NIO中的Channel类
以下是Java NIO包中提供的两种主要的Channel类实现:

  • FileChannel
    -文件读写的Channel,不支持非阻塞模式。
  • SocketChannel
    -有三种Socket Channel类,包括SocketChannel, ServerSocketChannel and DatagramChannel。
    -可选择(selectable)的Channel,支持非阻塞模式。

一个Java NIO Channel的例子
下面这个例子从文本文件中读数据并且把内容打印到控制台中。我们使用RandomAccessFile和FileChannel把数据读取到ByteBuffer。我们读取512字节的数据到buffer,然后调用buffer的flip方法,使其准备好get操作。一旦我们把数据打印到控制台,我们调用buffer的clear方法,使其可以进行下一次读。如此,一直到文件的末尾。

package com.javapapers.java.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ChannelExample {
    public static void main(String args[]) throws IOException {
        RandomAccessFile file = new RandomAccessFile("temp.txt", "r");
        FileChannel fileChannel = file.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        while (fileChannel.read(byteBuffer) &gt; 0) {
            // flip the buffer to prepare for get operation
            byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                System.out.print((char) byteBuffer.get());
            }
            // clear the buffer ready for next sequence of read
            byteBuffer.clear();
        }
        file.close();
    }
}

下一篇,我们会详细看下Java NIO的Buffer和各种Channel类。

本文翻译自 http://javapapers.com/java/java-nio-channel/,仅供参考和学习。