解决WordPress无法显示主题列表的问题

本博客系统之前使用的是Typecho,本来已经足够使用了,但是由于Typecho团队转而重心集中经营SegmentFault上了,拖拖拉拉几年了系统一直没有更新,不过这也不能责怪人家团队。但是听说最近又再更新了,这是个不错的消息。

系统迁回Wordpress有一段日子了,距离上一篇博客也有些日子了。今天写点东西是迁回Wordpress的一点小问题的解决方法。

问题

安装Wordpress后,在后台主题管理界面中无法查看所有已经上传的主题列表,永远只能看到一个当前主题的一些信息。

分析

系统部署在linode的vps上,使用的服务器是lnmp的一键安装版本,最新为0.9。

目录访问权限肯定没有问题。

目测wordpress显示主体列表跟typecho获得主题列表的函数方法不一致。

猜想wordpress估计使用了类似文件夹扫描的方法来获得主体列表,类似java的fileList。

后经过查看php.ini,发现lnmp0.9版本的php配置了禁用了一些函数,其中就有scandir函数,

查阅php的函数说明:

scandir() 函数返回一个数组,其中包含指定路径中的文件和目录

解决

  1. 修改php.ini文件内容

    找到/usr/local/php/etc/php.ini文件。
    修改php.ini文件中的disable_functions,位于314行。
    将后面的禁用的函数列表中的scandir删除,并保存文件。

  2. 重启php-fpm或者调用lnmp重启

    使用/etc/init.d/php-fpm restart重启或使用lnmp restart重启。

备注

  • 本次问题出现在使用lnmp 0.9的版本中,如果非lnmp一键安装版本,可以先确认php是否禁用了scandir函数,如果没有禁用,请再确认文件夹权限问题。
  • 操作中需要注意,不要误删除了任何配置文件。

使用Google Guava来编写优雅的代码 – 集合4(BiMap)

BiMap提供了一种新的集合类型,它提供了key和value的双向关联的场景。

通常情况下,我们在使用Java的Map时,往往是通过key来查找value的,但是如果出现下面一种场景的情况,我们就需要额外编写一些代码了。

首先来看下面一种表示标识序号和文件名的map结构。

Map<String,String> logfileMap = Maps.newHashMap();
logfileMap.put("1","a.log");
logfileMap.put("2","b.log");
logfileMap.put("3","c.log");

当我们需要通过序号查找文件名,很简单。但是如果我们需要通过文件名查找其序号时,我们就不得不遍历map了。当然我们还可以编写一段Map倒转的方法来帮助实现倒置的映射关系。

// Generic method to reverse map.
public %lt;S,T> Map<T,S> getInverseMap(Map<S,T> map) {
    Map<T,S> inverseMap = new HashMap<T,S>();
    for(Entry<S,T> entry: map.entrySet()) {
        inverseMap.put(entry.getValue(), entry.getKey());
    }
    return inverseMap;
}

上面的代码可以帮助我们实现map倒转的要求,但是还有一些我们需要考虑的问题:

  1. 如何处理重复的value的情况。不考虑的话,反转的时候就会出现覆盖的情况.
  2. 如果在反转的map中增加一个新的key,倒转前的map是否需要更新一个值呢?

在这种情况下需要考虑的业务以外的内容就增加了,编写的代码也变得不那么易读了。这时我们就可以考虑使用Guava中的BiMap了。

使用BiMap

BiMap的使用也非常简单,可以先看下面一段简单示例:

BiMap<String,String> logfileMap = HashBiMap.create();

//像通常的Map那样初始化和使用
logfileMap.put("1","a.log");
logfileMap.put("2","b.log");
logfileMap.put("3","c.log");

System.out.println(logfileMap.get("1"));

BiMap<String,String> filelogMap = logfileMap.inverse();
System.out.println(filelogMap.get("c.log"));

看上去代码确实简洁了点,但是我们有些事情时需要注意的。

数据的强制唯一性

在使用BiMap时,会要求Value的唯一性。如果value重复了则会抛出现。

logfileMap.put("1","a.log");
logfileMap.put("2","a.log");//会抛出   IllegalArgumentException异常

但是如果你确实有可能会插入重复的value,那么你可以选择forcePut方法。当时需要需要注意的是前面的key也会被覆盖了。

logfileMap.put("1","a.log");
logfileMap.forcePut("2","a.log");
System.out.println(logfileMap.get("2"));//打印a.log
System.out.println(logfileMap.get("1"));//null

理解inverse方法

inverse方法会返回一个反转的BiMap,但是注意这个反转的map不是新的map对象,它实现了一种视图关联,这样你对于反转后的map的所有操作都会影响原先的map对象。

filelogMap.put("d.log","4");
System.out.println(logfileMap.get("4"));//打印d.log   

BiMap的实现类

BiMap提供了多种实现类,比如HashBiMap、ImmutableBiMap、EnumBiMap、EnumHashBiMap。

如果需要了解更多BiMap的内容可以前往查看以下资源

使用Google Guava来编写优雅的代码 – 集合3(Multimap)

前面针对Guava中的集合已经介绍了集合的创建和不可变集合以及Guava提供的新增加的集合类型Multiset,今天将继续为大家介绍Guava中的集合接口类Multimap及其一些相关的API用法。

由来

在我们日常的开发中,经常会需要使用到下面一种数据结构

Map<String,List<MyClass>> myClassListMap test2  
          = new HashMap<String,List<MyClass>>()

比如每个用户有多个手机号码或者联系地址,在这种情况下就有可能使用到以上的数据结构了。但是在使用这种数据结构时,我们需要关注的内容太多了,比如:

  • 在为某一个key的value添加元素时,我们需要先确认该value是否为空
  • 删除某一个key对应的value中某一个元素时,就需要进行遍历

通常情况我们编写的代码会像下面一段代码:

void putMyObject(String key, Object value) {  
    List<Object> myClassList = myClassListMap.get(key);  
    if(myClassList == null) {  
        myClassList = new ArrayList<object>();  
        myClassListMap.put(key,myClassList);  
    }  
    myClassList.add(value);  
}

这样编写代码的过程让我们不得不短暂的从关注的业务处理层面更多的转移到实现该数据结构的操作上,编写这样的代码也让代码显得有点难以阅读。

使用Guava Multimap

Guava对于Multimap提供了很多实现类,类似上面的功能需求,我们可以选择使用ArraylistMultimap。

首先让我们来看一段使用了Guava Multimap后的代码:

public class MutliMapTest {  
    public static void main(String... args) {  
      Multimap<String, String> myMultimap = ArrayListMultimap.create();  

      // Adding some key/value  
      myMultimap.put("Fruits", "Bannana");  
      myMultimap.put("Fruits", "Apple");  
      myMultimap.put("Fruits", "Pear");  
      myMultimap.put("Vegetables", "Carrot");  

      // Getting the size  
      int size = myMultimap.size();  
      System.out.println(size);  // 4  

      // Getting values  
      Collection<string> fruits = myMultimap.get("Fruits");  
      System.out.println(fruits); // [Bannana, Apple, Pear]  

      Collection<string> vegetables = myMultimap.get("Vegetables");  
      System.out.println(vegetables); // [Carrot]  

      // Iterating over entire Mutlimap  
      for(String value : myMultimap.values()) {  
       System.out.println(value);  
      }  

      // Removing a single value  
      myMultimap.remove("Fruits","Pear");  
      System.out.println(myMultimap.get("Fruits")); // [Bannana, Pear]  

      // Remove all values for a key  
      myMultimap.removeAll("Fruits");  
      System.out.println(myMultimap.get("Fruits")); // [] (Empty Collection!)  
    }  
} 

从上面的代码中我们可以发现Multimap提供了以下API:

  • put(K,V) :为指定的key添加一个value元素
  • putAll(K,Iterable) : 为指定的Key添加一组value元素。
  • remove(K,V) : 移除Key对应的某一个value元素。如果执行成功则返回true
  • removeAll(K) : 移除该Key所对应的所有value。
  • replaceValues(K,Iterable) : 替换Key所对应的Values。
  • values()

如果需要知道更多的API及其用法可以前往Guava的官方站点进行查看。

Multimap的实现类

实现类 Keys 的行为类似… Values的行为类似..
ArrayListMultimap HashMap ArrayList
HashMultimap HashMap HashSet
LinkedListMultimap* LinkedHashMap* LinkedList*
LinkedHashMultimap** LinkedHashMap LinkedHashSet
TreeMultimap TreeMap TreeSet
ImmutableListMultimap ImmutableMap ImmutableList
ImmutableSetMultimap ImmutableMap ImmutableSet

以上的实现类中除去不可修改的以外,其它的实现类均支持null的键和值。

更多资源

ExtJS Grid 分组中groupTextTpl的参数使用

在我们使用ExtJS的表格组件(Grid)中经常会需要使用到分组的情况,像使用业务分组,然后组内显示该业务下挂的服务列表信息。通常情况下,分组数据的方式就有两种:服务端分组数据返回或者是客户端分组。在ExtJS的API文档中给出的示例也非常简单,其代码如下:

var grid = new Ext.grid.GridPanel({
    // A groupingStore is required for a GroupingView
    store: new Ext.data.GroupingStore({
        reader: reader,
        data: xg.dummyData,
        sortInfo:{field: 'company', direction: "ASC"},
        groupField:'industry'
    }),

    columns: [
        {id:'company',header: "Company", width: 60, sortable: true, dataIndex: 'company'},
        {header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
        {header: "Change", width: 20, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney},
        {header: "Industry", width: 20, sortable: true, dataIndex: 'industry'},
        {header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
    ],

    view: new Ext.grid.GroupingView({
        forceFit:true,
        // custom grouping text template to display the number of items per group
        groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
    }),

    frame:true,
    width: 700,
    height: 450,
    collapsible: true,
    animCollapse: false,
    title: 'Grouping Example',
    iconCls: 'icon-grid',
    renderTo: document.body
});

这里需要指出的时,在大多数文档和示例代码中归于分组列头的显示都是使用本示例中的代码:

groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'

很少有代码提及关于groupTextTpl的其它相关内容。

在API文档中提及groupTextTpl的介绍中提到了以下几个可以使用的属性调用:

  • group 显示分组列名的名称,如果使用emptypGroupText属性,那么当原始Group的值为空时,将会显示emptyGroupText的值。
  • gvalue 显示分组列的原始值,如果原始值为空,显示也是空。不会调用emptyGroupText。
  • text 如果配置了header属性,则会使用header属性的值加上原始分组列内的值。如果原始值为空,会调用emptyGroupText的值。
  • groupId ExtJS对每一个分组分配的独立的编号,通常其组成如下:ext-{生成器编码如gen20,gen19等}-gp-{分组列名}-{分组列值}
  • startRow 每一个组在整个Grid当前分页中的起始行的索引,索引从0开始。
  • rs 每个组包含的结果集。
  • cls 分组头样式的class名称
  • style : 分组头样式

比如在一些场景下,有业务和服务之分,服务是从属于业务的,业务和服务均会提供相应的编号和名称,这种情况下会按照业务编号分组,而希望显示的分组头内容为业务名称,而组内排序需要使用服务编号来排序。在这种情况下,我们可以这么定义:

比如业务名称和编号的列定义为appId,appName,服务名称和编号的列定义为serviceId,serviceName。

var myDataStore = new Ext.data.GroupStroe({
    autoLoad: false,
    url:'loadata.do',
    sortInfo:{field:'serviceId',direction:'DESC'}, // 这个属性必须配置,顺序可以为DESC或者ASC
    reader: reader,
    groupField:'appId'
});

//定义Grid,不显示业务名称、业务编号和服务编号
var mygrid = new Ext.grid.GridPanel  ({
    store: mydatastore,
    columns: [
        {header: '业务名称', width:45, dataindex: 'appName',hidden:true,groupable:false,dataIndex:"appName"},
        {header: '业务编号', width:55, dataindex: 'appId',hidden:true,groupable:false,dataIndex:"appId"},
        {header: '服务编号', width:70, dataindex: 'serviceId',hidden:true,groupable:false,dataIndex:"serviceId"},
        {header: '服务名称',  width:90, dataindex: 'serviceName'},
        {header: '其它内容',  width:40, dataindex: 'otherInfo'}
    ],
    stripeRows: true,
    autoSizeColumns: true,
    autoSizeGrid: true,
    title:'Your Grid Title',
    collapsible: false,
    animCollapse: false,
    height: 445,
    columnLines: true
});

下面如果我们需要在分组头显示业务名称的时候,我们就需要在GridPanel对象中定义view对象:

view: new Ext.grid.GroupingView({
    forceFit:true,
    groupTextTpl: '分组 : {text}|{[ values.rs[0].data["appName"] ]} ({[values.rs.length]} {[values.rs.length > 1 ? "Sevices" :        "Sevice"]})'
})

使用这个视图定义后,显示的分组头部文字效果就类似如下:

//比如数据形如:
//[
// {appId:'1',appName:'业务1',serviceId:'1001',serviceName:'服务1'},
// {appId:'1',appName:'业务1',serviceId:'1002',serviceName:'服务2'},
// {appId:'2',appName:'业务2',serviceId:'2001',serviceName:'服务3'},
// {appId:'2',appName:'业务2',serviceId:'2002',serviceName:'服务4'},
// {appId:'3',appName:'业务3',serviceId:'3001',serviceName:'服务5'}
//]
// 显示在表格中的头部效果如下:
// 分组: 1 | 业务1 (2 Services)
// 分组: 2 | 业务2 (2 Services)
// 分组: 3 | 业务3 (1 Service)

撰写本文的目的是因为在实际的产品开发中遇到类似的需求,为了不修改了后台的排序代码,于是进行了以上的尝试。这说明在使用ExtJS时,其API文档可能有些内容并没有提到,但是要能从其文档中慢慢发现API的规律,并予以尝试,肯定会带来不少帮助的。

官方示例中只给出了values.rs.length的示例,那么从这里面我们就能发现values.rs肯定是这个组内的所有结果集了,这样要实现我们本身的需求目的就非常简单了。

使用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: