使用Google Guava来编写优雅的代码 – 集合2

根据计划,从本篇开始会挑选Guava内部新增加的集合类来进行介绍。今天首先介绍的是Multiset:把重复的元素放入集合。JDK中Set定义了不能将重复的元素放入,那么Guava提供可以放入重复元素的Set的用意或者作用是什么呢?

首先让我们来看下面一段代码:

Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
    Integer count = counts.get(word);
    if (count == null) {
        counts.put(word, 1);
    } else {
        counts.put(word, count + 1);
    }
}

上面的代码实现的功能非常简单,用于记录字符串在数组中出现的次数。这种场景在实际的开发过程还是容易经常出现的,如果使用实现Multiset接口的具体类就可以很容易实现以上的功能需求:

Multiset<String> wordsMultiset = HashMultiset.create();
wordsMultiset.addAll(words);
//接下来就可以直接使用count方法来得到某个特定元素的个数了。

Multiset主要方法

Multiset接口定义的接口主要有:

  • add(E element) :向其中添加单个元素
  • add(E element,int occurrences) : 向其中添加指定个数的元素
  • count(Object element) : 返回给定参数元素的个数
  • remove(E element) : 移除一个元素,其count值 会响应减少
  • remove(E element,int occurrences): 移除相应个数的元素
  • elementSet() : 将不同的元素放入一个Set中
  • entrySet(): 类似与Map.entrySet 返回Set<Multiset.Entry>。包含的Entry支持使用getElement()和getCount()
  • setCount(E element ,int count): 设定某一个元素的重复次数
  • setCount(E element,int oldCount,int newCount): 将符合原有重复个数的元素修改为新的重复次数
  • retainAll(Collection c) : 保留出现在给定集合参数的所有的元素 * removeAll(Collection

    c) : 去除出现给给定集合参数的所有的元素

测试代码如下 :

/**
 * Test class for Multiset
 */

@Test
public class MultisetTest {


    @Test
    public void shouldAddElementTwoTimes() throws Exception {

        // given
        Multiset<String> multiset = HashMultiset.create();

        // when
        multiset.add("nothing");
        multiset.add("nothing");

        // then
        assertThat(multiset.count("nothing")).isEqualTo(2);
        assertThat(multiset.count("something")).isEqualTo(0);
    }

    @Test
    public void shouldUserCustomAddRemoveAndSetCount() throws Exception {

        // given
        Multiset<String> multiset = HashMultiset.create();

        // when
        multiset.add("ball");
        multiset.add("ball", 10);

        // then
        assertThat(multiset.count("ball")).isEqualTo(11);


        // when
        multiset.remove("ball", 5);

        // then
        assertThat(multiset.count("ball")).isEqualTo(6);


        // when
        multiset.setCount("ball", 2);

        // then
        assertThat(multiset.count("ball")).isEqualTo(2);
    }


    @Test
    public void shouldRetainOnlySelectedKeys() throws Exception {

        // given
        Multiset<String> multiset = HashMultiset.create();

        multiset.add("ball");
        multiset.add("ball");
        multiset.add("cow");
        multiset.setCount("twelve", 12);

        // when
        multiset.retainAll(Arrays.asList("ball", "horse"));

        assertThat(multiset.count("ball")).isEqualTo(2);
        assertThat(multiset.count("twelve")).isEqualTo(0);
    }

}

Multiset 不是Map

需要注意的是Multiset不是一个Map<E,Integer>,尽管Multiset提供一部分类似的功能实现。其它值得关注的差别有:

  • Multiset中的元素的重复个数只会是正数,且最大不会超过Integer.MAX_VALUE。设定计数为0的元素将不会出现multiset中,也不会出现elementSet()和entrySet()的返回结果中。
  • multiset.size() 方法返回的是所有的元素的总和,相当于是将所有重复的个数相加。如果需要知道每个元素的个数可以使用elementSet().size()得到.(因而调用add(E)方法会是multiset.size()增加1).
  • multiset.iterator() 会循环迭代每一个出现的元素,迭代的次数与multiset.size()相同。 iterates over each occurrence of each element, so the length of the iteration is equal to multiset.size().
  • Multiset 支持添加、移除多个元素以及重新设定元素的个数。执行setCount(element,0)相当于移除multiset中所有的相同元素。
  • 调用multiset.count(elem)方法时,如果该元素不在该集中,那么返回的结果只会是0。

实现类

Guava提供了Multiset的很多实现类,每一个实现类都对应这JDK的某一个map实现。具体如下表所示。

Map 对应的 Multiset实现 支持null
HashMap [HashMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/HashMultiset.html) Yes
TreeMap [TreeMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/TreeMultiset.html) Yes (如果comparator允许)
LinkedHashMap [LinkedHashMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/LinkedHashMultiset.html) Yes
ConcurrentHashMap [ConcurrentHashMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ConcurrentHashMultiset.html) No
ImmutableMap [ImmutableMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ImmutableMultiset.html) No
EnumMap [EnumMultiset](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/EnumMultiset.html)

除此以外还有ForwardingMultiset和ImmutableSortedMultiset,对于某些先增的类,需要关注其注解是否包含@Beta,那么这些类的API尚未稳定,调用过程需要小心。

本文对Guava中新增加的Multiset接口和实现类进行了简单的介绍,接下来我们将会介绍新增加的Multimap接口和其实现类的一些相关使用场景和使用方法,希望大家继续关注。

使用Google Guava来编写优雅的代码 – 集合1

使用Google Guava来编写优雅的代码 – 集合1

在我们日常的Java开发工作中,经常需要使用各种数据结构,通常情况我们会使用JDK内部的集合库,也有可能使用到其它优秀的第三方库,诸如Apache common-collections等。但是如果仅仅使用JDK内部的集合库,我们可能需要编写一段相对复杂的代码来实现相对复杂的处理逻辑。在这种情况下,我会建议你使用Google Guava内置的相关的集合类和工具类,很有可能这里面就有符合你需要的API方法。

Google Guava内置的集合相关的类原本属于Google开源的Google-collections库,它对JDK内置的集合类和工具类提供丰富的扩展和增强,目前已经集成到Guava中,使用Guava内置的集合库肯定能帮助你编写出更简洁、更易读和更易维护的工作代码,使你能更好的关注需要处理的业务逻辑本身。并且这些集合类和工具都经过了单元测试,并广泛用于Google内部,或其它著名的项目中,诸如elasticsearch等。

Guava对于集合的扩充和增强主要涵盖以下几个方面:

  • 提供了不可变集合类的支持
  • 增加了新的集合类型
  • 提供了强大的集合操作的工具,诸如排序、交集、差集、并集等方法的实现

本系列文章对于集合的介绍按照以上几个方面来逐一介绍,本章节将介绍集合创建和不可修改集合类的使用介绍,随后的章节将会挑选相对使用广泛的新增的集合类进行逐一介绍,最后一个章节则会介绍Guava中提供的集合操作的方法。

集合对象创建方法

在我们的日常开发中,肯定会经常使用到创建一个集合对象的地方,这些代码看起来就像下面这样:

List<String> fields = new ArrayList<String>();
Map<String,String> nameAddressMap = new HashMap<String,String>();   

在Guava中对于JDK标准的集合类和Guava增加的集合类都提供快速创建的方法。示例代码如下:

//Guava增加的集合类
Multimap<String,String> multimap1 = new ArrayListMultimap<String,String>();
Multimap<String,String> multimap2 = ArrayListMultimap.create();

对于标准的JDK集合类,Guava提供了Lists、Sets和Maps类来实现基本标准类型的创建方法,代码如下:

List<String> myList1 = new ArrayList<String>(); //标准方式
List<String> myList2 = Lists.newArrayList();    //使用Guava

Set<String> mySet1 = new HashSet<String>(); //标准方式
Set<String> mySet2 = Sets.newHashSet();     //使用Guava

不可修改集合

在我们的日常开发中还是有需要使用到不可修改集合的需求的,比如常量,避免多线程间的竞争检查(如果你定义了一个可变集合,那么你要处处小心,防止别的线程与它之间的使用关系。)。在JDK 5以后,JDK提供了创建不可修改的集合类的方法,编写的代码大致如下:

Set<String> set = new HashSet<String>(Arrays.asList(new String[]{"RED", "GREEN"})); 
Set<String> unmodifiableSet = Collections.unmodifiableSet(set); 

这样编写的代码看起来也不错,这个不可修改Set仍然是存在问题的。如果有人不小心了,前面定义的引用set对象里的数据的话,这个不可修改集合就会被改变了。
如果你使用Guava来编写这段代码的话,就变成下面这个样子:

ImmutableSet<String> immutableSet = ImmutableSet.of("RED", "GREEN"); 

两行变一行,并且不用担心引用对象被修改的问题了,做到了真正的不可修改了,如果尝试使用immutableSet进行add方法时也会抛出UnsupportedOperationException。

可以执行下面一段代码发现JDK自带的保护方法和Guava实现的异同和问题。

HashMap<String,Integer> temp = Maps.newHashMap();
temp.put("one",1);
temp.put("two",2);

ImmutableMap<String,Integer> tempmap = ImmutableMap.copyOf(temp);
Map<String,Integer> unmodifiableSet = Collections.unmodifiableMap(temp);
temp.put("3",3);


System.out.println(tempmap.size());
System.out.println(unmodifiableSet.size());

Guava对标准的集合和其增加的集合类都提供对应的不可变类。标准的集合类的不可修改实现接口为:ImmutableList,ImmutableSet和ImmutableMap,Guava增加的集合类诸如ImmutableMultimap。

对于创建不可修改集合,Guava提供了以下集中方法来实现:

  • of 如前文示例一般,可以快速的创建不可修改集合
  • copyOf 将已有的集合对象转变为不可修改集合

    ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz");
    thingamajig(foobar);
    
    void thingamajig(Collection<String> collection) {
        ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection);
        ...
    }
    
  • builder

    // use a builder...
    public Map<String,Integer> makeImmutableMap() {
        ImmutableMap.Builder<String,Integer> mapBuilder =
                new ImmutableMap.Builder<String,Integer>();
        Entry<String,Integer> entry = null;
        while((entry = getEntry()) != null) {
            mapBuilder.put(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }
    

asList

所有的集合类都提供了asList方法,比如你使用了一个ImmutableSortedSet,那么你可以直接使用sortedSet.asList().get(k)得到排序后的单个元素。

如果你需要了解更详细的关于本文相关的内容可以前往以下资源查看:

使用Google Guava来编写优雅的代码一字符串处理2

字符串处理在日常开发过程经常容易碰到,运用的场景也非常广泛,前面一篇文章简要介绍了Google Guava中关于字符串分割与合并的两个主要的类Joiner和Splitter的主要API和基本使用。

本章节则主要关于与字符串处理相关的其它类,诸如CharMatcher、CaseFormat、Charsets以及Strings等。

在日常的字符串操作中,我们可以视为两个部分:匹配和处理。

  • 匹配的过程就是找到符合要求的字符。前文介绍的Joiner和Splitter的on方法就是一个匹配的过程。另外还有如在WEB开发中经常涉及到的全数字校验、全英文字符校验、字符的位置等等。
  • 处理的过程则意味这如何对这些如何条件的数据进行处理。处理的需求也是多种多样的,常见的如替换、过滤、补全、清空等。

Google Guava的良好的API设计能帮助我们更容易的解决字符串处理中所需要实现的基本功能,从而能有更多的关注集中在业务处理层面。

CharMatcher

CharMatcher类提供了针对字符处理的多种方法。正如前文提到的字符处理过程分为两个部分:匹配和处理。

CharMatcher提供两个方面的API,分别用于匹配条件和处理数据。

匹配条件

首先CharMatcher预先定义了字符常量来帮助进行匹配字符:ANY、ASCII、BREAKING_WHITESPACE、DIGIT、INVISIBLE、JAVA_DIGIT、JAVA_ISO_CONTROL、JAVA_LETTER、JAVA_LETTER_OR_DIGIT、JAVA_LOWER_CASE、JAVA_UPPER_CASE、NONE、SINGLE_WIDTH、WHITESPACE等。这些常量里面包含了空格、ASCII、英文字母、数字或英文字母、大小写,基本涵盖了日常字符匹配的需要。

除了这些常量,CharMatcher还另外提供了一些方法来进行字符匹配:

  • is : 符合与给定参数相同的字符 ,例如:is('_') 判断知否有下划线
  • isNot : 符合不是给定参数的字符,例如:isNot('_') 排除掉下划线以外的字符
  • anyOf : 符合在给定参数字符内的任一字符 例如:anyOf("a83h") 找到字符’a’,’8′,’3′,’h’任一个。
  • inRange : 符合给定参数字符范围内的字符 例如:inRange('a','z') 在a-z范围内的字符

此外还提供了诸如and(CharMatcher)or(CharMatcher)以及negate 等其它的方法可以组合出多种匹配条件。

处理数据

在字符处理中需要对符合或者不符合条件的数据进行相应的处理。CharMatcher提供了两类处理API,一种是返回判定结果的,比如计算出现次数、索引位置等;一种对字符串进行处理的,比如替换、清除等。

  • replaceFrom: 替换符合条件的字符。例如:CharMatcher.is('_').replaceFrom("UNIMAS_SYSTEM",'&') 返回结果:UNIMAS&SYSTEM;
  • removeFrom: 清除符合条件的字符。例如:CharMatcher.isNot('_').removeFrom("UNIMAS_SYSTEM") 返回结果为: _
  • countIn:计算出现次数 例如:CharMatcher.anyOf("a83h").countIn("UNIMAS_SYSTEM") 返回结果为: 0
  • indexIn:首次出现的索引位置 ,可以从0开始,也可以指定从其它位置开始。 CharMatcher.anyOf("a83h").indexIn("UNIMAS_SYSTEM") 返回结果为: -1 CharMatcher.anyOf("a83h").indexIn("UNIMAS3_SYSTEM") 返回结果则为6
  • lastIndexIn: 最后一次出现的索引位置 ,可以从0开始,也可以指定从其它位置开始。

其它的方法还有retainFrom、trimLeadingFrom、trimTrailingfrom、matches、matchesAllOf等。

Strings

除了前面提到的几个字符处理相关的类以外,Guava也有一个专门针对String或CharSequence进行相关处理的工具类,其内部的API方法也非常实用和简便。在这里也简单介绍下。

  • 字符串对象的null和””的相关处理方法,在日常开发中经常需要会碰到。为此Guava提供了三个常用的方法:isNullOrEmpty(判断是否为null或者空字符串)、emptyToNull(将空字符串转换null)和nullToEmpty(将null转换为空字符串)。后面的两种方法经常出现在sql处理中。
  • 补全与重复。比如为了产生固定位数的的编码,我们常常需要在不足的位数前后进行前补全或后补全。偶尔也需要让某一个字符串重复多次。这里Guava提供padEnd、padStart和repeat方法来实现这些功能,比如:”Strings.padStart(“123″,8,”0”)”-–|在123前面补0变成8位数的编码。
  • 找到两个字符串中前缀相同或后缀相同的最长子串。比如Strings.commonPrefix(“A-Stringa”,”A-StrindB”)返回则是”A-Strin”;

Charsets和CaseFormat

Charsets

预定义了常用的字符集的常量。包含ISO_8859_1、US_ASCII、UTF_16、UTF_16BE、UTF_16LE、UTF_8;

CaseFormat

主要用于拼写格式的转换,从定义风格来看更向是符合编程语言的格式间转换。

提供的方法有:

  • LOWER_CAMEL : 转换为小写开头 lowerCamel
  • LOW_HYPHEN: 小写的中划线分割 lower-hyphen
  • LOWER_UNDERSCORE : lower_underscore
  • UPPER_CAMEL: UpperCamel
  • UPPER_UNDERSCORE :UPPER_UNDERSCORE

实现转换方法示例如下:

CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,"CONSTANT_NAME"); //returns "constantName";
CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN,"yourIdeaLove"); //returns "your-idea-lover"

使用Google Guava来编写优雅的代码一字符串处理1

注:本文部分内容来自于Having fun with Guava’s String Helpers 以及Guava官方指南

在我们日常的Java开发中,经常碰到的处理的都是与字符串处理相关的,拼接、分割、替换、字符集、判断和查找等。

首先我们来看实际开发中比较常遇到的两段代码。

下面是字符串拼接的一段处理代码:

public static String toCommaSeparatedString( List<String> strings ) {
    String result = "";
    if( strings != null && !strings.isEmpty() ) {
        StringBuilder stringBuilder = new StringBuilder();
        for( int i = 0; i < strings.size(); i++ ) {
            String string = strings.get( i );
            if( string != null ) {
                stringBuilder.append( string );
                stringBuilder.append( "," );
            }
        }
        stringBuilder.deleteCharAt( stringBuilder.length() - 1 );
        result = stringBuilder.toString();
    }
    return result;
}

还有拆分字符串的一段代码:

public static List<String> fromCommaSeparatedString( String string ) {
    List<String> strings = new ArrayList<String>();
    String[] splitted = string.split( "," );
    for( int i = 0; i < splitted.length; i++ ) {
        String element = splitted[ i ].trim();
        strings.add( element );
    }
    return strings;
}

以上两段经常出现在我们的日常开发的产品中,比如sql语句组装、前端页面传递到后台的组装数据的拆分等。上面的两段本身可以很好的转变为工具类来使用,但是就代码本身来说,维护代码的人可能还是需要阅读代码本身来了解该功能的具体的细节。

如果我们改用Google Guava的话,代码看上去就会很易读了,并且代码行也能得到控制。

public static String toCommaSeparatedString( List<String> strings ) {
    Joiner joiner = Joiner.on( "," ).skipNulls();
    return joiner.join( strings );
}

public static List<String> fromCommaSeparatedString( String string ) {
    Iterable<String> split = Splitter.on( "," ).omitEmptyStrings().trimResults().split( string );
    return Lists.newArrayList( split );
}

代码行从22行减少为9行,并且由于Guava的代码经过了100%的单元测试和Google内部产品的使用,因而我们更容易关注我们自身的业务实现。

上面使用Guava后的两个方法中还是有值得继续探究的地方。

首先让我们来看toCommaSeparatedString方法,其实代码可以直接写成一行 return Joiner.on( "," ).skipNulls().join( strings), 这样就可以比较容易的从方法的字面知道该语句具体做了哪些工作:

  1. on(",") 定义了字符串拼接时使用什么符号进行间隔设定;
  2. skipNulls() 定义了在拼接时跳过空值,即待处理的字符串值如果为null则不会被拼接;
  3. join(strings) 则是将待处理数据

同时以上代码的链式写法也让代码的易读性增加了不少,在以后的介绍Guava关于Function的部分将会继续深入介绍链式写法。

Joiner还提供其它的API,诸如:

  1. useForNull(String nullText) : 用于替换null值的字符串语句;比如sql语句中,我们经常需要将null转换为sql的”,可以使用为useForNull(“”);
  2. withKeyValueSeparator(String keyValueSeparator) : 用于拼接Map中的key与value间的字符串;比如使用Joiner.on("&").withKeyValueSeparator("=").join(hashMaps) 可以得到形如 leton=21&mole=22的结果。
  3. appendTo(*,*) : appendTo方法则提供了向可追加对象后增加数据的做法。诸如可以向已有的StringBuilder对象后追加循环数据等。

下面来看fromCommaSeparatedString方法,分割器Splitter同样也提供了丰富的API来帮助用户实现各种形式的字符串分割方法。我们首先仍然是看工作内容:

  1. on(",") 定义了分割字符串的检查依据。这里除了可以使用字符串还可以使用正则。Splitter还可以将长度对字符串进行分割,比如每隔两个字符进行一次分割,这时可以选择使用fixedLength(2)来实现。
  2. .omitEmptyStrings().trimResults()则用于对分割后的数据进行再处理的过程。

    • .omitEmptyStrings() 用于排除分割后的空字符串数据;比如Splitter.on(',').omitEmptyStrings().split("a,,c,d") returns "a", "c", "d"
    • .trimResults() 用于清除分割后的字符串中的空格;同样还可以使用trimResults(CharMatcher)制定清除符合CharMatcher的字符。
    • 此外还有limit(int limit) 用于限制分割后的数据个数;比如Splitter.on(',').limit(3).split("a,b,c,d") returns "a", "b", "c,d"
  3. split(string) 处理分割数据。

以上两个方法的执行链路顺序都可以视为先定义规则后处理数据,需要注意的事情如果连续两次调用同一方法则只响应后面一个方法。比如.trimResults().trimResults(CharMatcher.is('_')) 则只会执行后面一个方法。

针对Joiner和Splitter的简要介绍就到此结束,如果需要更详细的了解使用方法,可以参考官方手册和API文档。

在下一篇文章里,我们将介绍与字符串处理相关的CharMatcher、Charsets、CaseFormat和Strings类的一些方法。

使用Intellij IDEA插件生成使用Guava的equals、hashCode 和toString方法

本文为翻译文章,原文为:Let your IntelliJ IDEA generate good looking equals, hashCode and toString with Google Guava

问题

在Java编程里,我们需要经常编写equals, hashCode 和 toString方法。老实讲,这通常只需要一个模板就能完成。

托各种优秀IDE的福,我们已经不需要自己手动来编写这些代码了。我们可以让IDE使用插件来帮我们完成这些工作。但是仍然有一个问题。通常由IDE自动生成的这些代码都非常难看。让我们来看下面一个POJO:

public class Beer {

    private String brand;
    private String type;
    private int degrees;
    private double alcoholPercentage;
    private List<String> ingredients;

    // constructor
    // getters, setters if needed

}

通用做法

我注意到几乎所有的主流IDE都能够生成符合需求的方法的代码,但是生成的equals、hashCode和toString方法代码通常看起来就像下面这样:

1. equals – long list of IF statements …

@Override
public boolean equals(final Object o) {
    if (this == o) {
        return true;
    }
    if (!(o instanceof Beer)) {
        return false;
    }

    final Beer beer = (Beer) o;

    if (Double.compare(beer.alcoholPercentage, alcoholPercentage) != 0) {
        return false;
    }
    if (degrees != beer.degrees) {
        return false;
    }
    if (!brand.equals(beer.brand)) {
        return false;
    }
    if (!ingredients.equals(beer.ingredients)) {
        return false;
    }
    if (!type.equals(beer.type)) {
        return false;
    }

    return true;
}

2. hashCode – confusing magic numbers, xors and shifts

@Override
public int hashCode() {
    int result;
    long temp;
    result = brand.hashCode();
    result = 31 * result + type.hashCode();
    result = 31 * result + degrees;
    temp = alcoholPercentage != +0.0d ? Double.doubleToLongBits(alcoholPercentage) : 0L;
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    result = 31 * result + ingredients.hashCode();
    return result;
}

3. toString – nasty string concatenation

@Override
public String toString() {
    return "Beer{" +
            "brand='" + brand + '\'' +
            ", type='" + type + '\'' +
            ", degrees=" + degrees +
            ", alcoholPercentage=" + alcoholPercentage +
            ", ingredients=" + ingredients +
            '}';
}

使用Guava库来解决

你有可能已经听过说或者已经使用过Google Guava,但是无论怎样,Google Guava都是一个提供了很多非常使用Java工具的精巧的库。我们可以使用Google Guava来重写上面的三个方法来使得代码显得更好看:

1. equals – army of IF statements transformed to chained call

@Override
public boolean equals(final Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final Beer other = (Beer) obj;
    return Objects.equal(this.brand, other.brand) && Objects
            .equal(this.type, other.type) && Objects
            .equal(this.degrees, other.degrees) && Objects
            .equal(this.alcoholPercentage, other.alcoholPercentage) && Objects
            .equal(this.ingredients, other.ingredients);
}

2. hashCode – 一行搞定

@Override
public int hashCode() {
    return Objects.hashCode(brand, type, degrees, alcoholPercentage, ingredients);
}

3. toString – consistent chained call

@Override
public String toString() {
    return Objects.toStringHelper(this)
            .add("brand", brand)
            .add("type", type)
            .add("degrees", degrees)
            .add("alcoholPercentage", alcoholPercentage)
            .add("ingredients", ingredients)
            .toString();
}

安装Intellij IDEA插件

对于equals和hashCode方法,在IDEA中还可以使用由Michal Jedynak编写的Equals and HashCode Deluxe Generator来生成代码。你可以在IDEA的插件库中搜索该该插件:

search plugin

使用这个插件非常简单,你只需要在代码编辑器内弹出鼠标右键就可以看到增加的插件选单,或者你也可以使用ALT+INSERT(MAC下使用CTRL+N)快捷键,如图所示:

context menu

至于toString方法,我们只需要在IDEA中增加一个模板即可。使用ALT+INS 快捷键然后选择toString()菜单选项。点击设置按钮并切换到模板(Templates)选项卡。在模板选单页点击”+”按钮:

add toString template

填写新模板的名称(诸如guava tostring等),然后把下面的代码拷贝到模板编辑页里。

public String toString() {
    #set ($autoImportPackages = "com.google.common.base.Objects")
    return Objects.toStringHelper(this)
    #foreach ($member in $members)
        .add("$member.name", $member.accessor)
    #end
        .toString();
}

使用新模板也非常简单,使用ALT+INS快捷键,选择toString(),然后确认选择正确的模板即可:

enter image description here

Enjoy code!

使用Google Guava来编写优雅的代码一Guava简介

以往我们在使用工具包的时候首先想到是著名的Apache的Commons系列,今天我要介绍的是同样出色,并且目前发展比Apache Commons系列更为迅速的Google Guava库。

Google Guava库是一个非常优秀的包含很多Java工具类集的库,广泛使用在Google公司内部,因此它可以被使用到几乎所有的Java项目中。Google Guava库最初发布在2007年,经过几年的更新发展目前其最新的版本为14.0-rc3。如果你之前有使用过Google collections库,那么请注意该库也已经被合并到Guava中了。

下面Guava项目网站的一段简介

The Guava project contains several of Google’s core libraries that we rely on in our Java-based projects: collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, and so forth. Each of these tools really do get used every day by Googlers, in production services.

从上面的简介中我们可以知道Guava本身是Google内部的核心Java基础库,其涵盖的内容包含集合、缓存、基础类型、并发处理、常用注解、字符串处理、I/O、网络、数学运算、反射、范围(Range)等方面。

为什么要使用Guava?

我相信很多人已经使用了很长时间的Apache commons系列库了,那么为什么还要推荐Guava呢?

关于这个问题的讨论很多,这里将Stackoverflow上面的一个关于这个问题的讨论总结写到此处。

compare2apache

对于我个人而言,Guava的开发活跃度和良好的质量保证(从上面的Stackoverflow的讨论来看,google的java开发主管带领的小组负责该库的主要维护工作)是我更愿意转而使用Guava的的原因之一。Guava几年发展下来各界发表的各类文章和其自身良好的文档风格也极大的帮助了该库的传播使用。

Guava相关文档和资源

首先推荐访问Guava的知识库API文档

Guava官方网站也给出非常有用的学习资源的地址:

其中最后一项由社区开发者整理的Guava资料最为全面,其根据时间顺序整理了Guava相关的文章、教程和教学录像等内容,是非常不错的学习资料。

使用Guava

如果你是使用Maven作为项目管理和集成工具的话,那么只需要拷贝下面一段代码到项目的pom.xml中即可。

<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>14.0-rc3</version>
</dependency>

Guava官方网站不再提供其lib包的下载,如果需要下载可以前往maven的中央库进行搜索并选择进行下载,当前的最新版本为14.0-rc3。

如果有兴趣可以前往Guava Overview Slide查看更多关于Guava的简介。

本系列文章希望能逐步将Guava相关的API在实际开发中的使用方法进行相对详细的介绍,因此本系列的更新可能并不会那么迅速。如果你现在就需要了解和掌握Guava的相关知识和技术,可以先查看前文所提到的学习资源。

下面是推荐的同类的系列文摘地址:

Codemunchies’ 4 part series on Google Collections and Guava:

该系列文章已经被网友翻译为中文,可以前往oschina.net查看:

A five-part, quite extensive tutorial from Sezin Karli:

周末两三记

上周末两天,呆家里一天,外出各种转账还款一天。偶尔的翻翻书,想了些事情,简单记录下。

关于选择

人这一生除了投胎这件事情是没有办法选择以外,其它的事情都会面临各种各样的选择。
有些时候可以从容面对,有些时候只能匆忙决策。
正因为这些选择的多样性,也造就每个人独一无二的人生。

就像《黑客帝国》里面的场景,墨菲斯给两种药丸让Neo选择。

选择红色药丸还是蓝色药丸,所面对的世界将截然不同。

我常说我有选择恐惧症,对于大不部分人来说,这是一个太明显不过的借口罢了。
从侧面来说,我对于做选择没有什么把握,或者说我对于承受选择后面的失败缺少认识。
用朋友的话来说:

你这人只是鬼点子多,但是让你在这么多鬼点子里面挑选一个,你又拖拖拉拉不肯做选择。
只有在遇到时间截止前才好不容易匆忙做个决定。等到决定出来了,又会陷在下一个路口的选择中。
从表现上来看,你这是明显的害怕承担选择后果的一种反应嘛。
所以我看你陷在工作这般,生活这般,感情这般都是你自己找的嘛。

关于编程

上周修改了一个小程序,当然我自己的编程能力也不是很好。但是在这个过程也有些想法。
怎么才能让自己编写的东西才能达到小而美呢?

顺着前面的选择来说,本来只是打算维护那个小程序的,并没有打算要做比较大的改动的。
因为很容易”修修补补又一年”的境地,加上之前开发的一个产品至今半年有余还没有出厂过。

由于这个工具在最近一个测试周期内出现了很多问题,才让我动了想要修改的念头。
花了几天时间整理原有工具的工具的工作流程,又花了几天时间写新版本的代码,目前基本上算还是完成了。

在这个过程中关于小程序的的想法比较多,尤其是该如何编写和维护小工具有了些想法。
但是想法比较乱。比如如何选择外部组件,如何协调内部组件的关系,如何进行质量保证,如何进行单元测试等等。
正因为比较乱,所以自己先还是把修改的功能给完成了再说。

回头来写篇具体的长文来说说这些想法。不过由于我个人太喜欢回头看,然后否定自己的做法。还是得小心自己的想法发散。

关于励志

我最近几年总觉得自己在各个方面的变化总体上是一个颓势,因此总是表现的郁郁寡欢,很不开心。
以至于经常有朋友说:你这天天像个无头鬼样的晃荡来晃荡去,过得啥意思嘛。

买了很多励志书,也去看了心理咨询。
终于做了一个选择:”回到研发队伍去”。
在经历了2012年一整年的事情后,我发现自己存在几个问题,需要自己给自己励志:

1, 选择的东西就是苹果树一般,你不能随心所欲的一个一个的去选择自己想吃的苹果,也不可能吃一半,然后再拿一个好的吃。
你最好是拿一个杆子,打一杆子,吃掉下来那个。主要杆子质量好,要站在你选的平果下面。
2, 选错了苹果,先吃掉它,咱不是还会留下果核嘛,留在地里。好生照看,辛苦总会有结果的。
3,可能有好多摘苹果的人。多学习别人是怎么摘苹果,别人是怎么保存苹果的。
4,苹果的品种很多,不要太贪心了。一段时间内好生吃这么一个苹果,吃着手里的苹果,也要不忘给地里的那个苹果打点药呗。
5,练好挥杆子的动作,


书单,欢迎借阅:
1. 《思考,快与慢》
2. 《少有人走的路》
3. 《心理调节术》
4. 《瞬变》
5. 《意志力》
6. 《遇见未知的自己》
7. 《The Tools – 工具》
8. 《暗时间》
9. 《当心理学遇上博弈学》
10.《你可以,爱》
11.《活出全新的自己》
12.《不抱怨的世界》

年度总结 – 2012

今天是2012年12月31日,明天就是2013年了。
很显然,我们大家都安然的度过了传说的”世界末日”,开始走入一个新的玛雅纪元了。
年度总结写了几年了,中间断过一年,2011年的年度没有写。现在也就不再为为什么不写而烦恼了。
过去的就过去了吧。我还是要往下面走嘛。

2012年对于我来说,就是尝试摆脱纠结的一年,并且这个过程还会延续到下一年去。从目前来看,取得了一定的效果,但是自己还是会时不时想问这就是我想要的么?

接下来我将从以下几个方面来说明过去这一年中的变与不变,其中包含:工作和公司、技术积累、感情生活和妹子、家庭和朋友以及资产状况

工作和公司

对于工作和公司来说,不变是我还留在我的第一家公司–合众–工作,变化的是工作的内容和范围发生了变化。
上半年我一直在断断续续的从事部门的基础规范和内部系统的研发,期间有代码规范、文库系统、问答系统、质量控制系统等一系列系统的开发,但是基本上都是我一个人在开展工作的。也正因为这半年内的沟通乏善可陈,导致自己一直郁郁不振,加上从事的工作内容让我对工作本身产生怀疑,尤其是对于团队的认识让我感觉很迷茫。

期间为了解决这些问题,进行了几个月的心理治疗和生理治疗,尽管没有看到效果,加上我本来一直依赖的杨波同志也因为怀孕开始休产假的关系,所以自己完全独立的做了一个决定:回到开发团队内部去

于是下半年接手了一个WEB系统的新版本开发,最近又开始介入公司ETL产品的项目进程中。从目前来看,我自己觉得WEB系统的开发还算不错,但是仍然存在很多问题,尤其是在进度和培养人员方面存在很大的问题。另外这个系统的改造完全是采用了跟公司基础结构不一样的开发结构,中间也是花了不少时间在学习。

所以回到开发团队去这个选择的效果并不明显,加上维护内部系统的关系,手头上还是有不少事情的。如何进行梳理是新的一年需要考虑的事情。

从我2006年加入合众开始,确实几乎每年都些不一样的事情,就我个人而言,我还是觉得我自己跟不上公司的发展步伐。尤其是在近三年中,加上由于个人没有很好的区分工作和生活的关系,这种感觉是日趋强烈。

明年要减少自己在部门维护和其它事务上的时间量,正确让这些内部系统或者事务可以交给其他人去负责,希望有专职人员能接手这些事务。

技术积累

2011年中我接触到了NodeJS,同年底,我又接触了Objective-C。如果要评选我个人的年度语言的话,那么2012年肯定是NodeJS。2012年虽然没有深入NodeJS,但是也陆陆续续写了不少跟Node有关系的小工具和小系统,所以NodeJS在我心目中算了留下了不少美好的影子。

2012年新了解的语言是Scala,接触的比较早,但是一直比较浅薄,也没有写出过什么具体的系统或者案例出来。

之所以会学习NodeJS和Scala,都是希望能学习到多并发和异步编程的一些经验。

我记得我在2011年底的时候,跟同事讨论公司的技术方向,其中也有提到这个方面,实时数据处理与挖掘。前几天回头看了下这个思维导图,发现公司目前的产品线基本到涵盖了我们之前讨论的方向。从目前来看,这个结构跟一些互联网公司的结构很像,我坚信2013年中期或者年底,公司的部分产品开始需要针对实时数据和离线数据做一些不同的产品组合。

感情生活和妹子

2012年对于问我来说,感情生活基本是一个空白,怎么说呢。这有可能是因为2011年底的一个决定引起的吧。
可以说这是我2012年上半年情绪一直不稳定,以至于还需要进行心理治疗的关键。

如果没有记错的话,我年中决定回到开发团队中的时候,同时也做了另外一个决定: 32岁前结婚

因此2013年的感情生活将决定我这个目标能否达成的一个关键了。但是矛盾的是,我又觉得这样为了结婚而结婚,也还是跟以前一样是委屈了人家女孩子啊。

但是不管怎么说,2012年,妹子不在身旁啊!13年加油吧。

家庭和朋友

这一年主动联系家人和朋友的次数非常少,内心总是在逃避着他们的关心。

周围的朋友们也陆陆续续结婚生子,周围的各种聊天内容也陆陆续续转变为结婚、买房、育儿等等内容。
当然也有不少让我感觉到揪心的事情吧,各种不知道如何帮助的事情。

房子和夫妻关系算是我这里的年度词汇了。

当然买房并不是我没有妹子的关键因素,但是前提我得有尽快买房的意图才行。从目前来看,由于我个人之前几年的过度消费,半年内购房的可行性太低了,因此2013年总体的资金是需要进行控制的。或许可以考虑进行一些稳健性的投资吧。-–|||||–有点扯远的话题。

总体上我希望我的家人和朋友都能健康愉快的生活。有好多人几乎不怎么联系,但是我依然知道你在。有好多人我经常联系,但是也没有真正的去见过一次面,请原谅我吧。以前联系得多了,现在少了,可能是你有家室了吧。

资产状况

我本来不准备把这一项拿出来作为总结的,但是考虑到前面提到的买房和妹子的关系,尽管这二者没有必然关系。

结合我32岁前结婚的新目标,因此婚前积累一定的资金应该是一个非常重要的环节了。

2012年的消费状态和之前几年的消费状况总体一致,准确来说就是消费过多过高。

  • 2012年2月14日购买了魅族MX手机,坑爹的魅族啊。不过尽管被坑,我还是推荐性价比和质量都有要求的人可以考虑魅族。
  • 2012年4月初购买了New Ipad 32G。
  • 2012年美金消费明显增多,预计在2000-3000美金左右的消费,主要集中在购买外文图书和软件
  • 2012年图书的购买量仍然巨大,但是消化的内容却不多。
  • 2012年信用卡每月基本消费在5K左右。具体的内容也各种各样。
  • 2012年4月开始定期买彩票了。到现在消费也不少了。这个要控制

因此2013年如果需要达到资金积累的目标的话,对于消费是需要进行一定的控制的。

可以考虑学习小杨同学进行基金理财,或者干脆保留一定的固定资金存入定期存款中。

我一直主张钱给老婆管理。

附:

2012年个人年度报告

  • 年度词汇 : 纠结
  • 年度事件 : 回到研发
  • 年度技术 : NodeJS
  • 年度他人关注词汇 : 房子和关系
  • 年度妹子 : LCH (不断被提起)
  • 年度家庭事件 : 老哥买房和侄女周岁
  • 年度朋友 : 小杨同学(结婚了、买房了,赶紧生娃吧)
  • 年度图书 : 看见、正能量

2013年的关键词预计:

  • 基金
  • 定期储蓄
  • 妹子
  • 用户体验
  • Scala
  • NodeJS
  • ETL

Web应用开发模式选择与建立

本文撰写于新项目开始阶段,手头上有正在准备改造一个Java Web的项目。项目本身的功能和技术要点并不复杂,主要问题是集中在用户操作便捷性和数据展现方面的问题。因此这个项目的第一阶段是针对这些用户操作层面的问题做一些改动。经过一段时间的代码阅读,发现如果依然维持现有代码的体系改动为新版本的界面需求的话,尤其是考虑到未来的维护性要求,页面和后台交互的数据交互模型也需要考虑起来了,因此在这个过程中也就形成了本文。

在目前的流行的JAVA Web开发模式中,页面数据还是基于JSP或者模板模式开发的。那么就意味这JSP页面或者模板都需要经过编译才能够反馈并展示给用户。那么就意味着html页面内部会分布着很多的标签或者模板的tag数据。尽管模板技术提供了一定的界面定制的可能性,但是对于界面功能调整来说仍然存在复杂的地方。虽然使用这些技术可能降低后端开发人员对前台页面渲染技术的关注,但是却增加了前端人员对界面功能维护的难度。

现今SSH框架或者SSH框架的各种变体还是很受大众欢迎的,当然也有后起之秀,诸如Play!,Play! 2,Wicket,Apache Click等。在各种会议上也有关于如何选择Web开发框架的讨论和介绍,这里我并不想对这些讨论进行重复申述。如果有兴趣的话,可以Google或者访问以下几个地址(如果不能访问,请学习如何翻墙):

  1. How to choose a web framework and be surprised
  2. Choose the Right Java Web Development Framework
  3. Choose Java Web Framework

作为一个Web开发人员,无论你是新手还是老手,相信或多或少的了解了诸如MVC、MVVM、MOVE开发模式的各种概念。我也不准备针对这些概念进行更多的说明。如果有不明白的地方,可以通过下面的一些链接获得一点信息:

  1. 百度百科–MVC模式
  2. 百度百科–MVVM模式
  3. MVC模式不好用?何不试试MOVE

在之前的项目中,我主要使用的Java Web框架是Struts,也偶尔使用过Spring MVC,最近还使用过Play! 2。这里我不会比较这些技术框架的性能上的差异和具体实现细节上的不同。就我个人来说,我觉得Play! 2是非常不错的JAVA Web框架选择,但是它仍然不能让我满意。主要是基于它的模板技术以及对JAVA语言的支持偏弱。因为Play!2更多的是偏向Scala的,如果要完全发挥Play的作用,还需要学习Scala语言。

其实我下面所讲的内容跟JAVA Web框架选择本身并没有很直接的关系,只是出于我作为一个JAVA开发人员的角度来说,我更多也是围绕JAVA方面来描述。

前后端分离,以数据服务为中心

前端页面交互部分应当与后端数据处理部分分割开来,意即页面和后端完全通过JSON或者其他数据格式进行数据交互。这样就意味着后端服务器只提供数据服务,这些服务包含数据接受与返回,接受后验证等多个部分,服务的内部处理结果都以JSON形式返回给前端页面。前端页面根据相应的返回数据进行数据展示、页面提示、服务数据校验结果等。

这么做的好处有:

  1. 节省页面功能调整难度,避免前后端混杂带来的困难。将专业的事情交给专业的人来处理。
  2. 降低语言耦合度,容易切换开发语言。随着数据服务的提供,这些部分的数据可以给更多的WEB开发语言。除了支持JAVA语言外,还能提供给其它语言的开发程序使用。 3.移动数据交换接口。这里主要针对Web-APP来说,可以更方面为移动中端的数据请求提供服务。

前端模块化,组件化,进一步隔离页面交互与数据结果

前端页面也应提倡模块化、组件化开发模式。在以往的开发过程中,经常可以看到各种JS文件和代码的各处分布。由于缺乏模块化和组件依赖性的管理,导致对于JS代码维护的成本不小。也使得JS本身的测试覆盖一直不高。
随着Web2.0的发展以及NodeJS等服务端JS的兴起,对于JS的各个方面的研究也与日俱增。
对于JS文件的依赖管理和异步加载的管理,涌现了RequireJSSeaJSHeadJS等管理库,这些组件都能很好根据模块定义的需求来进行JS文件依赖的管理。
使用Javascript MVC 库。出于同样的原因,MVC的理念也从后端应用发展到前端页面交互层面,并且已经取得非常的发展。比如BackboneJS、SpineJS、EmberJS等。对于这些框架的优劣,我是没有发言权的,因为我只使用过其中的一两种。如果你想知道如何选择前端MVC框架,可以访问以下链接来深入了解:

  1. 基于MVC的JavaScript Web 富应用开发–图书
  2. The Top 10 Javascript MVC Frameworks Reviewed
  3. HTML5 TODO应用的各种MVC实现
  4. Javascript MVC最佳实践 –个人技术博客.

简化后端数据处理流程

从WEB开发的角度来说,Web数据服务不能仅仅是根据请求提供相应的数据结果而已,还应当有数据验证的结果。比如用户登陆校验、用户在线失效校验、数据提交校验、异常结果编码等。
将页面渲染和跳转的部分剥离后,后端引擎对于前端页面交互来说仍然需要处理两个地方:

  1. 静态资源的处理
  2. 伪页面的返回
  3. 前端页面数据渲染方法

1,静态资源的处理。
后端服务器引擎,仍然需要处理静态资源文件的请求,比如图片,CSS样式,JS文件等。

2,伪页面的返回
在主要模块之间跳转的的数据来说,后端服务仍然需要返回无数据的页面文件。

3.前端页面数据渲染方法
这些页面的数据填充则由前端根据request请求参数或者其它方法再次获得实际数据,并在客户端进行填充渲染。

需要关注的问题

  1. Session管理
  2. 用户在线与自动离线跟踪
  3. 抢占登陆与用户踢出处理

在这种情况下用户在线的判断更多的依赖Cookie的存储和判断了。

我个人推荐的几种组合方案:

  • 前端组合
    1,SeaJS+BackboneJS
    2,SeaJS+Module(不采用MVC模式。只遵循模块化依赖)。

  • 后端组合:

    1. MVC方面(视图被弱化为数据服务)仍然可以选择常用框架或者原生Servlet。推荐使用Spring MVC,NDOF(基于Servlet的基础包装,接近Spring MVC),Struts2等。
    2. 逻辑处理部分可以采用Spring,或者简单包装的逻辑服务层面。
    3. 数据访问层的选择则更丰富,可以选择如Hibernate、ibatis、Spring JDBC,简单的可以自己包装的DAO层。

我之前听过雪球网的同学分享他们使用NodeJS的地方(雪球NodeJS半年经验谈),就采用类似的方法。不过他们相对更激进,为了考虑到前端开发人员的问题,直接弃用JSP页面方案(理由是JSP太难维护),转换为NodeJS的产品,而后端数据来源还是由之前的JAVA应用提供,中间需要注意的是他们还特别增加了第三方的session管理(这是由于当时NodeJS ExpressJS不提供Session管理,据说express 3.0提供了。)