NumberFormat / DecimalFormat

时间:2016-05-30 09:07:25

标签: java multithreading formatting format decimalformat

无法在网络上找到合适的解决方案,因此我想问一下我使用java格式的方式是否正确。

1)在NumberFormat.java文档中,它表示

  

数字格式通常不同步。建议为每个线程创建单独的格式实例。

我们一直在多线程环境中使用格式对象(静态初始化),到目前为止没有任何问题。是否因为一旦定义了格式,我们的状态就不会改变(即之后不再调用setter)

2)我现在需要定义一个新格式,它应该在逗号后输出一个或两个有效数字,具体取决于一些额外的逻辑。我这样做的方法是定义一个新的格式包装器,并根据覆盖的#format(double,StringBuffer,FieldPosition)方法中的情况委托给两个不同的DecimalFormat。这是代码:

private final NumberFormat FORMAT = new DecimalFormat() {
    private final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.##");
    private final NumberFormat DECIMAL_FORMAT_DIGIT = new DecimalFormat(
                    "0.0#");
    public StringBuffer format(double number, StringBuffer result, java.text.FieldPosition fieldPosition) {
        if ((number >= 10 && Math.ceil(number) == number)) {
            return DECIMAL_FORMAT.format(number, result, fieldPosition);
        } else {
            return DECIMAL_FORMAT_DIGIT.format(number, result, fieldPosition);
        }
    }
};

这是最佳做法吗?我担心没有实际使用包装器类(它只用于遵守NumberFormat接口并委托内部格式的所有工作)。我不想调用DecimalFormat#applyPattern(),因为我认为这会破坏易失性并发性。

由于

3 个答案:

答案 0 :(得分:3)

  
      
  1. 我们一直在多线程环境中使用格式对象(静态初始化),到目前为止没有任何问题。可能是因为一旦定义了格式,我们的状态就不会改变(即之后不会调用setter)
  2.   

不可能确切地说出为什么没有看到任何问题,因为我们并不确切知道你是如何使用它们的。在我的头脑中,有几个原因可能是:

  • 您没有点击使用可变实例变量的DecimalFormat中的任何代码路径;
  • 您“恰巧”应用互斥,因此您一次不会在多个线程中使用该实例;
  • 您正在使用实际上正确同步的实现(请注意Javadoc说“通常不同步”,而不是“从未同步”);
  • 你实际上 有问题,但你只是没有充分监控它们;

正如我昨天看到其他人评论的那样,关于同步问题的一点是如果不同步,你不能保证看到问题;只是你不能保证不会看到它们。

关键在于,如果你没有应用同步,那么你可能会完全不知道任何数量的细微变化。今天它有效,明天它没有;你将有一个全能的工作来解决原因。

  
      
  1. 这是最佳做法吗?
  2.   

我可以在这里想到几个问题:

  1. 通过扩展课程,您可能会犯fragile base class problem

    简而言之,除非您实际上明确地在public StringBuffer format(double, StringBuffer, java.text.FieldPosition )实例上调用DecimalFormat方法,否则您无法可靠地知道被覆盖的方法是否实际上是被调用的方法:对实现的更改基类(DecimalFormat)可能会改变您依赖的逻辑来调用该方法。

  2. 您有三个可变实例 - FORMATDECIMAL_FORMATDECIMAL_FORMAT_DIGIT - 它们具有各种设置器来改变其行为。

    将所有这些设置者传播到所有实例,以便它们的行为一致,例如如果您在setPositivePrefix上致电FORMAT,则还应在DECIMAL_FORMATDECIMAL_FORMAT_DIGIT上调用相同的方法。

  3. 除非您确实需要将FORMAT作为参数传递给方法,否则如果您只定义了一个调用逻辑的普通旧方法,它会更加健壮:基本上,移动您要覆盖的方法匿名子类:

    private static StringBuffer formatWithSpecialLogic(double number, StringBuffer result, java.text.FieldPosition fieldPosition) {
    

    如果您想使用该特殊逻辑,那么必须显式调用该方法。

答案 1 :(得分:1)

我没有评论的声誉,但就第2点而言,我看到的最大缺点是你没有覆盖DecimalFormat类的所有行为,因此你的结果实例将表现不一致。例如:

final StringBuffer buffer = new StringBuffer();
FORMAT.format(0.111d, buffer, new FieldPosition(0));
System.out.println(buffer.toString());

final StringBuffer buffer2 = new StringBuffer();
FORMAT.format(new BigDecimal(0.111d), buffer2, new FieldPosition(0));
System.out.println(buffer2.toString());

产量

0.11
0.111

如果你走这条路线,那么覆盖所有必要的方法会更好,这样你就能获得一致的行为。或者,您可以在ThreadLocal中存储一个封装所需逻辑的函数:

private final ThreadLocal<Function<Double, String>> DECIMAL_FORMATTER = new ThreadLocal<Function<Double, String>>() {
  @Override
  protected Function<Double, String> initialValue() {
    final DecimalFormat decimalFormat = new DecimalFormat("0.##");
    final DecimalFormat decimalFormatDigit = new DecimalFormat("0.0#");
    return (number) -> {
      if ((number >= 10 && Math.ceil(number) == number)) {
        return decimalFormat.format(number);
      } else {
        return decimalFormatDigit.format(number);
      }
    };
  }
};

System.out.println(DECIMAL_FORMATTER.get().apply(0.111d));

答案 2 :(得分:1)

既然你说你需要有一个NumberFormat的实例,我建议你扩展那个类并实现所需的方法,而不是扩展那些不用于继承的DecimalFormat

private final NumberFormat FORMAT = new NumberFormat() {
    private final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.##");
    private final NumberFormat DECIMAL_FORMAT_DIGIT = new DecimalFormat("0.0#");

    @Override
    public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
        if ((number >= 10 && Math.ceil(number) == number)) {    // or number % 1 == 0
            return DECIMAL_FORMAT.format(number, result, fieldPosition);
        } else {
            return DECIMAL_FORMAT_DIGIT.format(number, result, fieldPosition);
        }
    }

    @Override
    public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
        return format((double)number, result, fieldPosition);
    }

    @Override
    public Number parse(String source, ParsePosition parsePosition) {
        return DECIMAL_FORMAT.parse(source, parsePosition);
    }
};

这减少了脆弱的基类问题,因为我们正在使用一个意味着要继承的基类。但是仍然存在所有死配置方法的问题。理想情况下,您可以覆盖它们以传播其更改或抛出一些异常。