避免空的catch块当期望异常时。

7

我正在尝试使用SimpleDateFormat解析日期。由于我的服务接受多种日期格式,因此我采用了这种方法:

String[] formats = {
        "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
        "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
        "yyyy-MM-dd'T'HH:mm:ss.SSS-HH:mm",
        "EEE MMM dd HH:mm:ss Z yyyy"};

for (String format : formats)
{
    try
    {
        return new SimpleDateFormat(format).parse(dateString);
    }
    catch (ParseException e) {}
}
return null;
try-catch的理由在于,如果当前日期格式无法解析dateString,则会抛出一个Exception,代码将继续循环直到找到合适的日期格式,或返回nullcatch块仅仅是为了让try块有一些内容跟随它(如果没有后续内容,可能会导致编译错误)。
我可以保留原始代码,但是空的catch块是不良实践。这也会给未来的维护者带来困惑。而且这样做并不雅观。
我可以在catch块中添加以下内容:
catch (Exception e)
{
    if (!(e instanceof ParseException))
    {
        throw e;
    }
}

但是,里面的代码没有任何作用,因为除了try块可能会抛出ParseException之外,没有其他Exception(我在代码前面检查了NullPointerException)。使用final块代替catch块也是无用的。

有没有办法避免空的或者无用的catch块?能否完全避免使用try-catch


类似的问题(但不完全相同):

如何避免空的catch子句

空的catch块

空catch语句是否可以接受?


除了 try 块可能抛出的 ParseException,没有其他异常,因此没有任何目的。根据 dateString 来源的不同,可能会出现 NullPointerException - Andy Turner
抱歉,我没有在我的代码中包含那个,但是在那之前有一个空指针检查。 - SegFault
2
你的评论,“catch块只是为了让try块有一个后续”似乎是相反的。如果您不想要catchfinally,那么您也不需要或希望try。据我所知,catch块通常存在于要捕获(特定的)异常的情况下。 - John Bollinger
我可以将以下内容放在catch块中:不要过度捕获异常:您只需要处理ParseException。然后,如果抛出了RuntimeException,那么它将被传播;并且编译器保证除了ParseException之外不会抛出任何已检查的异常。因此,这只是以一种复杂的方式获得基本相同的行为。 - Andy Turner
我明白了。我最初在catch块中有一个记录器,我想也许我可以摆脱整个东西,没有任何异常抛出。我将只保留一个记录器。 - SegFault
7个回答

3

您的代码没有问题。在这种情况下,当SimpleDateFormat抛出ParseException时不采取任何措施是有意义和合理的。唯一不同的是我会插入一个相关的文档注释:

for (String format : formats)
{
    try
    {
        return new SimpleDateFormat(format).parse(dateString);
    }
    catch (ParseException e) {
        // The string does not conform to the trial format.
        // Just try the next format, if any.
    }
}

使用空的catch块来避免处理应该被处理的异常是不好的做法。而你的情况则是一个特殊情况,正确的处理方式是什么都不做。


2
到目前为止,所有给出的答案都说你必须使用异常捕获,但是有方法可以完全避免异常。我演示了两种方法,一种是使用内置的SimpleDateFormat-API,另一种是使用我的库Time4J

SimpleDateFormat

private static final List<SimpleDateFormat> SDF_FORMATS;

static {
    String[] formats =
        {
               "yyyy-MM-dd'T'HH:mm:ss.SSSX", 
               "yyyy-MM-dd'T'HH:mm:ss.SSS-HH:mm",
               "EEE MMM dd HH:mm:ss Z yyyy"
        };

    SDF_FORMATS = 
        Arrays.stream(formats)
            .map(pattern -> new SimpleDateFormat(pattern, Locale.ENGLISH))
            .collect(Collectors.toList());
}

public static java.util.Date parse(String input) {
  for (SimpleDateFormat sdf : SDF_FORMATS) {
    ParsePosition pos = new ParsePosition(0);
    java.util.Date d = sdf.parse(input, pos);
    if (pos.getErrorIndex() == -1) {
        return d;
    }
  }
  // log an error message
  return null; // or throw an exception
}

相对于try-catch代码,这段代码虽然表现不是非常抢眼,但已经有明显的性能提升。但需要注意的是,这个代码不是线程安全的。在多线程环境中使用时,你必须要么始终实例化一个新的SimpleDateFormat,或者尝试使用ThreadLocal来最小化这样的实例化。

Time4J

private static final MultiFormatParser<Moment> MULTI_FORMAT_PARSER;

static {
    String[] formats =
        {
               "yyyy-MM-dd'T'HH:mm:ss.SSSX", 
               "yyyy-MM-dd'T'HH:mm:ss.SSS-HH:mm",
               "EEE MMM dd HH:mm:ss Z yyyy"
        };

    List<ChronoFormatter<Moment>> formatters = 
        Arrays.stream(formats)
            .map(pattern -> 
                ChronoFormatter.ofMomentPattern(
                    pattern,
                    PatternType.CLDR,
                    Locale.ENGLISH,
                    Timezone.ofSystem().getID()))
            .collect(Collectors.toList());
    MULTI_FORMAT_PARSER = MultiFormatParser.of(formatters);
}

public static java.util.Date parse(String input) {
      ParseLog plog = new ParseLog();
      Moment m = MULTI_FORMAT_PARSER.parse(input, plog);
      if (plog.isError()) {
         // log an error message based on plog.getErrorMessage()
         return null; // or throw an exception
      } else {
         return TemporalType.JAVA_UTIL_DATE.from(m); // converted to old API
      }
}

这是目前解析多种格式最快的方法。您可以自己试试(也可以使用3.x版本行,在Java-6或Android上使用Time4J,但需要在静态初始化程序中调整Java-8-streaming代码)。性能方面的改进非常大。而且代码也是线程安全的。
关于您的格式模式的一般说明:
  • 我担心看到模式“yyyy-MM-dd'T'HH:mm:ss.SSS-hh:mm”,因为“h”代表12小时制钟表(所以缺少AM / PM!)。
  • 我还担心看到模式“yyyy-MM-dd'T'HH:mm:ss.SSS'Z'”,因为在输入中转义文本“Z”是错误的,除非您在 SimpleDateFormat 实例上明确设置GMT-Zone(零偏移量)。背景:ISO-8601定义了这种模式,并始终将偏移UTC + 00:00分配给文本“Z”。通过转义,您将获得基于错误计算的结果(没有异常或警告)。

谢谢你的回答,我会研究你提到的方法。另外,我会改变我的日期格式。因为有时我会收到这种日期:2016-07-15T08:32:53.689Z,所以我必须在其中一个日期格式中包含 'Z' - SegFault
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Meno Hochschild
如果您想使用SimpleDateFormat且正在使用Java-7,则还可以使用模式符号“X”,该符号可以正确解释文字“Z”。 - Meno Hochschild

1
如果你想避免try/catch块,是可以的,但它肯定会增加代码的大小。我对未注释的空catch块最大的问题是,它们留下了一个问题:“我是否打算进行//TODO:检查异常?”仅当它以这种方式无意义时,即如果您正在解析以查看某些内容是否为数字,而不是使用isNumber方法时,代码味道才真正适用于IMO。
您可以创建一个显式方法来检查它是否可解析,然后在可解析时返回该值。
boolean isParseable(Sting dateString, SimpleDateFormat format) {
    try {
        format.parse(dateString);
        return true;
    }
    catch(ParseException e) {
        return false;
    }
}

然后应用

for (String format : formats)
{
    SimpleDateFormat format = new SimpleDateFormat(format);
    if(isParseable(dateString, format))
        return format.parse(dateString);
}
return null;

根据您处理SDF对象的方式,您可以选择实例化它两次,传递它,或传回null / String值。


1
你可以让 isParseable(或等价物)返回一个 Optional<Date>,这样你就不必解析两次了。 - Andy Turner

1
当你尝试一个格式又尝试另一个格式来格式化日期时,很可能会出现许多异常情况。传播这些异常是没有帮助的,大部分时间看到它们也没有帮助。我唯一会做的改变是将异常记录在DEBUG或TRACE级别(低于正常日志记录水平,当代码在开发中运行时设置),这样,以防你想回来检查发生了什么,你只需要更改日志配置即可。
for (String format : formats)
{
    try
    {
        return new SimpleDateFormat(format).parse(dateString);
    }
    catch (ParseException e) {
        if (log.isTrace()) {
            log.trace("formatting failed for " + dateString 
            + " using format " + format, e);
        }
    }
}
return null;

0

可以这样做。但在我看来(这也是许多其他专业软件开发人员的共识),空的catch块是一种反模式。你在代码中遇到了一个异常情况,为什么要忽略它呢?这意味着你得到了不良/无法使用的输入。这值得一些关注,你同意吗?

所以,是的,你可以这样做。但是,请问你真的想这样做吗?还是你更愿意对不良输入采取更智能的处理方式,而不是默默地将其丢弃。让我给你这个建议:至少记录异常数据,以便自己(和任何与你的代码一起工作的人)能够查看。不要只记录异常;尝试在日志语句中捕获应用程序状态中出现的错误/异常。在你的情况下,我建议记录失败的日期字符串。

最后,如果你想完全省略catch块...好吧,你不能完全忽略异常。你可以在方法原型中包含一个throws Exception声明。但即使如此,使用你的API的任何人都必须捕获它。这就是Java中检查异常的工作方式的一部分。


我的问题是如何避免这种情况,而不是询问它是否可以或不可以。 - SegFault
@PhotometricStereo 请看我的回答的最后一段。你可以避免在代码中编写catch块,但代价是期望别人捕获它。这是唯一的方法。如果字符串无法解析,DateFormat的规范要求抛出已检查异常(具体来说是ParseException)。语言规范防止你完全忽略它们。 - nasukkin
你不能省略catch块。try必须至少跟着一个catch或者final - SegFault
@PhotometricStereo 是的,你说得对。也许我应该多说一点。当我说可以省略 catch 时,我的意思是完全省略 try-catch 结构。 - nasukkin

0

你说你不喜欢 SimpleDateFormat 提供的接口。你唯一的选择是将该接口包装在另一个接口中,以提供你所寻找的接口。例如:

class MySimpleDateFormat {
  final String format;

  MySimpleDateFormat(format f) {
    this.format = f;
  }

  String format() {
    try {
      new SimpleDateFormat(format).format();
    } catch (Exception e) {
      return f.format();
    }
    return null;
  }
}

0
我会使用类似 org.apache.commons.lang3.time.DateUtils.parseDate 的方法(如果需要匹配整个模式,则使用 parseDateStrictly)。它接受可能的模式列表。请看以下示例:
Date d = DateUtils.parseDate("23/10/2014T12:34:22", 
        new String[] {"yyyy/MM/dd'T'HH:mm:ss",
            "dd/MM/yyyy'T'HH:mm:ss"});

System.out.println(d);

请查看此文档:http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/time/DateUtils.html

如果输入不符合任何模式,将会抛出异常。空的 catch 块是一种不好的实践。您应该处理异常,可以记录错误、抛出异常或者进行其他操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接