java交互(处理日期的类、正则表达式)

169 阅读13分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情

4 处理日期的类

4.1 Date类

Java提供了Date类来处理日期、时间(此处的Date是指java.util包下的Date类,而不是java.sql包下的Date类),Date对象既包含日期,也包含时间。Date类从JDK 1.0起就开始存在了。但正因为它历史悠久,所以它的大部分构造器、方法都已经过时,不再推荐使用了。 剩下的两个构造器如下所示。

  • Date():生成一个代表当前日期时间的Date对象。该构造器在底层调用System.currentTimeMillis()获得long整数作为日期参数。
  • Date(long date):根据指定的long型整数来生成一个Date对象。该构造器的参数表示创建的Date对象和GMT 1970年1月1日00:00:00之间的时间差,以毫秒作为计时单位。

与Date构造器相同的是,Date对象的大部分方法也Deprecated了,剩下为数不多的几个方法。

  • boolean after(Date when):测试该日期是否在指定日期when之后。
  • boolean before(Date when):测试该日期是否在指定日期when之前。
  • int compareTo(Date anotherDate):比较两个日期的大小,后面的时间大于前面的时间时返回-1,否则返回1。
  • boolean equals(Object obj):当两个时间表示同一时刻时返回true。
  • long getTime():返回该时间对应的long型整数,即从GMT 1970-01-0100:00:00 到该Date对象之间的时间差,以毫秒作为计时单位。
  • void setTime(long time):设置该Date对象的时间。

因为Date类的很多方法已经不推荐使用了,所以Date类的功能已经被大大削弱了。例如,对时间进行加减运算,获取指定Date对象里年、月、日的所有方法都已被Deprecated,如果需要对日期进行这些运算,则应该使用Calendar工具类。

4.2 Calendar类

Calendar类本身是一个抽象类,它是所有日历类的模板,并提供了一些所有日历通用的方法;但它本身不能直接实例化,程序只能创建Calendar子类实例,Java本身提供了一个GregorianCalendar类,一个代表GregorianCalendar的子类,它代表了我们通常所说的公历

Calendar类是一个抽象类,所以不能使用构造器来创建Calendar对象。但它提供了几个静态getInstance()方法获取Calendar对象,这些方法根据TimeZoneLocale类来获取特定的Calendar,如果不指定TimeZone、Locale,则使用默认的TimeZone、Locale来创建Calendar。

Calendar与Date都是表示日期的工具类,它们直接可以自由转换,如下代码所示。

        //创建一个默认的Calendar对象
        Calendar calendar=Calendar.getInstance();
        //从Calendar 对象中取出Date 对象
        Date date=calendar.getTime();
        //通过Date对象获得对应的Calendar对象
        //因为Calendar/GregorianCalendar没有构造函数可以接收Date对象
        //所以必须先获得一个Calendar实例,然后调用其setTime()方法
        Calendar calendar2=Calendar.getInstance();
        calendar2.setTime(date);

Calendar类提供了大量访问、修改日期时间的方法,常用方法如下。

  • void add(int field, int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。
  • int get(int field):返回指定日历字段的值。
  • int getActualMaximum(int field):返回指定日历字段可能拥有的最大值。例如月,最大值为11。
  • int getActualMinimum(int field):返回指定日历字段可能拥有的最小值。例如月,最小值为0。
  • void roll(int field, int amount):与add()方法类似,区别在于加上amount后超过了该字段所能表示的最大范围时,也不会向上一个字段进位。
  • void set(int field, int value):将给定的日历字段设置为给定值。
  • void set(int year, int month, int date):设置Calendar对象的年、月、日3个字段的值。

上面的很多方法都需要一个int类型的field参数,field是Calendar类的静态Field,如Calendar.YEAR、Calendar.MONTH等分别代表了年、月、日、小时、分钟、秒等时间字段。需要指出的是,Calendar.MONTH字段代表月份,月份的起始值不是1,而是0,所以要设置8月时,用7而不是8。如下程序示范了Calendar类的常规用法

        public class CalendarTest
        {
            public static void main(String[] args)
            {
                  Calendar c=Calendar.getInstance();
                  // 取出年
                  System.out.println(c.get(YEAR));
                  // 取出月份
                  System.out.println(c.get(MONTH));
                  // 取出日
                  System.out.println(c.get(DATE));
                  // 分别设置年、月、日、小时、分钟、秒
                  c.set(2003 , 10 , 23 , 12, 32, 23); //2003-11-23 12:32:23
                  System.out.println(c.getTime());
                  // 将Calendar的年前推1年
                  c.add(YEAR , -1); //2002-11-23 12:32:23
                  System.out.println(c.getTime());
                  // 将Calendar的月前推8个月
                  c.roll(MONTH , -8); //2002-03-23 12:32:23
                  System.out.println(c.getTime());
            }
        }

Calendar类还有如下几个注意点。 1.add与roll的区别 add(int field, int amount)的功能非常强大,add主要用于改变Calendar的特定字段的值。如果需要增加某字段的值,则让amount为正数;如果需要减少某字段的值,则让amount为负数即可。 2.设置Calendar的容错性

4.3 TimeZone类

在地理上,地球被划分成24个时区,中国北京时间属于东八区,而程序中对时间的默认实现是以格林威治时间为标准的,这样就产生了8小时的时间差。为了让程序更加通用,可以使用TimeZone设置程序中时间所属的时区,其中TimeZone就代表了时区。

TimeZone是一个抽象类,不能调用其构造器来创建实例,但可以调用它的静态方法:getDefault()getTimeZone()得到TimeZone实例。

        public class TimeZoneTest
        {
            public static void main(String[] args)
            {
                  //取得Java所支持的所有时区ID
                  String[] ids=TimeZone.getAvailableIDs();
                  System.out.println(Arrays.toString(ids));
                  TimeZone my=TimeZone.getDefault();
                  //获取系统默认时区的ID:Asia/Shanghai
                  System.out.println(my.getID());
                  //获取系统默认时区的名称:中国标准时间
                  System.out.println(my.getDisplayName());
                  //获取指定ID的时区名称:纽芬兰标准时间
                  System.out.println(TimeZone
                        .getTimeZone("CNT").getDisplayName());
            }
        }

5 正则表达式

正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作。 String类里也提供了如下几个特殊的方法。

  • boolean matches(String regex):判断该字符串是否匹配指定的正则表达式。
  • String replaceAll(String regex, String replacement):将该字符串中所有匹配regex的子串替换成replacement。
  • String replaceFirst(String regex, String replacement):将该字符串中第一个匹配regex的子串替换成replacement。
  • String[] split(String regex):以regex作为分隔符,把该字符串分割成多个子串。

上面这些特殊的方法都依赖于Java提供的正则表达式支持,除此之外,Java还提供了PatternMatcher两个类专门用于提供正则表达式支持。

5.1 创建正则表达式

前面已经介绍了,正则表达式就是一个用于匹配字符串的模板,可以匹配一批字符串,所以创建正则表达式就是创建一个特殊的字符串

正则表达式所支持的合法字符 在这里插入图片描述 除此之外,正则表达式中有一些特殊字符,这些特殊字符在正则表达式中有其特殊的用途,比如前面介绍的反斜线(\)。如果需要匹配这些特殊字符,就必须首先将这些字符转义,也就是在前面添加一个反斜线(\)。正则表达式中的特殊字符如下表所示。

正则表达式中的特殊字符 在这里插入图片描述 将上面多个字符拼起来,就可以创建一个正则表达式。例如:

        "\\u0041\\"  //匹配a\
        "\\0101\t"   //匹配a<制表符>
        "\?\["   //匹配?[

上面的正则表达式依然只能匹配单个字符,这是因为还未在正则表达式中使用“通配符”,“通配符”是可以匹配多个字符的特殊字符。正则表达式中的“通配符”远远超出了普通通配符的功能,它被称为预定义字符,正则表达式支持如表所示的预定义字符。 在这里插入图片描述 上面的7个预定义字符其实很容易记忆——d是digit的意思,代表数字;s是space的意思,代表空白;w是word的意思,代表单词。d、s、w的大写形式恰好匹配与之相反的字符。

        c\wt  //可以匹配cat、cbt、cct、c0t、c9t等一批字符串
        \d\d\d-\d\d\d-\d\d\d\d  //匹配如000-000-0000形式的电话号码

在一些特殊情况下,例如,若只想匹配a~f的字母,或者匹配除了ab之外的所有小写字母,或者匹配中文字符,上面这些预定义字符就无能为力了,此时就需要使用方括号表达式,方括号表达式有如表7.4所示的几种形式。

在这里插入图片描述 正则表示还支持圆括号表达式,用于将多个表达式组成一个子表达式,圆括号中可以使用或运算符(|)。例如,正则表达式"(public|protected|private)"用于匹配Java的三个访问控制符其中之一。

5.2 使用正则表达式

一旦在程序中定义了正则表达式,就可以使用Pattern和Matcher来使用正则表达式。

Pattern对象是正则表达式编译后在内存中的表示形式,因此,正则表达式字符串必须先被编译为Pattern对象,然后再利用该Pattern对象创建对应的Matcher对象。执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可共享同一个Pattern对象。

        //将一个字符串编译成Pattern对象
        Pattern p=Pattern.compile("a*b");
        //使用Pattern对象创建Matcher对象
        Matcher m=p.matcher("aaaaab");
        boolean b=m.matches(); //返回true

上面定义的Pattern对象可以多次重复使用。如果某个正则表达式仅需一次使用,则可直接使用Pattern类的静态matches方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配,如下所示。

        boolean b=Pattern.matches("a*b", "aaaaab");  //返回true

上面语句等效于前面的三条语句。但采用这种语句每次都需要重新编译新的Pattern对象,不能重复利用已编译的Pattern对象,所以效率不高。

Pattern是不可变类,可供多个并发线程安全使用。

Matcher类提供了如下几个常用方法

  • find():返回目标字符串中是否包含与Pattern匹配的子串。
  • group():返回上一次与Pattern匹配的子串。
  • start():返回上一次与Pattern匹配的子串在目标字符串中的开始位置。
  • end():返回上一次与Pattern匹配的子串在目标字符串中的结束位置加1。
  • lookingAt():返回目标字符串前面部分与Pattern是否匹配。
  • matches():返回整个目标字符串与Pattern是否匹配。
  • reset(),将现有的Matcher对象应用于一个新的字符序列。

注意: 在Pattern、Matcher类的介绍中经常会看到一个CharSequence接口,该接口代表一个字符序列,其中CharBuffer、String、StringBuffer、StringBuilder都是它的实现类。简单地说,CharSequence代表一个各种表示形式的字符串。

6.7 使用DateFormat格式化日期、时间

与NumberFormat相似的是,DateFormat也是一个抽象类,它也提供了几个工厂方法用于获取DateFormat对象

  • getDateInstance():返回一个日期格式器,它格式化后的字符串只有日期,没有时间。该方法可以传入多个参数,用于指定日期样式和Locale等参数;如果不指定这些参数,则使用默认参数。
  • getTimeInstance():返回一个时间格式器,它格式化后的字符串只有时间,没有日期。该方法可以传入多个参数,用于指定时间样式和Locale等参数;如果不指定这些参数,则使用默认参数。
  • getDateTimeInstance():返回一个日期、时间格式器,它格式化后的字符串既有日期,也有时间。该方法可以传入多个参数,用于指定日期样式、时间样式和Locale等参数;如果不指定这些参数,则使用默认参数。

上面3个方法可以指定日期样式、时间样式参数,它们是DateFormat的4个静态常量:FULL、LONG、MEDIUM和SHORT,通过这4个样式参数可以控制生成的格式化字符串。看如下例子程序。

        public class DateFormatTest
        {
            public static void main(String[] args)
            {
                  //需要被格式化的时间
                  Date dt=new Date();
                  //创建两个Locale,分别代表中国、美国
                  Locale[] locales={Locale.CHINA, Locale.US};
                  DateFormat[] df=new DateFormat[16];
                  //为上面两个Locale创建16个DateFormat对象
                  for (int i=0 ; i < locales.length ; i++)
                  {
                        df[i * 8]=DateFormat.getDateInstance(SHORT, locales[i]);
                        df[i * 8 + 1]=DateFormat.getDateInstance(MEDIUM, locales[i]);
                        df[i * 8 + 2]=DateFormat.getDateInstance(LONG, locales[i]);
                        df[i * 8 + 3]=DateFormat.getDateInstance(FULL, locales[i]);
                        df[i * 8 + 4]=DateFormat.getTimeInstance(SHORT, locales[i]);
                        df[i * 8 + 5]=DateFormat.getTimeInstance(MEDIUM , locales[i]);
                        df[i * 8 + 6]=DateFormat.getTimeInstance(LONG , locales[i]);
                        df[i * 8 + 7]=DateFormat.getTimeInstance(FULL , locales[i]);
                  }
                  for (int i=0 ; i < locales.length ; i++)
                  {
                        switch (i)
                        {
                            case 0:
                                  System.out.println("-------中国日期格式--------");
                                  break;
                            case 1:
                                  System.out.println("-------美国日期格式--------");
                                  break;
                        }
                        System.out.println("SHORT格式的日期格式:"
                            + df[i * 8].format(dt));
                        System.out.println("MEDIUM格式的日期格式:"
                            + df[i * 8 + 1].format(dt));
                        System.out.println("LONG格式的日期格式:"
                            + df[i * 8 + 2].format(dt));
                        System.out.println("FULL格式的日期格式:"
                            + df[i * 8 + 3].format(dt));
                        System.out.println("SHORT格式的时间格式:"
                            + df[i * 8 + 4].format(dt));
                        System.out.println("MEDIUM格式的时间格式:"
                            + df[i * 8 + 5].format(dt));
                        System.out.println("LONG格式的时间格式:"
                            + df[i * 8 + 6].format(dt));
                        System.out.println("FULL格式的时间格式:"
                            + df[i * 8 + 7].format(dt));
                  }
            }
        }

提示: 获得了DateFormat之后,还可以调用它的setLenient(boolean lenient)方法来设置该格式器是否采用严格语法。举例来说,如果采用不严格的日期语法(该方法的参数为true),对于字符串"2004-2-31"将会转换成2004年3月2日;如果采用严格的日期语法,解析该字符串时将抛出异常。

DateFormat的parse()方法可以把一个字符串解析成Date对象,但它要求被解析的字符串必须符合日期字符串的要求,否则可能抛出ParseException异常。例如,如下代码片段:

        String str1="2007-12-12";
        String str2="2007年12月10日";
        //下面输出 Wed Dec 12 00:00:00 CST 2007
        System.out.println(DateFormat.getDateInstance().parse(str1));
        //下面输出 Mon Dec 10 00:00:00 CST 2007
        System.out.println(DateFormat.getDateInstance(LONG).parse(str2));
        //下面抛出 ParseException异常
        System.out.println(DateFormat.getDateInstance().parse(str2));

6.8 使用SimpleDateFormat格式化日期

前面介绍的DateFormat的parse()方法可以把字符串解析成Date对象,但实际上DataFormat的format()方法不够灵活——它要求被解析的字符串必须满足特定的格式!为了更好地格式化日期、解析日期字符串,Java提供了SimpleDateFormat类。

SimpleDateFormat可以非常灵活地格式化Date,也可以用于解析各种格式的日期字符串。创建SimpleDateFormat对象时需要传入一个pattern字符串,这个pattern不是正则表达式,而是一个日期模板字符串

        public class SimpleDateFormatTest
        {
            public static void main(String[] args)
                  throws ParseException
            {
                  Date d=new Date();
                  //创建一个SimpleDateFormat对象
                  SimpleDateFormat sdf1=new SimpleDateFormat("Gyyyy年中第D天");
                  //将d格式化成日期,输出:公元2007年中第354天
                  String dateStr=sdf1.format(d);
                  System.out.println(dateStr);
                  //一个非常特殊的日期字符串
                  String str="07###三月##21";
                  SimpleDateFormat sdf2=new SimpleDateFormat("y###MMM##d");
                  //将日期字符串解析成日期,输出:Wed Mar 21 00:00:00 CST 2007
                  System.out.println(sdf2.parse(str));
            }
        }

提示: 如果想知道SimpleDateFormat支持哪些日期、时间占位符,可以查阅API文档中Simple DateFormat类的说明,此处不再赘述。