痛点

在Java开发中我们要面对各种各样的类型转换问题,尤其是从命令行获取的用户参数、从HttpRequest获取的Parameter等等,这些参数类型多种多样,我们怎么去转换他们呢?常用的办法是先整成String,然后调用XXX.parseXXX方法,还要承受转换失败的风险,不得不加一层try catch,这个小小的过程混迹在业务代码中会显得非常难看和臃肿。

Convert类

Convert类可以说是一个工具方法类,里面封装了针对Java常见类型的转换,用于简化类型转换。Convert类中大部分方法为toXXX,参数为Object,可以实现将任意可能的类型转换为指定类型。同时支持第二个参数defaultValue用于在转换失败时返回一个默认值。

Java常见类型转换

  1. 转换为字符串:

    1. int a = 1;
    2. //aStr为"1"
    3. String aStr = Convert.toStr(a);
    4. long[] b = {1,2,3,4,5};
    5. //bStr为:"[1, 2, 3, 4, 5]"
    6. String bStr = Convert.toStr(b);Copy to clipboardErrorCopied
  2. 转换为指定类型数组:

    1. String[] b = { "1", "2", "3", "4" };
    2. //结果为Integer数组
    3. Integer[] intArray = Convert.toIntArray(b);
    4. long[] c = {1,2,3,4,5};
    5. //结果为Integer数组
    6. Integer[] intArray2 = Convert.toIntArray(c);
  3. 转换为日期对象:

    1. String a = "2017-05-06";
    2. Date value = Convert.toDate(a);
  4. 转换为集合

    1. Object[] a = {"a", "你", "好", "", 1};
    2. List<?> list = Convert.convert(List.class, a);
    3. //从4.1.11开始可以这么用
    4. List<?> list = Convert.toList(a);

    其它类型转换

  5. 标准类型

通过Convert.convert(Class<T>, Object)方法可以将任意类型转换为指定类型,Hutool中预定义了许多类型转换,例如转换为URI、URL、Calendar等等,这些类型的转换都依托于ConverterRegistry类。通过这个类和Converter接口,我们可以自定义一些类型转换。详细的使用请参阅“自定义类型转换”一节。

  1. 泛型类型

通过convert(TypeReference<T> reference, Object value)方法,自行new一个TypeReference对象可以对嵌套泛型进行类型转换。例如,我们想转换一个对象为List<String>类型,此时传入的标准Class就无法满足要求,此时我们可以这样:

  1. Object[] a = { "a", "你", "好", "", 1 };
  2. List<String> list = Convert.convert(new TypeReference<List<String>>() {}, a);

通过TypeReference实例化后制定泛型类型,即可转换对象为我们想要的目标类型。

半角和全角转换

在很多文本的统一化中这两个方法非常有用,主要对标点符号的全角半角转换。
半角转全角:

  1. String a = "123456789";
  2. //结果为:"123456789"
  3. String sbc = Convert.toSBC(a);

全角转半角:

  1. String a = "123456789";
  2. //结果为"123456789"
  3. String dbc = Convert.toDBC(a);

16进制(Hex)

在很多加密解密,以及中文字符串传输(比如表单提交)的时候,会用到16进制转换,就是Hex转换,为此Hutool中专门封装了HexUtil工具类,考虑到16进制转换也是转换的一部分,因此将其方法也放在Convert类中,便于理解和查找,使用同样非常简单:
转为16进制(Hex)字符串

  1. String a = "我是一个小小的可爱的字符串";
  2. //结果:"e68891e698afe4b880e4b8aae5b08fe5b08fe79a84e58fafe788b1e79a84e5ad97e7aca6e4b8b2"
  3. String hex = Convert.toHex(a, CharsetUtil.CHARSET_UTF_8);

将16进制(Hex)字符串转为普通字符串:

  1. String hex = "e68891e698afe4b880e4b8aae5b08fe5b08fe79a84e58fafe788b1e79a84e5ad97e7aca6e4b8b2";
  2. //结果为:"我是一个小小的可爱的字符串"
  3. String raw = Convert.hexStrToStr(hex, CharsetUtil.CHARSET_UTF_8);
  4. //注意:在4.1.11之后hexStrToStr将改名为hexToStr
  5. String raw = Convert.hexToStr(hex, CharsetUtil.CHARSET_UTF_8);

因为字符串牵涉到编码问题,因此必须传入编码对象,此处使用UTF-8编码。 toHex方法同样支持传入byte[],同样也可以使用hexToBytes方法将16进制转为byte[]

Unicode和字符串转换

与16进制类似,Convert类同样可以在字符串和Unicode之间轻松转换:

  1. String a = "我是一个小小的可爱的字符串";
  2. //结果为:"\\u6211\\u662f\\u4e00\\u4e2a\\u5c0f\\u5c0f\\u7684\\u53ef\\u7231\\u7684\\u5b57\\u7b26\\u4e32"
  3. String unicode = Convert.strToUnicode(a);
  4. //结果为:"我是一个小小的可爱的字符串"
  5. String raw = Convert.unicodeToStr(unicode);

很熟悉吧?如果你在properties文件中写过中文,你会明白这个方法的重要性。

编码转换

在接收表单的时候,我们常常被中文乱码所困扰,其实大多数原因是使用了不正确的编码方式解码了数据。于是Convert.convertCharset方法便派上用场了,它可以把乱码转为正确的编码方式:

  1. String a = "我不是乱码";
  2. //转换后result为乱码
  3. String result = Convert.convertCharset(a, CharsetUtil.UTF_8, CharsetUtil.ISO_8859_1);
  4. String raw = Convert.convertCharset(result, CharsetUtil.ISO_8859_1, "UTF-8");
  5. Assert.assertEquals(raw, a);

注意 经过测试,UTF-8编码后用GBK解码再用GBK编码后用UTF-8解码会存在某些中文转换失败的问题。

时间单位转换

Convert.convertTime方法主要用于转换时长单位,比如一个很大的毫秒,我想获得这个毫秒数对应多少分:

  1. long a = 4535345;
  2. //结果为:75
  3. long minutes = Convert.convertTime(a, TimeUnit.MILLISECONDS, TimeUnit.MINUTES);

金额大小写转换

面对财务类需求,Convert.digitToChinese将金钱数转换为大写形式:

  1. double a = 67556.32;
  2. //结果为:"陆万柒仟伍佰伍拾陆元叁角贰分"
  3. String digitUppercase = Convert.digitToChinese(a);

注意 转换为大写只能精确到分(小数点儿后两位),之后的数字会被忽略。

原始类和包装类转换

有的时候,我们需要将包装类和原始类相互转换(比如Integer.classs 和 int.class),这时候我们可以:

  1. //去包装
  2. Class<?> wrapClass = Integer.class;
  3. //结果为:int.class
  4. Class<?> unWraped = Convert.unWrap(wrapClass);
  5. //包装
  6. Class<?> primitiveClass = long.class;
  7. //结果为:Long.class
  8. Class<?> wraped = Convert.wrap(primitiveClass);

自定义类型转换-ConverterRegistry

由来

Hutool中类型转换最早只是一个工具类,叫做“Convert”,对于每一种类型转换都是用一个静态方法表示,但是这种方式有一个潜在问题,那就是扩展性不足,这导致Hutool只能满足部分类型转换的需求。

解决

为了解决这些问题,我对Hutool中这个类做了扩展。思想如下:

  • Converter 类型转换接口,通过实现这个接口,重写convert方法,以实现不同类型的对象转换
  • ConverterRegistry 类型转换登记中心。将各种类型Convert对象放入登记中心,通过convert方法查找目标类型对应的转换器,将被转换对象转换之。在此类中,存放着默认转换器自定义转换器,默认转换器是Hutool中预定义的一些转换器,自定义转换器存放用户自定的转换器。

通过这种方式,实现类灵活的类型转换。使用方式如下:

  1. int a = 3423;
  2. ConverterRegistry converterRegistry = ConverterRegistry.getInstance();
  3. String result = converterRegistry.convert(String.class, a);
  4. Assert.assertEquals("3423", result);

自定义转换

Hutool的默认转换有时候并不能满足我们自定义对象的一些需求,这时我们可以使用ConverterRegistry.getInstance().putCustom()方法自定义类型转换。

  1. 自定义转换器

    1. public static class CustomConverter implements Converter<String>{
    2. @Override
    3. public String convert(Object value, String defaultValue) throws IllegalArgumentException {
    4. return "Custom: " + value.toString();
    5. }
    6. }
  2. 注册转换器

    1. ConverterRegistry converterRegistry = ConverterRegistry.getInstance();
    2. //此处做为示例自定义String转换,因为Hutool中已经提供String转换,请尽量不要替换
    3. //替换可能引发关联转换异常(例如覆盖String转换会影响全局)
    4. converterRegistry.putCustom(String.class, CustomConverter.class);
  3. 执行转换

    1. int a = 454553;
    2. String result = converterRegistry.convert(String.class, a);
    3. Assert.assertEquals("Custom: 454553", result);

    注意: convert(Class type, Object value, T defaultValue, boolean isCustomFirst)方法的最后一个参数可以选择转换时优先使用自定义转换器还是默认转换器。convert(Class type, Object value, T defaultValue)和convert(Class type, Object value)两个重载方法都是使用自定义转换器优先的模式。

ConverterRegistry单例和对象模式

ConverterRegistry提供一个静态方法getInstance()返回全局单例对象,这也是推荐的使用方式,当然如果想在某个限定范围内自定义转换,可以实例化ConverterRegistry对象。