字符串、格式化、正则表达式【下】

(这一个章节,将会提及到Java里面很特殊的一个数据类型:String,其类型的主要麻烦在于我们经常会用到它,而且针对它的“不可变”,很多时候很难去理解。附带这个章节会讲到很多关于处理字符串格式的内容,包括使用正则表达式做验证以及使用日期、货币格式化处理,还会提及到的就是如果在使用JDBC的时候针对SQL的类型[java.sql包内]和针对Java的类型[java.util]的一些相互转换问题。这一个章节涵盖的内容可能比较小范围,但是我们在开发项目过程中会经常遇到,所以是不得不去了解和不得不去学习的一个章节。方便大家看的时候容易查找代码段,代码部分都有[$]作为段落查找前缀,如果有什么笔误请大家来Email告知:[email protected],谢谢!)

本章目录
1.不可变类
2.String详解
3.StringBuilder和StringBuffer详解
4.正则表达式
5.关于转型
6.货币、时间、日期格式化
4.正则表达式
  i.简介:
  在程序开发过程,很多时候需要针对文本进行处理,这里撇开文件的IO先不提,在处理过程中往往会遇到匹配、查找、替换、判断字符串的情况发生,而这些情况有些时候往往变得异常复杂。在文本处理里面,常用的手段就是正则表达式,对于新手可能不明白正则表达式是干什么的,这里先简单介绍一下:
  正则表达式是一种用来进行模式匹配和替换的规范,一个正则表达式本身就是由普通的字符组成的文字模式,它的主要目的就是以正则表达式为模板,将需要分析的文本以及提供的搜索字符串进行匹配。Java自1.4开始有了java.util.regex包,该包为我们提供了一个很好的Java正则表达式的应用。学习最初先看一个比较简单的进行判断的正则表达式的例子:
package org.susan.java.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*正则表达式初次接触
**/
public class RegexFirst {
    public static void main(String args[]){
        String inputValue = new String("abcaaaabcaaaaabcccc");
        Pattern pattern = Pattern.compile("abc");
        Matcher matcher = pattern.matcher(inputValue);
        int number = 0;
        while(matcher.find()){
            number++;
        }
        System.out.println("Patterns: " + number);
    }
}
  上边这段代码不列出输出,需要注意的是Pattern的参数,上边这段代码做了些什么事情呢?上边的目标字符串为inputValue的值,而Pattern定义了一个简单的模式,也就是上边的代码就做了一件事情:查找abc字符串在输入字符串中出现的次数。运行上边的代码就可以看到匹配次数是三次,也就是abc字符串在输入字符串里面出现了三次。
  ii.正则引擎[参考合作者网站:http://www.xwooo.com/]
  1)简介:
  使用正则表达式(Regular Expression)处理文本是一个不错的选择,因为它本身就是一个便捷、高效的文本处理工具,它能够针对目标文本进行添加、删除、分离、叠加、插入以及修正各种类别的文本和数据,一旦掌握了过后就可以很方便针对文本里面的所需数据进行各种提取操作,各种语言都提供了一定的处理正则表达式的库。一般来说,正则表达式引擎分为两大类:
  一种是DFA、一种是NFA——这两种引擎有很久的历史了,当中也由这两种引擎产生了很多变体!于是就有了POSIX出台,它的出现使得正则表达式变得更加规范化了,这样的改变使得正则表达式引擎分成了三种类型:DFA、NFA(相对传统)、POSIX NFA
  • DFA:
    DFA引擎在线性时状态下执行,因为它不要求回溯(也就是在匹配过程,它永远不会测试一个相同的字符两次)。DFA引擎还可以确保匹配最长的可能字符串,但是因为DFA引擎只包含了有限的状态,所以它不能匹配具有反向引用的模式;并且因为它不构造显示扩展,所以它不不可以捕获子表达式。
  • NFA:
    NFA引擎运行使用的“贪婪的”匹配回溯算法,以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。因为传统的NFA构造正则表达式的特定扩展以获得成功的匹配,所以它可以捕获子表达式匹配和匹配的反向引用。但是,因为传统的NFA回溯,所以它可以访问完全相同的状态多次(如果通过不同的路径到达该状态)。因此,在最坏的情况下,它的执行速度可能非常缓慢,因为传统的NFA接受它找到的第一个匹配,所以它可能还会导致其他匹配未被发现。
  • POSIX NFA
    POSIX NFA引擎与传统的NFA引擎类似,不同的一点在于:在它们可以确保已经找到了可能的最长的匹配之前,它们将继续回溯。因此POSIX NFA引擎的速度比NFA引擎更加慢,并且在使用该引擎的时候,恐怕我们都不愿意在更改回溯搜索的顺序的情况下来支持较短的匹配搜索,而非较长的匹配搜索。
  2)分类:
  目前使用的引擎主要有:
  DFA引擎:awk、egrep、flex、lex、MySQL、Procmail
  NFA引擎:GNU Emacs、Java、ergp、less、more、.NET、PCRE library、Perl、PHP、Python、Ruby、Sed、Vi
  POSIX NFA引擎:mawk、Mortice Kern Systems' utilities、GNU Emacs
  DFA/NFA混合的引擎:GNU awk、GNU grep/egrep、Tcl
  举个简单的例子:
  比如有字符串this is yansen's blog,正则表达式为/ya(msen|nsen|nsem)/【这里只是为了区别NFA和DFA,至于正则表达式的写法不是java语言里面使用的写法,前边这样的写法我平时开发在JavaScript写客户端脚本的时候蛮常用的】
  NFA工作方式如下:
  现在字符串查找y然后匹配其后是否为a,如果是a就继续,查找其后是否为m如果不是则匹配其后是否为n(这个时候淘汰掉msen),然后继续看其后是否一次为s、e,接着测试是否为n,是n则匹配成功,不是则测试是否为m。为什么这里是m呢?因为NFA工作方式是以正则表达式为标准,反复测试字符串,这样同一个字符串有可能被反复测试了很多次!
  DFA工作方式如下:
  DFA会从this中t开始一次查找y,定位到y,已知其后为a,则查看是否表达式中有a,此处正好有a,然后字符串a后为n,DFA依次测试表达式,此时msen不符合要求淘汰。nsen和nsem符合要求,然后DFA依次检查字符串,检测到sen中的n时只有nsen分支符合,则匹配成功。
  从上边这个简单的例子可以知道:两种引擎的工作方式截然不同,一个NFA以表达式为主导,一个DFA以文本为主导!一般来说,DFA引擎则搜索更快一些!但是NFA以表达式为主,反而更容易操纵,因此一般程序员更偏爱NFA引擎!
  3)构成:
  既然是一个表达式引擎一定会有多种零件,要真正了解引擎的工作原理,就必须先了解其零件,正则引擎的零件主要有以下几个:
  文字字符:
  一般如:a、/*、!、好
  如果一个正则表达式只包含纯文本字符,如上边的abc,则正则引擎会视为:a后边接着一个b,然后接着一个c,这种格式很容易理解。
  字符组[…]、点号.、Unicode属性以及其他
  需要说明的一点就是无论字符组的长度多长,它都只能匹配一个字符
  捕获型括号():
  用于捕获文本的括号,不会影响到匹配过程
  锚点:
  锚点可以分为:简单锚点和复杂锚点
  简单锚点只是检查目标字符中的特性位置(如^,$),或者是比较两个相邻的字符(/<,/b)
  复杂锚点(环视)能包含任意复杂的子表达式
  非“电动”的括号、反向引用和忽略优先量词
  捕获括号、以用相应的反向引用和表示法(如$1或/1)、忽略优先量词只对NFA引擎起作用,DFA不支持。
  4)关于回溯:
  这里提及到回溯主要是因为它是NFA引擎中很重要的一点:
  NFA引擎最重要的性质就是,它会依次处理各个表达式或组合元素,遇到需要在两个可能成功的途经中进行选择的时候,它会选择其一,然后记住另一个选项,以备稍后可能的需要。一般需要做出选择的情形包括量词和多选结构。
  真实世界中的面包屑

  回溯就像在道路的每个分岔口留下一小堆面包屑。如果走了死路,就可以原路返回,直到遇到面包屑标示的尚未尝试过的道路。如果还是死路,继续返回,找到下一堆面包屑,如些重复,直到找到出路,或者走完所有没有尝试过的道路。
  不论选择那一种途经,如果它能成功匹配,而且余下的正则也匹配成功了,匹配即告完成。如果余下的正则匹配失败,引擎会回溯到之前做选择时记录的备用途经。这样引擎最终会尝试表达式的所有可能途经(如果没有匹配成功的话)。

  回溯的两个要点
  如果需要在“进行尝试”和“跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”而对于忽略优先量词,会选择“跳过尝试”
     在需要回溯时,距离当前最近储存的选项就是当本地失败强制回溯时返回的。使用的原则是LIFO(last in first out,后进先出)。

  iii.正则表达式语法:
  由上边演示的代码可以知道,真正起作用的是这句话:
  Pattern pattern = Pattern.compile("abc");
  这里传入的abc对Java初学者而言就是一个字符串,实际这个字符串有特殊的意义,因为它代表的是正则表达式,而不是一个字符串,上边的表达式已经在前边章节解释过含义了,那么正则表达式的其他写法应该如何进行呢:
  1)正则表达式基本用法:
  [1]普通字符:
  正则表达式的构成为:字母、数字、汉字、下划线、以及没有特殊定义的标点符号都属于正则表达式里面的普通字符,表达式中的普通字符在匹配一个字符串的时候,匹配与之相同的一个字符就可以。上边提供的例子说明的就是这类字符。
  [2]多字符表达式:
  因为在书写过程会出现一些特殊字符,有些需要进行转义,使用"/"的方法区分,转义字符对很多学习过编程的人应该不难理解,因为这些字符都带有很特殊的含义:
  这里主要列举常用的一些多字符表达式:
表达式 可匹配的字符以及含义
/s 匹配所有空白字符、制表符、换页符中任意一个,等价自定义表达式:
[/t/n/x0B/f/r]
/w 任意一个字母或数字以及下划线,等价表达式:
[a-zA-Z_0-9]
/d 任意一个数字,0到9中的任意一个,等价表达式:
[0-9]
/b 标识一个单词的边界
/S 匹配所有的非空白符号,等价表达式:
[^/t/n/x0B/f/r]
/W 匹配所有的字母、数字、下划线以外的字符,等价表达式:
[^a-zA-Z_0-9]
/D 匹配任意一个非数字,等价表达式:
[^0-9]
/B 匹配一个非单词的边界,即左右两边都是/w范围或者左右两边都不是/w范围时的字符缝隙
  [3]修饰规则
  上边部分谈到的都是表达式匹配一个字符的规则,任何时候都只能匹配一次,而有一种办法还可以修饰模式里面字符的数目,比如:
  [b][b][b]可以写成[b]{3}
  其写法为:“次数修饰符”直接放在“被修饰的表达式”后边,就像上边这样的写法就是这样的一个简单的例子
  其修饰表为:
表达式 其含义
{n} 表达式重复n次,比如:a{5}等价于aaaaa
{m,n} 表达式至少重复m次,最多重复n次
{m,} 表达式至少要出现m次
? 匹配表达式0次或者1次,等价于{0,1}
+ 表达式至少出现1次,等价于{1,}
* 表达式不出现或者出现任意次数,等价于{0,}
  [4]特殊符号:
  正则表达式里面还有一些比较特殊的匹配符号,如上边需要转义的^、$、.等都是正则表达式中很特殊的符号,这些符号具有特殊的含义:
符号 作用
^ 与字符串开始的地方匹配,不匹配任何字符
$ 与字符串结束的地方匹配,不匹配任何字符
. 条件限制除开/n以外的任意一个单独字符
| 左右两边的表达式之间“或”关系,匹配左边或者右边都可以
() 1.在被修饰匹配的次数的时候,括号中的表达式可以作为整体被修饰
2.取匹配结果的时候,括号中的表达式匹配到的内容可以被单独得到
[] 用来自定义能够匹配多字符的表达式
{} 修饰匹配次数的符号
  所以上边的[3]和[4]这些出现的符号如果需要进行录入的时候都需要使用转义,比如?应该使用/?、等价的|符号应该使用/|,其他符号可以参见上边的表
  [5]Pattern类里面的字段摘要:
static int CANON_EQ:[Pattern.CANON_EQ]
  当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下,才认定匹配。比如用了这个标志之后,表达式"a/u030A"会匹配"?"。默认情况下,不考虑"规范相等性(canonical equivalence)"
static int CASE_INSENSITIVE(?i):[Pattern.CASE_INSENSITIVE]
  默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹配,只要将UNICODE_CASE与这个标志合起来就行了
static int COMMENTS(?x):[Pattern.COMMENTS]
  在这种模式下,匹配时会忽略(正则表达式里的)空格字符(译者注:不是指表达式里的"//s",而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式
static int DOTALL(?s):[Pattern.DOTALL]
  在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符
static int MULTILINE(?m):[Pattern.MULTILINE]
  在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束
static int UNICODE_CASE(?u):[Pattern.UNICODE_CASE]
  在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集
static int UNIX_LINES(?d):[Pattern.UNIX_LINES]
  在这个模式下,只有'/n'才被认作一行的中止,并且与'.','^',以及'$'进行匹配
  2)常用正则表达式:
正则表达式写法 含义描述
^/d+$
非负整数(正整数+0)
^[0-9]*[1-9][0-9]*$
正整数
^((-/d+)|(0+))$ 非正整数(负整数+0)
^-[0-9]*[1-9][0-9]*$ 负整数
^-?/d+$ 整数
^/d+(/./d+)?$ 非负浮点数(正浮点数+0)
^(([0-9]+/.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*/.[0-9]+)|([0-9]*[1-9][0-9]*))$ 正浮点数
^((-/d+(/./d+)?)|(0+(/.0+)?))$ 非正浮点数(负浮点数+0)
^(-?/d+)(/./d+)?$ 浮点数
^[A-Za-z]+$ 由26格英文字母组成的字符串
^[A-Z]+$ 由26格大写字母组成的字符串
^[A-Za-z0-9]+$ 由数字和26个英文字母组成的字符串
^/w+$ 由数字、26格英文字母或者下滑线组成的字符串
^[/w-]+(/.[/w-]+)*@[/w-]+(/.[/w-]+)+$ Email地址的验证格式
^[a-zA-z]+://(/w+(-/w+)*)(/.(/w+(-/w+)*))*(/?/S*)?$ URL地址的格式
^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$ 年-月-日
^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$ 月/日/年
(d+-)?(d{4}-?d{7}|d{3}-?d{8}|^d{7,8})(-d+)? 手机号码
^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$ IP地址
[/u4e00-/u9fa5]
中文字符的正则表达式
[^/x00-/xff]
双字节
^[1-9]*[1-9][0-9]*$
腾讯QQ号
^d{15}|d{}18$
国内身份证格式【没有验证生日】
(/(/d{3,4}/)|/d{3,4}-|/s)?/d{8}
国内的电话号码
  【*:这里不涉及正则表达式的高级用法,包括正则表达式的贪婪匹配一些相关内容,在下边的代码中做简单的说明】
  iv.Java中正则表达式的使用:
  首先第一点需要说明的是,上边的正则表达式在作为Java的Pattern类传入参数的时候,需要进行一些字符的转义因为Pattern的方法在进行正则表达式的传入的时候是一个字符串,这个上边String的split函数中提及到一些:
  ——[$]验证格式——
package org.susan.java.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* 正则表达式的初步使用
* @author Lang Yu
*
*/
public class ValidateRegex {
    public static void main(String args[]){
        // 验证邮件格式
        String inputEmail = "[email protected]";
        String errorEmail = "silent#126.com";
        System.out.println(validateEmail(inputEmail));
        System.out.println(validateEmail(errorEmail));
        // 验证时间格式
        String inputURL = "http://www.google.cn";
        String errorURL = "www.baidu.com";
        System.out.println(validateURL(inputURL));
        System.out.println(validateURL(errorURL));
    }
    // 验证是否合法的邮件格式
    private static boolean validateEmail(String inputValue){
        return inputValue.matches("^[//w-]+(//.[//w-]+)*@[//w-]+(//.[//w-]+)+$");
    }
    // 验证URL
    private static boolean validateURL(String inputValue){
        Pattern pattern = Pattern.compile("^[a-zA-z]+://(//w+(-//w+)*)(//.(//w+(-//w+)*))*(//?//S*)?$");
        Matcher matcher = pattern.matcher(inputValue);
        return matcher.find();
    }
}
  上边这段的输出为:
true
false
true
false
  其实从上边的代码可以看出来一点,如果仅仅只是为了做验证,使用String的matches方法比较方便而且是很便捷,如果是需要做其他操作可能就需要使用到Pattern类以及java.util.regex里面的类,这里没有做太多的操作,仅仅是验证和匹配,从上边的代码可以看出,第二种方式也有一个好处,就是在写的时候可以创建一个独立的模式使得每个字符串都可以去匹配这种模式进行重用,其实开发过程可以将正则表达式的模式文件存储于一个固定的常量文件以方便调用达到在项目开发过程中的软件复用效果,至于整个过程中关于复用的设计就可以根据不同的项目来进行相关的操作。
  这里在讲解之前,简单透析一下Java里面的java.util.regex包内的API函数以及相关内容,其实真正需要关注的只有两个类:Pattern和Matcher:
  从上边的代码可以知道,Pattern类主要是为了构造正则表达式的编译表示形式而存在,常用的代码段为:
Pattern pattern = Pattern.compile(strValue);
  在这个地方传入的strValue就是正则表达式的表示形式,也就是如上边的代码所写的格式,也是为了建立正则表达式中所需要的匹配原子而存在的,可以这样理解。指定为字符串的正则表达式必须首先被编译为此类的实例。然后就可以得到使用该模式的Matcher对象,一早正则表达式,这个Matcher对象就可以直接针对需要的字符串进行匹配、提取以及各种所需要的操作。而真正在使用过程中比较常用的类是Matcher类:通过调用模式的matcher方法从模式创建匹配器,创建过后可以进行下边这些相关操作:
  • matcher方法尝试将整个输入序列与模式匹配
  • lookingAt尝试将输入序列从头开始与该模式匹配
  • find方法扫描输入序列以查找与该模式匹配的下一个子序列
  每个方法都返回一个表示成功或者失败的布尔值,通过查询匹配器的状态可以获取关于成功匹配的更多信息。匹配器在其输入的子集中查找匹配项,默认情况下,此区域包含全部的匹配器的输入,可通过region方法修改区域,通过regionStart和regionEnd方法查询区域,区域边界与某些模式构造交互的方式是可以更改的。此类还定义使用新字符串匹配子序列的方法,需要的时候,可以从匹配结果计算出新字符串的内容,可以先后使用appendReplacement和appendTail方法将结果收集到现有的字符串缓冲区,或者使用更加便捷的replaceAll方法创建一个可以在其中替换输入序列中每个匹配子序列的字符串。匹配器的显示状态最初是未定义的,在成功匹配导致IllegalStateException抛出之前尝试查询其中的任何部分,每个匹配操作都将重新计算匹配器的显示状态。匹配器的隐式状态包括输入字符序列和添加位置,添加位置最初是零,然后由appendReplacement方法更新。可以通过调用匹配的reset()方法来显示重置匹配器,如果需要新的输入序列,就需要调用reset(CharSequence)方法,重置匹配器将放弃其显示状态信息并将添加位置设置为零。
  【*:该类的实例用于多个并发线程会存在线程安全问题,这一点需要特别注意。】
  ——[$]寻找可匹配重复项的一个应用——
package org.susan.java.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class MatchDuplicateWords {
    public static void main(String args[]){
        hasDuplicate("pizza pizza");
        hasDuplicate("Faster pussycat kill kill");
        hasDuplicate("The mayor of of simpleton");
        hasDuplicate("Never Never Never Never Never");
        hasDuplicate("222 2222");
        hasDuplicate("sara sarah");
        hasDuplicate("Faster pussycat kill, kill");
        hasDuplicate(". .");
    }
    public static boolean hasDuplicate(String phrase){
        boolean result = false;
        String duplicatePattern = "//b(//w+) //1//b";
        Pattern pattern = null;
        try{
            pattern = Pattern.compile(duplicatePattern);
        }catch(PatternSyntaxException pex){
            pex.printStackTrace();
            System.exit(0);
        }
        int matches = 0;
        Matcher matcher = pattern.matcher(phrase);
        String valString = null;
        while(matcher.find()){
            result = true;
            valString = "$" + matcher.group() + "$";
            System.out.println(valString);
            matches++;
        }
        String msg = "NO MATCH: Pattern: " + phrase + "/r/n" + "Regex: " + duplicatePattern;
        if( result){
            msg = "MATCH : Pattern: " + phrase + "/r/n" + "Regex:" + duplicatePattern;
        }
        System.out.print(msg + "/r/n");
        System.out.println("-------------Result: " + result + "----------");
        return result;
    }
}
  先分析上边这段代码的输出:
$pizza pizza$
MATCH : Pattern: pizza pizza
Regex:/b(/w+) /1/b
-------------Result: true----------
$kill kill$
MATCH : Pattern: Faster pussycat kill kill
Regex:/b(/w+) /1/b
-------------Result: true----------
$of of$
MATCH : Pattern: The mayor of of simpleton
Regex:/b(/w+) /1/b
-------------Result: true----------
$Never Never$
$Never Never$
MATCH : Pattern: Never Never Never Never Never
Regex:/b(/w+) /1/b
-------------Result: true----------
NO MATCH: Pattern: 222 2222
Regex: /b(/w+) /1/b
-------------Result: false----------
NO MATCH: Pattern: sara sarah
Regex: /b(/w+) /1/b
-------------Result: false----------
NO MATCH: Pattern: Faster pussycat kill, kill
Regex: /b(/w+) /1/b
-------------Result: false----------
NO MATCH: Pattern: . .
Regex: /b(/w+) /1/b
-------------Result: false----------
  从上边这段代码可以看出,主要是区分单词,就是找出在一个字符串中的两个相同的词语,而且这两个词语还是完整的,不能是附带任何多余字符的,这里主要针对里面的一些小细节稍稍做讲解,详细的内容请读者自己尝试这个Demo,针对上边主要对两个方法做讲解
  • 首先是Matcher的find方法,find方法做了两个事情,首先是指代目标字符串是否能够匹配我们所给的字符串,所以它的返回值是boolean,另外就是将匹配的游标指向下一个匹配的位置,这一点一定要记得
  • Matcher的group方法主要是提取出来目标字符串里面匹配的字符串的文字,从上边的输出就可以知道只有能够匹配的位置然后group返回的就是两个$符号中间的内容
  其实真正开发过程比较常用的find方法,因为这个方法主要是为了针对目标字符串进行操作用的一个方法,往往我们需要匹配的目标文字一般可能不是一次匹配,可能是多次
  ——[$]IP地址的匹配工具(IPv4和IPv6)——
package org.susan.java.regex;

import java.util.regex.Pattern;

public class InetAddressUtils {
    private static final Pattern IPV4_PATTERN =
        Pattern.compile("^(25[0-5]|2[0-4]//d|[0-1]?//d?//d)(//.(25[0-5]|2[0-4]//d|[0-1]?//d?//d)){3}$");
    private static final Pattern IPV6_STD_PATTERN =
        Pattern.compile("^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
    private static final Pattern IPV6_HEX_COMPRESSED_PATTERN =
        Pattern.compile("^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");

    public static boolean isIPv4Address(final String input){
        return IPV4_PATTERN.matcher(input).matches();
    }
    public static boolean isIPv6StdAddress(final String input){
        return IPV6_STD_PATTERN.matcher(input).matches();
    }
    public static boolean isIPv6HexCompressedAddress(final String input){
        return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
    }
    public static boolean isIPv6Address(final String input){
        return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
    }
}
  上边这段代码主要是为了验证IP地址,为一个常用工具类,其原理很简单,就不多讲。
  ——[$]append相关方法——
package org.susan.java.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AppendReplace {
    public static void main(String args[]) throws Exception{
        CharSequence inputStr = "ab12 cd efg34 asdf 123";
        String patternStr = "([a-zA-Z]+[0-9]+)";
        Pattern pattern = Pattern.compile(patternStr);
        Matcher matcher = pattern.matcher(inputStr);
        StringBuffer buffer = new StringBuffer();
        boolean found = false;
        while((found = matcher.find())){
            String replaceStr = matcher.group();
            matcher.appendReplacement(buffer, "found<" + replaceStr + ">");
        }
        matcher.appendTail(buffer);
        String result = buffer.toString();
        System.out.println(result);
    }
}
  这里同样先看上边这段代码的输出:
found cd found asdf 123
  这里最主要讲解两个方法,一个是appendTail,一个是appendReplacement方法,这两个方法一般在替换字符串的时候很常用,这里不仅仅做了匹配操作,而且将匹配部分做了标记了。
  • appendReplacement方法:
    此方法将执行下边的操作:
    [1]它从添加位置开始在输入序列读取字符,并且将其添加到给定的字符串缓冲区,在读取以前匹配之前的最后字符(索引位于start() - 1处)之后,它就会停止
    [2]它将给定替换字符串添加到字符串缓冲区
    [3]它将此匹配器的添加位置设置为最后匹配位置的索引加1,即end()
  • appendTail方法:
    这个方法应该执行下边的操作:此方法从添加位置开始从输入序列读取字符,并将其添加到给定字符串缓冲区,可以在一次或多次调用appendReplacement方法后调用它来复制剩余的输入序列
  这两个方法的演示上边的代码已经指出了,这里还涉及到另外两个方法,就是是start()和end()方法,这两个方法一个返回匹配好的索引的开始位置,一个是匹配好的索引的结束位置。
  ——[$]reset方法——
package org.susan.java.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ResetDemo {
    public static void main(String args[]){
        Pattern pattern = Pattern.compile("//d");
        Matcher matcher = pattern.matcher("012345");
        while(matcher.find()){
            System.out.println("/t" + matcher.group());
        }
        matcher.reset("56789");
        System.out.println("After resetting the Matcher");
        while(matcher.find()){
            System.out.println("/t" + matcher.group());
        }
    }
}
  这里就不阐述输出了,使用reset方法可以重新进行匹配:
  【*:关于正则表达式的部分,目前就讲这些,大部分常用的代码上边都有一些简单的Demo可以做一个说明,如果还有什么建议记得来Email告知,注意一点的是:这里不包含正则表达式的高级话题!】

5.关于转型
  这里又要提及的就是转型的问题,我们在开发项目过程经常遇到数据类型的转换,首先需要理解的一点是语言的概念区分:
  i.基本概念:
  强类型语言和弱类型语言的区分:
  • 强类型语言:强类型语言有强制数据类型的定义,也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制类型转换,那么它永远就是这个数据类型了。举个例子:如果你定义一个整型变量a,那么程序根本不可能将a当作字符串类型处理,换一个角度来讲,强类型语言是类型安全的语言。Java就是一个典型的强类型定义的语言,因为它存在严格的类型定义,比如String、int、short、long等各种类型。
  • 弱类型语言:弱类型语言是指类型可以被忽略的语言,它与强类型定义的语言想法,一个变量可以赋不同数据类型的值。比如我们常用的PHP和JavaScript是典型的弱类型,这里需要区分的一点是:弱类型语言不是说变量完全没有类型,是说类型的定义不如强类型语言一样很严格,比如下边的PHP代码:
    $a = 1;
    $a = "Hello";
    这种情况如果出现在Java里面就会因为类型问题报错,因为在强类型语言里面数据转换是有特殊规则的,上边代码也只有像JavaScript和PHP这种若类型语言可以完全通过编译
  动态语言和静态语言的区分:
  • 动态类型语言:动态类型语言是指在运行期间才会去做数据类型检查的语言,也就是说,在用动态类型的语言编程的时候,永远也不用给任何变量指定数据类型,该语言会在我们编程的第一次给变量赋值的时候进行内部的数据类型的记录。
  • 静态类型语言:静态类型语言与动态类型语言相反,它的数据类型是在编译期间检查的,也就是说在写程序的时候需要声明所有的数据类型,而Java就是其中代表之一
  ii.Java中的转型:
  Java语言里面的类型转换是需要一定的技巧的,这里针对常用的转型进行说明,关于引用的类型转换在前边的《Java的类和对象》已经说明了,这里不做说明,这里主要针对的是常用的一些业务转换:
  1)时间和字符串:
  首先需要讲解的是时间类型和字符串之间的转换:
  ——[$]时间和字符串的转换——
package org.susan.java.convert;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateConvert {
    public static void main(String args[]){
        Date outputDate = new Date();
        String inputValue = "2009-11-11";
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        try{
            outputDate = format.parse(inputValue);
            System.out.println(outputDate);
        }catch(ParseException ex){
            ex.printStackTrace();
        }
    }   
}
  这句话的输出为:
Wed Nov 11 00:00:00 CST 2009
  针对时间和字符串之间的转化需要做一点点说明,从时间转换为字符串有时候需要输出,最主要的是进行格式化输出,这一点在下边进行简单说明
  • 有些方法目前已经被JDK放弃了,最原始的字符串到时间的转化为:
    Date date = Date.parse(inputValue);
    上边这种格式会报编译错误,而且Date.parse方法是被弃用的一个方法,替代方法为:DateFormat.parse(String s)现在很多时候开发项目并不使用这种方法进行String到Date的操作
    关于Date的parse静态方法的说明如下:
      尝试把字符串s解释为日期和时间的表示形式,如果尝试成功,则返回指示的时间,用该时间与历元(1970年1月1日,00:00:00 GMT)的时间差来表示(以毫秒为单位),如果尝试失败就会抛出IllegalArgumentException异常。这个方法接受很多语法,特别是它识别IETF标准日期语法:“Sat, 12 Aug 1995 13:30:00 GMT”,它也了解美国大陆市区缩写,但对于一般用途,应该使用时区偏移量:“Sat, 12 Aug 1995 13:30:00 GMT+0430”(比格林威治子午线晚 4 小时 30 分)。如果没有指定时区,则假定用本地时区,GMT和UTC被认为是相同的。系统将从左到右处理字符串inputValue,从中查找相对应的数据,inputValue中包含在ASCII括号字符中的任何内容都被忽略,inputValue只允许下边这些字符集:
    abcdefghijklmnopqrstuvwxyz
    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    0123456789,+-:/

    以及空白
  • 还有一点需要说明的是(下边是API文档里面的内容):
    连续的十进制位序列被当成十进制数:
    [1]如果数字前面有 + 或 -,并且已经识别了年份,那么数字就是一个时区偏移量。如果数字少于 24,那么它是以小时进行测量的偏移量。否则,它被认为是以分钟进行测量的偏移量,用没有标点的 24 小时时间格式来表示。前面的 - 意味着向西的偏移量。时区偏移量始终相对于 UTC(格林威治)。因此,例如,在字符串中出现的 -5 意味着“比格林威治时间晚 5 小时”,+0430 将意味着“比格林威治时间早 4 小时 30 分”。允许字符串冗余地指定 GMT、UT 或 UTC——例如,GMT-5 或 utc+0430。
    [2]如果下面条件之一为真,数字就被认为是年数:
    ——数字等于或大于 70,并且后跟一个空格、逗号、斜杠或结束字符串
    ——数字小于 70,并且已经识别月份和月中的某一天
    如果被识别的年数小于 100,它就被解释为相对于其日期所在世纪的缩写年,缩写年的日期位于初始化 Date 类的时间的 80 年以前和 19 年以后之间。在调整年数后,从其减去 1900。例如,如果当前的年份是 1999,那么范围 19 至 99 的年数就被假定表示 1919 至 1999,而 0 至 18 的年数就被假定表示 2000 至 2018。注意,这与 SimpleDateFormat 中使用的小于 100 的年份具有稍微不同的解释。
    [3]如果数字后跟一个冒号,则认为是小时,如果小时已经被识别,则认为是分钟。
    [4]如果数字后跟一个斜杠,则认为是月份(把它减 1,以便产生范围 0 至 11 的数字),如果月份已经被识别,则认为它是月中的某一天。
    [5]如果数字后跟空白、逗号、连字符或结束字符串,那么如果小时已识别但分钟未识别,则认为是分钟;否则,如果分钟已识别,而秒没有识别,则认为是秒;否则,它被认为是月中的某一天。

    连续的字母序列被认为是单词,并按以下方法进行处理:
    [1]忽略匹配 AM(忽略大小写)的单词(但如果小时尚未识别,或者小时小于 1 或大于 12,则解析失败)。
    [2]匹配 PM(忽略大小写)的单词,添加 12 到小时(但如果小时尚未识别,或者小时小于 1 或大于 12,则解析失败)。
    [3]忽略匹配 SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY 或 SATURDAY 的任何前缀(忽略大小写)的任何单词。例如,sat、Friday、TUE 和 Thurs 会被忽略。
    [4]否则,匹配 JANUARY、FEBRUARY、MARCH、APRIL、MAY、JUNE、JULY、AUGUST、SEPTEMBER、OCTOBER、NOVEMBER 或 DECEMBER 的任何前缀(忽略大小写,并按这里给出的顺序考虑它们)的任何单词都被识别为指定月份,并被转换成一个数字(0 至 11)。例如,aug、Sept、april 和 NOV 被识别为月份。Ma 也是这样,它被识别为 MARCH,而不是 MAY。
    [5]匹配 GMT、UT 或 UTC(忽略大小写)的任何单词都认为是指 UTC。
    [6]匹配 EST、CST、MST 或 PST(忽略大小写)的任何单词都被认为是指北美的时区,该时区分别比格林威治时间晚 5、6、7 或 8 小时。匹配 EDT、CDT、MDT 或 PDT(忽略大小写)的任何单词都被识别为在夏令时期间分别指相同的时区。

    一旦扫描了整个字符串,就以两种方式之一把它转换成时间结果。如果已经识别时区或时区偏移量,那么年、月、月中某一天、小时、分钟和秒以 UTC 进行解释,然后应用时区偏移量。否则,年、月、月中某一天、小时、分钟和秒用本地时区进行解释。
  【*:也就是说在真正进行字符串到日期转换的时候,现在不使用这个方法,而是使用DateFormat.parse进行替换操作,Date.parse方法返回值不是一个Date这也是上边代码会报错的原因。】
  2)普通的类型转换:
  ——[$]整型和String数据——
package org.susan.java.convert;

public class CommonConvert {
    public static void main(String args[]){
        int a = 12;
        String outAStr = String.valueOf(a);
        System.out.println(outAStr);
        Integer b = 10;
        System.out.println(b.toString());
    }
}
  上边这个数据就不作说明了,需要讲解的是:
  • 前边已经说明Java的基础类型有八种,在八种基础类型转化为String的时候,只能使用String的静态方法String.valueOf(),而不能使用toString()方法,因为基础类型不支持toString()方法,但是包装过的类型不一样,使用基础类型的包装类型的时候是可以使用toString()方法的。
  • 关于toString()方法需要简单说明:在Java里面,所有的对象都有toString()方法,这个方法继承于Object类,一般情况下在没有重写的情况下,格式为:
    java.lang.Object@de6ced
    @前边部分是该引用的类型,后边是对象的一种标识,在没有重写toString方法的时候,其格式针对每个对象都是固定的
  • 还有一点需要说明的是:Integer这种类似包装类的类型的toString方法是经过重写过的,比如上边的代码:
    System.out.println(b.toString());
    如果改写成:
    System.out.println(b);
    其输出结果不会变化
  • 在针对所有的类型这里都是一样的,比如其他的原始类型比如boolean、short或者其他的包装类型Float或者Double等等,也就是说:上边这段代码是从基本类型到String的概念说明代码,所有的基本类型以及包装类型都可以通过这种方式来转换。反过来转换的办法就不做说明,一般为:Integer.parseInt的格式,这种方式可以使得String转换称为一个int,这种方法的衍生可以去查询API
  ——[$]简单说明逆向转换——
package org.susan.java.convert;

public class StringToDouble {
    public static void main(String args[]){
        String inputString = "1.23";
        double a = Double.parseDouble(inputString);
        Double b = Double.parseDouble(inputString);
        System.out.println(a+b);
        System.out.println(inputString + inputString);
    }
}
  这段代码的输出为:
2.46
1.231.23
  这样就可以简单说明二者的转换了
  【*:在JDK1.5之前,就是还没有在Java语言里面出现自动拆箱解箱装置之前,上边的代码是无法通过编译的,因为上边的说明代码在JDK1.5以上是可以运行通过的,而上边的两段代码可以说明。前边在讲解自动拆箱解箱的时候已经说明过JDK 1.4和JDK 1.5的区别了,这里不重复。】
  3)java.sql.Date、java.sql.Time、java.sql.Timestamp和java.util.Date
  java.sql.Date和java.util.Date没有特殊的区别,主要在于:java.sql.Date用于直接跟数据库中的Date类型的字段相联系,而java.util.Date是用于程序中的特殊类,可以和系统时间联系,虽然二者都是Date类型,但是相互之间也需要转换的,而且二者存在继承关系
  ——[$]两个Date类——
package org.susan.java.convert;

public class TimeConvert {
    public static void main(String args[]){
        java.util.Date date = new java.util.Date();
        java.sql.Date sqlDate = new java.sql.Date(date.getTime());
        System.out.println(sqlDate);
        System.out.println(date);
        System.out.println(date.getTime() == sqlDate.getTime());
    }
}
  上边这份代码的输出为:
2009-11-08
Sun Nov 08 20:34:15 CST 2009
true
  所以可以知道的是初始化二者的办法就如上边这段代码所写,而且针对两个相同的时间二者都调用getTime方法的时候返回的时间值应该是等价的,在java.sql包里面还有一个Time类,该类会直接和数据库里面的SQL标准下的TIME值相关联,同样可以使用上边的方式来进行初始化操作,而逆向转化就可以省略不讲了。需要改变的只有下边几个最简单的代码就可以进行逆向转换:
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date outputDate = format.parse(sqlDate.toString());
System.out.println(outputDate);
  4)java.sql.Timestamp类的说明:
  ——[$]从String到Timestamp——
package org.susan.java.convert;

import java.sql.Timestamp;

public class TimeStampTester {
    public static void main(String args[]){
        java.util.Date today = new java.util.Date();
        Timestamp ts1 = new Timestamp(today.getTime());
        Timestamp ts2 = Timestamp.valueOf("2005-04-06 09:01:10");
        long tsTime1 = ts1.getTime();
        long tsTime2 = ts2.getTime();
        System.out.println(tsTime1);
        System.out.println(tsTime2);
    }
}
  这段的输出为:
1257684641602
1112749270000
  这个值是不是很莫名,因为看到这个值是时间的毫秒值,所以再进行下边的转换就可以得到时间类型:
java.util.Date setTime = new java.util.Date(tsTime1);
System.out.println(setTime);
  从long到Date的构造是可以直接进行的,所以这个地方的这种转型是没有什么差距的
  Timestamp类主要代表是时间戳,也就是它和SQL标准的TIMESTAMP值对应起来,它添加了用维持SQL TIMESTAMP纳秒值的能力并提供格式化和分析操作以支持用于timestamp值的JDBC转码语法。注:
  该类型是java.util.Date和分离的毫秒值的组合,仅完整的秒数存到java.util.Date组件中,分数秒数-毫秒数-是分离的。getTime方法仅返回完整的秒数,如果需要包含分数秒数的时间值就必须把毫秒转换成毫秒(毫微秒/1000000),并把它添加到getTime值上。注:hashCode()方法使用基本的java.util.Date来实现,因此在它的计算中不包括豪微秒。
  ——[$]java.sql内部的时间转换——
package org.susan.java.convert;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;

public class JavaSQLTester {
    public static void main(String args[]){
        java.util.Date currentDate = new java.util.Date();
        Timestamp stamp = new Timestamp(currentDate.getTime());
        Time time = new Time(currentDate.getTime());
        Date date = new Date(currentDate.getTime());
        Timestamp stamp2 = new Timestamp(time.getTime());
        Time time2 = new Time(time.getTime());
        Date date2 = new Date(time.getTime());
        System.out.println(stamp);
        System.out.println(date);
        System.out.println(stamp2.toString());
        System.out.println(time2);
        System.out.println(date2);
    }
}
  上边这段代码能够说明什么呢?其实不言而喻:
2009-11-08 21:05:59.722
2009-11-08
2009-11-08 21:05:59.722
21:05:59
2009-11-08
  这是内部之间的相互转换,这段烂代码如果仔细琢磨就可以看到它们内部之间的转换的相关法则,这也是说明内部概念的代码段。
  还有一点请开发人员牢记:
  java.util.Date是上边代码使用的三个java.sql的父类,也就是说继承树结构为:
  java.util.Date(C)
  |—java.sql.Date(C)
  |—java.sql.Time(C)
  |—java.sql.Timestamp(C)
  既然如此,相信上边的代码还有什么写法这里就不需要再做说明了!有这样的结构在可能更加容易辅助理解这几个类。
  【*:上边这些代码没有太多的开发价值,需要理解的是这些代码在常用过程可以解除掉我们平时开发的一些小困惑,以不至于我们老是去查阅,这一点需要说明的是这些代码从开发本质上讲没有太大的价值,只是为了说明概念而用,所以一定要理解基础概念才能在这基础之上针对转型进行举一反三。这里不包含三方驱动的一些数据库类型,比如Oracle数据库里面用到的数据类型,如果与三方数据库有关就直接查阅相关驱动文档就可以了。】
  
6.货币、时间、日期格式化
  最后需要说明的是常规用法了,这一个章节会详细解析Java里面的时间类型以及被Cancel掉的时间过时的一些方法等等,这里不提及到i18n的国际化相关内容,只是涉及到一部分相关内容,因为内容比较繁多,抱歉。
  i.货币:
  我们在项目开发过程中经常遇到一些业务数据,比如不同的货币、不同的时间以及需要格式化的一些数据,这一点是我们用得比较多的,注意:本章的内容可能对于任何人而言都是比较基础的,而且大部分跟数据处理有关,不过这里不提及到的有下边几点,所以需要说明:
  • 关于JSON和XML的数据格式这里不做说明,这个会在序列化中讲到
  • 关于二进制数据比如图片读取这里也不做说明,这个会保留到IO章节中
  讲解货币使用之前先介绍几个常用的类:
  1)Locale类:
  Locale对象表示特定的地理、政治和文化地区,需要Locale来执行其任务的操作称为语言环境敏感的操作,它使用Locale为用户量定制信息,例如,显示一个数值就是语言环境的敏感操作,应该根据用户的国家、地区和文化的风俗/传统来格式化该数据。该类的构造方法为:
Locale(String language):
根据语言代码构造一个语言环境
Locale(String language, String country):
根据语言和国家/地区构造一个语言环境
Locale(String language, String country, String variant):
根据语言、国家/地区和变量构造一个语言环境
  语言参数是一个有效的ISO语言代码,这些代码是由ISO-639定义的小写两字母代码,这个完整列表可以从GOOGLE搜索到;
  国家/地区参数是一个有效的ISO国家/地区代码,这些代码是由ISO-3166定义的大写两字母代码,这两种规范希望读者要了解的话从GOOGLE里面自己去搜
  变量参数是特定于供应商或浏览器的代码,例如,用WIN代表Windows、MAC代表Macintosh,POSIX代表POSIX,有两个变量时,把它们用下划线区分开来,把最重要的放在前面。
  因为一个Locale对象就是一个地区的标识符,所以构造一个Locale时不执行有效性的检查,如果想了解特定的资源在您构造的Locale中是否可用,则必须查询那些资源。例如,使用getAvailableLocales方法询问NumberFormat所支持的语言环境,而Locale类本身提供了一些常量用常用的语言环境,比如:Locale.US。创建完Locale后,就可以查询有关自身的信息。比如国家代码、ISO语言代码等等。
  2)NumberFormat类:
  一旦涉及到不同的语言和地区的格式的时候,不得不提及NumberFormat类型,因为在常用的格式里面这个类型也起到了至关重要的作用:
  NumberFormat是所有数据格式的抽象类,既然是抽象类就不可以进行实例化,但是它却有两个子类可以使用:ChoiceFormat和DecimalFormat,这个在格式化的时候会进行详细说明。【*:Java格式化处理的时候这一部分比较混乱主要是我们学习的时候很难理清楚它们相互之间的结构,以及在使用的时候已经习惯了常用的格式化做法,而且平时开发过程对于普通的开发人员很难遇到一个Java软件的多语言开发,或者遇到了过后来查阅也不是什么难点,所以这一点没什么可以争议的,慢慢琢磨就可以知道结果了。】
  NumberFormat可以用户格式化以及解析任何语言环境的数值,使得代码能够完全独立于小数点、千分分隔符甚至所用的特定小数位数的语言环境约定,并与数值格式是否为偶小数无关。若要格式化当前的Locale数值,可使用其中一个工程类方法:
outString = NumberFormat.getInstance().format(inputNumber);
  如果格式化多个数值,那么获取该格式并多次使用它是比较高效的做法,这样系统就不必多次获取关于语言环境语言和国家/地区约定的信息了:
NumberFormat format = NumberFormat.getInstance();
for( int i = 0; i < inputNumber.length; i++ )
{
    System.out.println(format.format(inputNumber[i]) + ":");
}
  如果要格式化不同Locale的日期,可在对getInstance的调用中指定,这里先讲到这里,后边再用代码说明其详细的一些格式化的结构。
  3)Currency类:
  该类型的定义为:
public final class Currency extends Object implements Serializable
  该类表示货币,货币由ISO 4217货币代码标识,该类的设计目标是:对于任何指定货币,从不会有多个Currency实例,因此,没有公共的构造方法,可以使用getInstance方法获得Currency实例。其方法列表:
public String getCurrencyCode():
——获取此货币的ISO 4217货币代码
public int getDefaultFractionDigits():
——获取与此货币一起使用的默认小数位数,例如对于欧元,默认小数位数为2,对于日元,默认为0,对于伪货币返回为-1,这里不解释伪货币:IMF Special Drawing Rights
public static Currency getInstance(Locale locale):
——返回给定语言环境的国家/地区的 Currency 实例。忽略语言环境的语言和变量组件。因为国家/地区会改变其货币,所以结果可能随时间而变化。例如,对于欧洲货币联盟的最初成员国,该方法在 2001 年 12 月 31 日前返回旧的国家/地区货币,从 2002 年 1 月 1 日(各自国家/地区的本地时间)起返回欧元。
public static Currency getInstance(String currencyCode):
——返回给定货币代码的 Currency 实例。
public String getSymbol():
——获取默认语言环境的货币符号。例如,对于美元,如果默认语言环境是美国,则符号是 "$",而对于其他语言环境,它可能是 "US$"。如果无法确定符号,则返回 ISO 4217 货币代码。
public String getSymbol(Locale locale):
——获取指定语言环境的货币符号。例如,对于美元,如果指定语言环境是美国,则符号是 "$",而对于其他语言环境,它可能是 "US$"。如果无法确定符号,则返回 ISO 4217 货币代码。
  ——[$]基本使用方法——
package org.susan.java.format;

import java.text.NumberFormat;
import java.util.Currency;
import java.util.Locale;

public class FormatCurrency {
    public static void main(String args[]){
        // 使用目前的货币格式输出
        NumberFormat format = NumberFormat.getCurrencyInstance();
        System.out.println(format.format(1234.56));
        // 转换成为百分比的格式
        format = NumberFormat.getPercentInstance();
        System.out.println(format.format(0.5733));
        // 使用欧元的格式
        format = NumberFormat.getCurrencyInstance(Locale.UK);
        System.out.println(format.format(1234.58));
        // 使用美元的格式输出
        format = NumberFormat.getCurrencyInstance(Locale.US);
        System.out.println(format.format(1234.98));
        // 使用默认的格式输出
        format = NumberFormat.getInstance();
        System.out.println(format.format(334.58));
        // 使用整数的格式输出,注意这里会进行四舍五入
        format = NumberFormat.getIntegerInstance();
        System.out.println(format.format(1234.58));
        // 使用数值的默认格式化输出
        format = NumberFormat.getNumberInstance();//(Locale.UK);
        System.out.println(format.format(1234.58));
        // 使用UK的货币格式
        Currency currency = Currency.getInstance(Locale.UK);
        format.setCurrency(currency);
        System.out.println(format.format(34.54));
    }
}
  这段代码的输出为:
¥1,234.56
57%
£1,234.58
$1,234.98
334.58
1,235
1,234.58
34.54
  这段代码里面没有太复杂的地方,有一个地方需要说明,可以思考一下,最后一次format为什么没有成功,其原因是在于这个时候的format是使用的NumberFormat.getNumberInstance();这句话创建的,也就是说一旦不是通过getCurrencyInstance方法创建的NumberFormat在使用过程中都会把传入参数作为数字进行传递,也就是说真正在操作过程最初是需要选择创建一个什么类型的NumberFormat,然后再进行format操作才能够得到我们所想要的效果。而百分比在操作的时候也需要注意的是传入的是一个值,format过程是把这个值进行了转换,比如传入0.345那么转换过来就是34.5%,这里也需要注意。
  ii.日期
  上边简单讲解了java.sql里面的日期的使用,不过是基于数据库的,而这里真正要说明的类有两个核心的日期类,一个是java.util.Date,一个是java.util.Calendar,对于日期的一些内容而言需要理解的是Sun公司在进行开发的时候重写过一次,而有很多Date里面的方法被废弃掉了,所以比较核心的一点就是一定要记得哪些方法被废弃了然后能够真正找到替换的方案,在Java编程规范里面,开发过程是不提倡使用废弃的方法的,不论从哪个版本开始,虽然它同样可以通过编译,但是真正使用过程可以解释的是未被废弃的方案应该是比较择优的方案,和Sql那三个类不一样是Date和Calendar是平级的,不存在继承关系。
  二者需要从概念上区分:Date主要描述的是一个精确的瞬间,也就是说Date不适合描述我们常规业务里面的时间;而Calendar描述的常规业务的时间,所以Calendar是常用的时间Java类。
  1)关于时间格式的简介:
  Java语言的Calendar、Date、DateFormat组成了Java标准的一个基本而且比较重要的部分,因为日期是商业逻辑计算一个关键部分,所有开发者应该学会使用这个体系结构来编写基于业务需求的代码,毕竟时间的计算和其他的内容可能存在差距。这个小节主要按照下边的顺序用代码实例来讨论:
  • java.util.Date
  • java.text.DateFormat(抽象类)和它的子类java.text.SimpleDateFormat(具体类)
  • java.util.Calendar(抽象类)和它的子类java.util.GregorianCalendar(具体类)
  [1]Date类:
  ——[$]Date初次使用——
package org.susan.java.date;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class BasicDate {
    public static void main(String args[]){
        Date date = new Date();
        System.out.println(date);
        DateFormat dateFormat = new SimpleDateFormat();
        System.out.println(dateFormat.format(date));
    }
}
  先看看这段代码的输出:
Mon Nov 09 11:25:27 CST 2009
09-11-9 上午11:25
  根据上边的代码可以有几点思考:
  • Date的默认值就是现在的时间值,而且是现在的系统时间值
  • DateFormat因为是一个抽象类本身不能实例化,在实例化一个SimpleDateFormat类实例的时候,默认就是按照系统的语言/区域进行的因为我选择的China的系统区域,所以时间格式为上边输出的:“09-11-9 上午11:25”
  [2]Calendar类和GregorianCalendar类:
  正如上边所讲,Calendar才是使用过程最常用的时间类,也是真正在使用过程用得最多的时间类型,因为它还支持一些比较实用的时间的计算,以及从时间中直接提取一些值,比如Year、Month、Day等
  ——[$]Calendar的初次使用——
package org.susan.java.date;

import java.util.Calendar;

public class BasicCalendar {
    public static void main(String args[]){
        Calendar calendar = Calendar.getInstance();
        System.out.println(calendar);
        System.out.println(calendar.getTime());
    }
}
  这段简单代码的输出为:
java.util.GregorianCalendar[time=1257737567302,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2009,MONTH=10,WEEK_OF_YEAR=46,WEEK_OF_MONTH=2,DAY_OF_MONTH=9,DAY_OF_YEAR=313,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=11,HOUR_OF_DAY=11,MINUTE=32,SECOND=47,MILLISECOND=302,ZONE_OFFSET=28800000,DST_OFFSET=0]
Mon Nov 09 11:32:47 CST 2009
  从这里可以看出Calendar是一个复杂的时间类,它的toString方法包含了很多内容,实际上可以知道的是Calendar一般通过静态方法进行实例化,而且最终生成的就是一个GregorianCalendar的实例。
  ——[$]Calendar用法——
package org.susan.java.date;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* 这个例子主要演示的是Calendar的运算,包括比较复杂的运算过程
*/
public class CalendarUse {
  public static void main(String args[]){
        Calendar calendar = Calendar.getInstance();
        printCalendar(calendar);
        calendar.add(Calendar.WEEK_OF_YEAR,-2);
        printCalendar(calendar);
        calendar.add(Calendar.YEAR,1);
        printCalendar(calendar);
        Calendar calendar2 = new GregorianCalendar();
        calendar2.set(2000, 1, 6);
        printCalendar(calendar2);
        calendar2.set(Calendar.YEAR, 1200);
        calendar2.set(Calendar.MONTH, 3);
        calendar2.set(Calendar.HOUR_OF_DAY, 15);
        printCalendar(calendar2);
        System.out.println(calendar.after(calendar2));
        System.out.println(calendar.before(calendar2));
    }
    private static void printCalendar(Calendar calendar){
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(dateFormat.format(calendar.getTime()));
    }
}
  这段代码的输出为:
2009-11-09 14:47:51
2009-10-26 14:47:51
2010-10-26 14:47:51
2000-02-06 14:47:51
1200-04-06 15:47:51
true
false
  唯一不同的可能尝试的时候获取的时间值不一样,但是输出结果都可以根据这个例子来进行添加、修改、比较两个Calendar包括进行直接的格式化输出,其实API文档里面还有一个针对Calender的例子这里就不做列出,有兴趣的读者自己稍稍做个查阅,这里需要理解以下内容(摘录API介绍):
  GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。GregorianCalendar 是一种混合日历,在单一间断性的支持下同时支持儒略历和格里高利历系统,在默认情况下,它对应格里高利日历创立时的格里高利历日期(某些国家/地区是在 1582 年 10 月 15 日创立,在其他国家/地区要晚一些)。可由调用者通过调用 setGregorianChange() 来更改起始日期。历史上,在那些首先采用格里高利历的国家/地区中,1582 年 10 月 4 日(儒略历)之后就是 1582 年 10 月 15 日(格里高利历)。此日历正确地模拟了这些变化。在开始格里高利历之前,GregorianCalendar 实现的是儒略历。格里高利历和儒略历之间的唯一区别就是闰年规则。儒略历指定每 4 年就为闰年,而格里高利历则忽略不能被 400 整除的世纪年。GregorianCalendar 可实现预期的 格里高利历和儒略历。也就是说,可以通过在时间上无限地向后或向前外推当前规则来计算日期。因此,对于所有的年份,都可以使用 GregorianCalendar 来生成有意义并且一致的结果。但是,采用现代儒略历规则时,使用GregorianCalendar 得到的日期只在历史上从公元 4 年 3 月 1 日之后是准确的。在此日期之前,闰年规则的应用没有规则性,在 45 BC 之前,甚至不存在儒略历。在格里高利历创立以前,新年是 3 月 25 日。为了避免混淆,此日历始终使用 1 月 1 日为新年。如果想要格里高利历转换之前并且处于 1 月 1 日和 3 月 24 日之间的日期,则可以进行手动调整。为 WEEK_OF_YEAR 字段所计算的值的范围从 1 到 53。一年的第一个星期始于 getFirstDayOfWeek() 的最早 7 天,至少包含该年的 getMinimalDaysInFirstWeek() 各天。这取决于 getMinimalDaysInFirstWeek()、getFirstDayOfWeek() 的值以及 1 月 1 日是星期几。一年的第一个星期和下一年的第一个星期之间的各个星期按顺序从 2 到 52 或 53(根据需要)进行编号。例如,1998 年 1 月 1 日是星期四。如果 getFirstDayOfWeek() 为 MONDAY,并且 getMinimalDaysInFirstWeek() 为 4(这些值反映了 ISO 8601 和很多国家/地区标准),则 1998 年的第一个星期开始于 1997 年 12 月 29 日,结束于 1998 年 1 月 4 日。但是,如果 getFirstDayOfWeek() 为 SUNDAY,那么 1998 年的第一个星期开始于 1998 年 1 月 4 日,结束于 1998 年 1 月 10 日;1998 年头三天是 1997 年第 53 个星期的一部分。为 WEEK_OF_MONTH 字段所计算的值的范围从 0 到 6。一个月的第一个星期(WEEK_OF_MONTH = 1 的日期)是该月至少连续 getMinimalDaysInFirstWeek() 天中的最早日期,结束于 getFirstDayOfWeek() 的前一天。与一年的第一个星期不同,一个月的第一个星期可能短于 7 天,也不必从 getFirstDayOfWeek() 这一天开始,并且不包括前一个月的日期。在第一个星期之前该月日期的 WEEK_OF_MONTH 为 0。例如,如果 getFirstDayOfWeek() 为 SUNDAY,getMinimalDaysInFirstWeek() 为 4,那么 1998 年 1 月的第一个星期是从 1 月 4 日星期日到 1 月 10 日星期六。这些天的 WEEK_OF_MONTH 为 1。1 月 1 日星期四到 1 月 3 日星期六的 WEEK_OF_MONTH 为 0。如果 getMinimalDaysInFirstWeek() 变为 3,则 1 月 1 日到 1 月 3 日的 WEEK_OF_MONTH 为 1。clear 方法将日历字段设置为未定义。GregorianCalendar 为每个日历字段使用以下默认值(如果该值未定义)
字段 默认值
ERA AD
YEAR 1970
MONTH JANUARY
DAY_OF_MONTH 1
DAY_OF_WEEK 一个星期的第一天
WEEK_OF_MONTH 0
DAY_OF_WEEK_IN_MONTH 1
AM_PM AM
HOUR,HOUR_OF_DAY,MINUTE,
SECOND,MILLISECOND
0
默认值只是不适合以上没有列出的字段、
  格式化的两个类型留到本章的最后一个章节进行详细说明
  iii.格式化:
  格式化在项目开发过程中也属于常用的内容,上边已经使用了最简单的格式化的相关内容了,这里先列个清单,格式化的类主要集中在java.text包里面:
  java.text.Format(A)
  |—java.text.DateFormat(A)
    |—java.text.SimpleDateFormat(C)
  |—java.text.MessageFormat(C)
  |—java.text.NumberFormat(A)
    |—java.text.ChoiceFormat(C)
    |—java.text.DecimalFormat(C)
  格式化主要接触的就是上边一个继承树结构的一些格式化内容,其中和原来的继承树一样(A)为抽象类(C)为一个具体类,这里仅仅详解SimpleDateFormat以及NumberFormat的两个子类,MessageFormat这里先不做介绍
  1)SimpleDateFormat类:
  SimpleDateFormat是一个以与语言环境有关的方式来格式化和解析日期的具体类,它允许进行格式化、解析和规范化,SimpleDateFormat需要理解的是用户可以自定义模式,模式定义的含义如下:
字母 日期或时间元素 示例子
G Gra标志符 AD
y 1996;96
M 年中的月份 July;Jul;07
w 年中的周数 27
W 月份中的周数 2
D 年中的天数 189
d 月份中的天数 10
F 月份中的星期 2
E 星期中的天数 Tuesday;Tue
a Am/pm标记 PM
H 一天中的小时数(0-23) 0
k 一天中的小时数(1-24) 24
K am/pm中的小时数(0-11) 0
h am/pm中的小时数(1-12) 12
m 小时中的分钟数 30
s 分钟中的秒数 55
S 毫秒数 978
z 时区 Pacific Standard Time;
PST;GMT-08:00
Z 时区 -0800
  2)时间格式化的例子说明:
  ——[$]修改ERA标记——
package org.susan.java.date;

import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ChangeEra {
    public static void main(String args[]){
        SimpleDateFormat format = new SimpleDateFormat();
        DateFormatSymbols dateFormatSymbols = format.getDateFormatSymbols();
        String era[] = {"BCE","CE"};
        dateFormatSymbols.setEras(era);
        format.setDateFormatSymbols(dateFormatSymbols);
        format.applyPattern("MMMM d yyyy G");
        System.out.println(format.format(new Date()));
    }
}
  上边成功修改了年代标记,年代标记一般我们常用的是BC和AD,这里修改成为了自定义的,输出为:
十一月 9 2009 CE
  ——[$]最后一例——
package org.susan.java.date;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class SimpleDateFormatDemo {
    public static void displayDate(Locale currentLocale){
        Date todayDate = new Date();
        SimpleDateFormat format = new SimpleDateFormat("EEE d MMM yy", currentLocale);
        String result = format.format(todayDate);
        System.out.println("Locale: " + currentLocale.toString());
        System.out.println("Result: " + result);
    }
    public static void displayPattern(String pattern,Locale currentLocale){
        Date todayDate = new Date();
        SimpleDateFormat format = new SimpleDateFormat(pattern,currentLocale);
        String output = format.format(todayDate);
        System.out.println(pattern + " " + output);
    }
  public static void main(String args[]){
        Locale[] locales = {new Locale("fr","FR"),new Locale("de","DE"),new Locale("en","US")};
        for( int i = 0; i < locales.length; i++ ){
            displayDate(locales[i]);
        }
        String[] patterns = {"dd.MM.yy", "yyyy.MM.dd G 'at' hh:mm:ss z",
               "EEE, MMM d, ''yy", "h:mm a", "H:mm", "H:mm:ss:SSS", "K:mm a,z",
               "yyyy.MMMMM.dd GGG hh:mm aaa"};
        for( int i = 0; i < patterns.length; i++ ){
            displayPattern(patterns[i], Locale.getDefault());
        }   
    }
}
  上边的输出为:
Locale: fr_FR
Result: lun. 9 nov. 09
Locale: de_DE
Result: Mo 9 Nov 09
Locale: en_US
Result: Mon 9 Nov 09
dd.MM.yy 09.11.09
yyyy.MM.dd G 'at' hh:mm:ss z 2009.11.09 公元 at 03:51:54 CST
EEE, MMM d, ''yy 星期一, 十一月 9, '09
h:mm a 3:51 下午
H:mm 15:51
H:mm:ss:SSS 15:51:54:796
K:mm a,z 3:51 下午,CST
yyyy.MMMMM.dd GGG hh:mm aaa 2009.十一月.09 公元 03:51 下午
  到这里就不再针对SimpleDateFormat做说明了,上边的例子仔细分析分析就可以知道它的用法了
  最后提供一个Date方法的过时替换方法表,就是Date里面的过时方法用Calendar的什么方法来替代:
  构造方法表:
java.util.Date(int year,int month,int date):
——从 JDK 1.1 开始,由 Calendar.set(year + 1900, month, date)GregorianCalendar(year + 1900, month, date) 取代
java.util.Date(int year, int month, int date, int hrs, int min)
——从 JDK 1.1 开始,由 Calendar.set(year + 1900, month, date, hrs, min)GregorianCalendar(year + 1900, month, date, hrs, min) 取代
java.util.Date(int year, int month, int date, int hrs, int min, int sec)
——从 JDK 1.1 开始,由 Calendar.set(year + 1900, month, date, hrs, min, sec)GregorianCalendar(year + 1900, month, date, hrs, min, sec) 取代
java.util.Date(String s)
——从 JDK 1.1 开始,由 DateFormat.parse(String s) 取代
  普通方法表:
public int getDate()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.DAY_OF_MONTH) 取代
public int getDay()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.DAY_OF_WEEK) 取代
public int getHours()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.HOUR_OF_DAY) 取代
public int getMinutes()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.MINUTE) 取代
public int getMonth()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.MONTH) 取代
public int getSeconds()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.SECOND) 取代
public int getTimezoneOffset()
——从 JDK 1.1 开始,由 -(Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000) 取代
public int getYear()
——从 JDK 1.1 开始,由 Calendar.get(Calendar.YEAR) - 1900 取代
public static long parse(String s)
——从 JDK 1.1 开始,由 DateFormat.parse(String s) 取代
public void setDate(int date)
——从 JDK 1.1 开始,由 Calendar.set(Calendar.DAY_OF_MONTH, int date) 取代
public void setHours(int hours)
——从 JDK 1.1 开始,由 Calendar.set(Calendar.HOUR_OF_DAY, int hours) 取代
public void setMinutes(int minutes)
——从 JDK 1.1 开始,由 Calendar.set(Calendar.MINUTE, int minutes) 取代
public void setSeconds(int seconds)
——从 JDK 1.1 开始,由 Calendar.set(Calendar.SECOND, int seconds) 取代
public void setYear(int year)
——从 JDK 1.1 开始,由 Calendar.set(Calendar.YEAR, year + 1900) 取代
public String toGMTString()
——从 JDK 1.1 开始,由 DateFormat.format(Date date) 取代,使用 GMT TimeZone
public String toLocaleString()
——从 JDK 1.1 开始,由 DateFormat.format(Date date) 取代
public static long UTC(int year,int month,int date,int hrs,int min,int sec)
——从 JDK 1.1 开始,由 Calendar.set(year + 1900, month, date, hrs, min, sec) GregorianCalendar(year + 1900, month, date, hrs, min, sec) 取代,使用 UTC TimeZone,后跟 Calendar.getTime().getTime()。

7.小结:
  这个章节的内容最核心的部分在于String的用法以及其存储结构,其他的内容都是和字符串相关的一些话题,这些话题的涵盖面在于我们开发系统的时候经常会去查阅的一些主题,因为最近在开发一个比较复杂的项目一直没有时间来书写这一个章节,而且这个章节的内容第一是比较多,第二是比较杂乱,所以写的时候也不知道什么行文,所以请见谅!有什么笔误来Email告知:[email protected],这个章节的内容结束了Java语言基础部分就只剩下三个比较大的主题了,我会尽量早点写出来的,而且最近在换工作,所以可能时间不多,抱歉抱歉。这个章节里面我只是提取了API里面可能真正会用得上的比较实用的内容,其他的更加概念的东西没有提取出来,如果读者不满足文章里面的内容可以自己去查阅一下API的内容哈,字符串部分是这个章节的主题,因为这个章节的内容我做了内存探测的测试。谢谢合作!

你可能感兴趣的:(java)