使用 Moneta (JavaMoney) JSR 354 实现自定义 MonetaryAmount 格式

Customizing a MonetaryAmountFormat using the Moneta (JavaMoney) JSR354 implemenation

我真的很困惑如何使用 Moneta JSR-354 实现来自定义 MonetaryAmountFormat

我的意图是能够将 1.23.45 都解析为 MonetaryAmounts。

这是我的单元测试:

@Test
public void testString() {
    Bid bid = new Bid("1.23");
    assertEquals(1.23, bid.getValue(), 0.0);
    System.out.println(bid);

    bid = new Bid(".45");
    assertEquals(3.45, bid.getValue(), 0.0);
    System.out.println(bid);
}

这是我的 class:

public final class Bid {

    private static final CurrencyUnit USD = Monetary.getCurrency("USD");
    private MonetaryAmount bid;

    /**
     * Constructor.
     *
     * @param bidStr the bid
     */
    public Bid(String bidStr) {
        MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(
                AmountFormatQueryBuilder.of(Locale.US)
                .set("pattern", "###.##")
                .build());
        if (StringUtils.isNotBlank(bidStr)) {
            bidStr = bidStr.trim();
            bidStr = bidStr.startsWith("$") ? bidStr.substring(1) : bidStr;
            try {
                bid = FastMoney.parse(bidStr, format);
            } catch (NumberFormatException e) {
                bid = FastMoney.of(0, USD);
            }
        }
    }

    /**
     * Constructor.
     *
     * @param bidDouble the bid
     */
    public Bid(double bidDouble) {
        bid = FastMoney.of(bidDouble, USD);
    }

    public double getValue() {
        return bid.getNumber().doubleValue();
    }
}

我真的很想能够使用单个 MonetaryAmountFormat 来解析 bidStr 有或没有 $,但是在花了很多时间试图找出答案之后如何让$可选,我放弃了。不幸的是,我什至不知道如何将 1.23 解析为美元。 Moneta 抛出 NullPointerException。我应该使用 AmountFormatQueryBuilder 设置货币吗? AmountFormatQueryBuilder 可以设置哪些键?我搜索了文档,但找不到任何地方,除了几个常用键,比如 pattern.

JavaMoney 在尝试解析不合格的数字(如 1.23)时似乎效果不佳。

默认情况下,MonetaryAmountFormat 希望您提供货币名称(如 USD 1.23):

MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.US);
MonetaryAmount amount = format.parse("USD 1.23");

如果设置CurrencyStyle.SYMBOL,则可以按货币名称或符号解析(因此,USD 1.23.45):

AmountFormatQuery formatQuery = AmountFormatQueryBuilder.of(Locale.US)
    .set(CurrencyStyle.SYMBOL)
    .build();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(formatQuery);
MonetaryAmount amount1 = format.parse("USD 1.23");
MonetaryAmount amount2 = format.parse(".45");

这可能是您使用 JavaMoney 最接近的结果。

如果你知道你只从同一种货币中获取数字,你可能最好在别处解析字符串(使用正则表达式或其他东西)并转换为一致的输入格式,因为(正如你发现的那样),你很容易得到NullPointerException当 JavaMoney 不高兴时没有任何解释。

就可用键而言,您可以查看 org.javamoney.moneta.format.AmountFormatParams 上的常量:patterngroupingSizesgroupingSeparators.

设置格式的时候需要注意的是一定要使用通用的currency sign:¤。例如,你可以做 .set("pattern", "¤#,##0.00").

最后,您可以直接从 MonetaryAmountFormat:

中解析,而不是直接使用 Moneta RI 中的 FastMoney class
// rather than using Monata directly:
MonetaryAmount amount = FastMoney.parse(bidStr, format);

// use the API:
MonetaryAmount amount = format.parse(bidStr);

如果您使用的是自定义格式模式,那么您可以解析仅包含数字而不包含货币的字符串,但您应该自己指定预期的货币。 这是一个例子:

@Test
public void testParse_pattern_without_currency_sign_but_with_currency_in_context() {
    CurrencyUnit usd = Monetary.getCurrency("USD");
    AmountFormatContextBuilder builder = AmountFormatContextBuilder.of(US);
    builder.set(AmountFormatParams.PATTERN, "0.00"); // the pattern doesn't contains a currency sign ¤
    builder.set(CurrencyUnit.class, usd); // set expected currency
    AmountFormatContext context = builder.build();
    DefaultMonetaryAmountFormat format = new DefaultMonetaryAmountFormat(context);
    MonetaryAmount parsedAmount = format.parse("0.01 USD");
    assertSame(parsedAmount.getCurrency(), usd);
    assertEquals(parsedAmount.getNumber().doubleValueExact(), 0.01D);
    assertEquals(parsedAmount.toString(), "USD 0.01");
}

请注意,您需要使用 set(CurrencyUnit.class, usd) 创建上下文。

但是,如果您尝试设置带有货币符号的模式,例如0.00 ¤ 然后你会收到一个错误:javax.money.format.MonetaryParseException: Error parsing CurrencyUnit: no input.

我为此创建了一个工单Lenient Formatter: Parse a string with a number but without a currency code,请阅读并添加您的意见。