java国际化之(一)--- jdk相关api

阅读更多

引言

 

随着公司海外业务发展,对系统的国际化改造已经势在必行。为了不影响国内业务,我们的做法是基于现有系统,采用国际化的思路搭建一套新的系统。我有幸被指定负责这次国际化基础架构设计和开发工作。

 

本人没有做过国际化相关项目,但以前在学习Spring MVC时隐约记得看到过相关章节。于是这几天又把 Spring MVC “国际化”相关章节又复习了一遍,并查阅其他一些相关资料,完成了一个简单的Spring MVC国际化demo搭建。

 

初步准备把这次的搭建经历分成三部分进行总结:1、java国际化之---jdk相关api(一);2、java国际化之---springMVC(二);3、java国际化之---demo讲解(三)。并计划在后续项目进行过程中,对实际遇到的问题再进行补充。

 

本章是第一部分:jdk相关api,主要讲解java.util.Locale类,以及java.text中的三大格式化类NumberFormat、DateFormat、MessageFormat的用法。另外 ResourceBundle类准备放到第二部分springMVC中进行分析。

 

名词解释

 

国际化:是开发支持多语言和数据格式的应用程序的技术,无需重新编写程序逻辑。简写为:i18n(Internationalization)。

本地化:是将国际化应用程序改成支持特定语言区域的技术。简写为:l10n(localization)。

 

个人理解为:在开发阶段,基于国际化技术,对系统程序进行开发和设计;在部署阶段,对于不同的国家采用对应本地化语言配置,但部署的代码是同一套。虽然程序在不同的国家分别单独进行部署,但对于开发人员来说代码是同一套,这样可以大大的降低代码维护成本。

 

java.util.Locale类

 

java.util.Locale类表示一个语言区域,是java国际化的核心,可以说对java程序的国际化设计,就是基于该类进行的。构造该类的实例,可以通过三个重要的变量完成:language、country、variant。对应的三个公有的构造方法分别为:

public Locale(String language)//指定语言
public Locale(String language, String country)//指定语言、国家
public Locale(String language, String country, String variant)//指定语言、国家、自定义参数

 

再来分别看下三个参数的含义:

language:语言代号,对于不同的语言有不同的语言代号。比如:zh(汉语)、en(英语)、de(德语) 等等。要想知道某个指定语言的代号,可以通过查询《ISO 639语言代码表》获得。另外可以通过查看LocaleISOData类的isoLanguageTable字段,这个字段定义了所有java支持的”语言代号”。

 

country:国家码,对于不同的国家有不同的国家码。比如:CN(中国)、US(美国)、DE(德国)等等。要想知道某个指定国家的国家码,可以通过查询《ISO 3166国家码表》。主要应用场景:有时候只是根据语言还是无法区分语言区域的,比如 美国和英国都说英语,但他们之间还是有区别,这时可以通过添加country字段进行区分。另外可以通过查看LocaleISOData类的isoCountryTable字段,这个字段定义了所有java支持的”国家码”。

 

variant:自定义变量,是自己任意指定。比如:可以是省份名称,比如有些省份有方言;也可以使是电脑操作系统等等。个人感觉用得较少,但也有特殊的场景会用到。

 

另外对于主流的语言区域,Locale类还提供了static final的常量,比如:

Locale locale = Locale.CHINA;

 

但一共只提供了10几个这样的常量,对于其他语言只能通过调用上述三个构造方法实现实例创建。

 

三大格式化类之NumberFormat

 

基于国际化技术的程序设计,最首要的工作就是对不同国家的不同数字表现形式进行处理,主要有三类数字类型需要处理:数字(整数、小数),金额(带符号)、百分数,不同语言区域有不同的表现形式。比如:德国的小数点是“逗号”,而大多数国家是“点”;中国的货币符号是“¥”,而美国的是“$”等等。

 

在java的国际化程序设计中,可以通过创建NumberFormat的不同实例,并结合不同的Locale参数来实现,如下(locale为传入的区域语言对象):

 NumberFormat.getInstance(locale);//处理普通Number型的格式
 NumberFormat.getCurrencyInstance(locale);//处理货币型的格式
 NumberFormat.getPercentInstance(locale);//处理百分数的格式
 NumberFormat.getIntegerInstance(locale);//处理整数的格式

 

 

通过查看源码,可以发现这些方法本质上是通过创建其子类DecimalFormat的实例,来实现的。通过调用NumberFormat实例的下列两个方法完成转换:

parse()方法,把字符串转换为指定的对象。

Format()方法,把对象转换成指定语言的字符串表现形式。

 

以下是我写的一个NumberFormat 国际化处理的工具类,由于NumberFormat是线程安全的,可以在程序启动时调用initLocal初始化方法,对指定locale的NumberFormat对象只初始化一次:

package com.sky.locale.web.I18nUtil;
 
import org.apache.commons.lang.StringUtils;
 
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
 
/**
 * NumberFormat国际化工具类 NumberFormat是线程安全的,可以提前初始化
 * Created by gantianxing on 2017/6/8.
 */
public class NumberFormatUtil {
 
    public static NumberFormat numF; //number型格式
    public static NumberFormat curF; //货币型格式
    public static NumberFormat perF; //百分比型格式
    public static NumberFormat intF; //整型格式
 
    /**
     * 程序启动时,或者切换语言时调用
     * @param localeStr 如:en
     */
    public static void initLocal(String localeStr){
        Locale locale = null;
        if(StringUtils.isBlank(localeStr)){
            return;
        }
        String [] a = localeStr.split("_");
        if(a.length>1){
            locale = new Locale(a[0],a[1]);
        } else {
            locale = new Locale(localeStr);
        }
        numF = NumberFormat.getInstance(locale);
        curF = NumberFormat.getCurrencyInstance(locale);
        perF = NumberFormat.getPercentInstance(locale);
        intF = NumberFormat.getIntegerInstance(locale);
    }
 
    /**
     * 字符串类型 转换为 数字类型
     * @param numberStr
     * @return
     * @throws ParseException
     */
    public static Number getNumber(String numberStr) throws ParseException{
        return numF.parse(numberStr);
    }
 
    /**
     * 数字类型 转换为 字符串类型
     * @param number
     * @return
     */
    public static String getStringNumber(Number number){
        numF.setMinimumFractionDigits(4);//最多保留4个小数
        return numF.format(number);
    }
 
    /**
     * 把价格字符串转换为 double型
     * 用于从页面读取字符串,保存到数据库
     * @param priceStr 如:$100.00
     * @return 如:100.00
     */
    public static double getNumberCur(String priceStr) throws ParseException {
        return curF.parse(priceStr).longValue();
    }
 
    /**
     * 把double型转换为指定国家的 价格字符串
     * 用户从数据库读取,输出到页面
     * @param price 如:100.0012
     * @return 如:$100.00 (自动四舍五入 保留两位小数)
     */
    public static String getStringCur(double price){
        return curF.format(price);
    }
 
    /**
     * 百分比字符串传 double
     * @param perStr
     * @return
     * @throws ParseException
     */
    public static double getNumberPer(String perStr) throws ParseException{
        return perF.parse(perStr).longValue();
    }
 
    /**
     * 字double 转 百分比字符串
     * @param per
     * @return
     */
    public static String getStringPer(double per){
        perF.setMinimumFractionDigits(2); //保留两位小数
        return perF.format(per);
    }
 
    /**
     * 字符串 转 number
     * @param intStr
     * @return
     * @throws ParseException
     */
    public static int getNumberInt(String intStr) throws ParseException{
        return intF.parse(intStr).intValue();
    }
 
    /**
     * int 转 字符串
     * @param i
     * @return
     */
    public static String getStringInt (int i){
        return intF.format(i);
    }
 
 
    public static void main(String[] args) throws Exception{
        double price = 111110.0058;
 
        //initLocal("de_DE"); //德国
        //initLocal("th_TH"); //泰国
        initLocal("zh_CN");  //中国
        System.out.println(getStringNumber(price));
        System.out.println(getStringCur(price));
        System.out.println(getStringPer(0.87344));
        System.out.println(getStringInt(1111111));
        System.out.println("-------分界线------");
        System.out.println(getNumber(getStringNumber(price)));
        System.out.println(getNumberCur(getStringCur(price)));
        System.out.println(getNumberPer(getStringPer(0.87344)));
        System.out.println(getNumberInt(getStringInt(1111111)));
    }
}

 

 

在mian方法中首先调用initLocal方法,参数为指定某个语言区域。转入不同的locale,执行main方法可以看到不同的数据格式。

 

三大格式化类之DateFormat

 

基于国际化技术的程序设计,其另一个主要工作是对不同国家的不同日期、时间表现形式进行处理。在java可以通过创建不同的DateFormat实例实现。

 

对于日期格式,可以调用下列方法进行处理:

public final static DateFormat getDateInstance(int style, Locale aLocale)

 

两个参数,第二参数是语言区域对象。第一个参数,可以确定日期格式的显示完整性,从源码看可以选择如下5种类型:

DateFormat.DEFAULT //默认格式
DateFormat.SHORT //最短格式
DateFormat.MEDIUM //中等格式
DateFormat.LONG //长格式
DateFormat.FULL //最完整格式

 

 

对于时间格式,可以调用下列方法进行处理:

public final static DateFormat getTimeInstance(int style, Locale aLocale)

 

两个参数的含义,与日期方法getDateInstance的两个参数完全相同。

 

对于日期+时间格式,可以调用下列方法进行处理:

public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)

 

这里有三个参数,第一个是日期的格式,第二个是时间的格式,第三个是语言区域对象

 

对于日期的格式化转换是通过调用DateFormat的format和parse方法完成的:

format()方法 将日期(时间)对象转换为指定国家的字符串表现形式

parse()方法 将指定国家的字符串表现形式转换为日期(时间)对象。

 

来看下示例代码:

/**
 * Created by gantianxing on 2017/6/8.
 */
public class DateFormatUtil {
 
    public static void main(String[] args) throws Exception{
        Date date = new Date();
        String dateStr = date.toString();
        System.out.println("获取时间:" +dateStr);
 
        Locale locale = Locale.GERMANY;//德国
        DateFormat defStyleD = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
        DateFormat shortStyleD = DateFormat.getDateInstance(DateFormat.SHORT, locale);
        DateFormat mediumStyleD = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
        DateFormat longStyleD = DateFormat.getDateInstance(DateFormat.LONG, locale);
        DateFormat fullStyleD = DateFormat.getDateInstance(DateFormat.FULL, locale);
 
        System.out.println(defStyleD.format(date));
        System.out.println(shortStyleD.format(date));
        System.out.println(mediumStyleD.format(date));
        System.out.println(longStyleD.format(date));
        System.out.println(fullStyleD.format(date));
 
        System.out.println("-----分割线 ------");
        DateFormat defStyleT = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
        DateFormat shortStyleT = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
        DateFormat mediumStyleT = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
        DateFormat longStyleT = DateFormat.getTimeInstance(DateFormat.LONG, locale);
        DateFormat fullStyleT = DateFormat.getTimeInstance(DateFormat.FULL, locale);
 
        System.out.println(defStyleT.format(date));
        System.out.println(shortStyleT.format(date));
        System.out.println(mediumStyleT.format(date));
        System.out.println(longStyleT.format(date));
        System.out.println(fullStyleT.format(date));
 
        System.out.println("-----分割线 parse方法------");
 
        System.out.println(fullStyleD.parse("Donnerstag, 8. Juni 2017"));
        System.out.println(fullStyleT.parse("21:33 Uhr CST"));
 
        System.out.println("-----分割线 日期时间格式化处理------");
 
        DateFormat shortDf = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT,Locale.CHINA);
        System.out.println(shortDf.format(date));
        DateFormat longDF = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG,Locale.CHINA);
        System.out.println(longDF.format(date));
 
        //String 转日期
        String dStr = "2017年6月7日 上午10时13分01秒";
        DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.CHINA);
        String dateS = df.parse(dStr).toString();
        System.out.println("反向解析出的结果:" + dateS);
    }
}

 

 

这里是以德国语言区域为例,执行main方法,可以看到德国的日期(时间)格式。也可以改变不同的Locale,实现国际化。

 

另外需要注意的是这里调用的getXXXInstance()方法,最终会创建其子类SimpleDateFormat的实例进行处理。并且是线程不安全的,与上述NumberFormat的处理方法不同,这里在每次使用时都需要创建一个实例,防止高并发情况下出现异常。

 

三大格式化类之MessageFormat

 

MessageFormat是对消息的格式化处理,同时该类也提供对国际化的支持。先看例子,如下:

 

public static void main(String[] args) {
        msgf();
        msgNum();
    }
    public static void msgf(){
        Object[] objects={new Date(),"中国","晴朗"};//指定date或time,传入Date 实例
        //只指定应用对象:objects
        MessageFormat mf= new MessageFormat("当前时间:{0,date,full},地点:{1},天气:{2}",Locale.CHINA);
        String result=mf.format(objects);
        System.out.println(result);
    }
 
    public static void msgNum() {
        Object[] objects={1111.11,2111.11};//指定date或time,传入Date 实例
        MessageFormat mf= new MessageFormat("当前价格:{0,number,percent},原价:{1,number,currency}",Locale.US);
        String result=mf.format(objects);
        System.out.println(result);
    }
 

 

通过查看源码,可以发现MessageFormat对国际化的支持,其底层其实是调用的NumberFormat和DateFormat进行处理的。

 

如上示例代码中:

日期处理:{0,date,full} 0表示取数组中下标为0的值进行替换,date表示是日期型,full表示完整的日期显示。

数字处理:{0,number,percent} 0表示取数组中下标为0的值进行替换,number表示是数字类型,percent表示是百分数。

执行main方法,打印信息如下:

当前时间:2017年6月8日 星期四,地点:中国,天气:晴朗
当前价格:111,111%,原价:$2,111.11

 

其他组合方式,不再一一列举,结合以上关于NumberFormat和DateFormat的描述,替换相关参数即可完成不同的消息格式处理。

 

总结

 

总之java.util.Locale的实例对象是实现国际化的关键,相对于key。贯穿在这个国际化的数字、日期(时间)、消息根式化处理过程中。 

 

关于MessageFormat的一个典型的应用场景是,从配置文件中取出指定个格式消息体,再进行相关占位符的替换,然后返回给前端页面。这个场景我会在下一章springMVC的demo中进行讲解。这里不再累述。

 

另外MessageFormat也是线程不安全的,使用的时候需要注意。也就是说java中的三大格式化处理,只有NumberFormat是线程安全的,DateFormat和MessageFormat都不是。

 

最后,关于创建NumberFormat和DateFormat的实例的getXXXInstance()方法,是静态工厂方法模式,与通过构造方法创建实例想必,在《effective java》书中跟推崇的静态工厂方法模式,在我们的日常开发中也可以借鉴这种方式。而创建MessageFormat的实例采用的是构造方法。

 

你可能感兴趣的:(java国际化,Locale,NumberFormat,MessageFormat,DateFormat)