2016-05-30 2 views
2

Не удалось найти подходящее решение в сети, поэтому я подумал, что правильный способ использования формата java.Нитевидный динамический шаблон для NumberFormat/DecimalFormat

1) В документации NumberFormat.java он говорит, что

Количество форматов, как правило, не синхронизированы. Рекомендуется создавать отдельные экземпляры формата для каждого потока.

Мы использовали объекты формата (статически инициализированные) в многопоточной среде без каких-либо проблем. Возможно, потому, что, как только форматы определены, мы не изменим их состояние (т. Е. После этого не создаются сеттеры)

2) Теперь мне нужно определить новый формат, который должен выводить одну или две значащие цифры после запятой , в зависимости от некоторой дополнительной логики. То, как я это делал, это определить новую оболочку формата и делегировать два различных DecimalFormat в зависимости от случая в перезаписанном методе #format (double, StringBuffer, FieldPosition). Вот код для этого:

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(), поскольку я думаю, что это скомпрометирует волатильный параллелизм.

Благодаря

+1

Неправильно использовать/совместно использовать экземпляры NumberFormat для нескольких потоков. Даже если вы не столкнулись с какой-либо проблемой, uptil сейчас, они произойдут, как только ваше приложение достигнет истинной параллельной обработки. У нас был аналогичный вариант использования DateFormat, в котором использовались статически созданные форматы дат, но мы масштабировали наше приложение для огромной обработки данных, когда n-параллельные процессы выкапывают данные, мы обнаружили, что формат может дать вам очень странный и неожиданный результат. Они могут не терпеть неудачу, но дадут некоторое неожиданное значение –

+0

@SangramJadhav, так что вы, вероятно, сохраните многоразовый пул потоков в форматах, верно? – d56

+1

Если вы хотите использовать общий формат, вам необходимо предоставить собственную синхронизацию. Или вы можете использовать ThreadLocal для объявления экземпляра формата, таким образом, каждый поток будет иметь свою собственную копию форматирования, и вам не нужно предоставлять синхронизацию. –

ответ

3
  1. Мы используем объекты формата (статически инициализируются) в многопоточной среде без каких-либо проблем до сих пор. не это, может быть, потому, что когда-то форматы определены, мы их состояние не изменяется (т.е. не сеттеры называются впоследствии)

невозможно точно сказать, почему вы еще не видели каких-либо проблем, так как мы не знаю точно, как вы их используете.С верхней части моей головы, несколько причин может быть:

  • Вы не попав любой из путей кода в DecimalFormat, которые используют изменяемые переменные экземпляра;
  • Вы «случайно» применяете взаимное исключение, поэтому вы никогда не используете экземпляр более чем в одном потоке за раз;
  • Вы используете реализацию, которая на самом деле правильно синхронизируется (обратите внимание, что Javadoc говорит «вообще не синхронизирован», а не «никогда не синхронизирован»);
  • Вы на самом деле : есть проблемы, но вы просто не контролируете их адекватно;
  • т.д.

Дело о проблемах синхронизации, так как я видел, кто-то другой комментарий вчера, что вы не гарантированы, чтобы увидеть проблему, если не синхронизировать; просто вам не гарантировано не видеть их.

Дело в том, что если вы не применяете синхронизацию, вы находитесь в прихоти и милосердии любого количества тонких изменений, о которых вы можете просто не знать. Сегодня это работает, завтра это не так; почему у вас будет одна всемогущая работа.

  1. Это лучшая практика?

Есть несколько проблем, которые я могу думать здесь:

  1. Расширяя класса, вы рискуете падения фол в fragile base class problem.

    В двух словах, если вы на самом деле вызов метода public StringBuffer format(double, StringBuffer, java.text.FieldPosition) на вашем DecimalFormat случае явно, вы не можете достоверно знать, является ли ваш переопределяется метод фактически один называется: изменение к реализации базового класса (DecimalFormat) может изменить логику, на которую вы полагаетесь, чтобы вызвать этот метод.

  2. У вас есть три изменяемых экземпляра - FORMAT, DECIMAL_FORMAT и DECIMAL_FORMAT_DIGIT - которые имеют все способы настройки, чтобы изменить свое поведение.

    Вы, , должны распространять все эти сеттеры во всех экземплярах, чтобы они ведут себя последовательно, например. если вы позвоните setPositivePrefix по телефону FORMAT, вы также можете позвонить по этому методу на DECIMAL_FORMAT и DECIMAL_FORMAT_DIGIT.

Если вы на самом деле не нужно передать FORMAT в качестве параметра метода, было бы гораздо более надежным, если вы только что определили простой старый метод, который вызывающий свою логику: в основном, переместите метод переопределения из анонимный подкласс:

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

Тогда вы должны вызывать этот метод явно, если вы хотите использовать эту особую логику.

+0

1. Я думаю, вы ошибаетесь в этом. Метод NumberFormat # format (double), по которому проходит DecimalFormat, отмечен как окончательное, чтобы запретить его переопределять. Это означает, что создатели пожелали, чтобы потребители переопределили другой формат метода (double, StringBuffer, FieldPosition) 2. Я не называю никаких сеттеров в результирующем формате. 3. Я не могу вытащить formatWithSpeicalLogic из класса, потому что формат (double) неявно называется javax.swing. Это не будет потокобезопасным (ну, даже больше, чем сейчас). text.NumberFormatter class – d56

+1

1. «Я думаю, вы ошибаетесь в этом», не так, ju возможно, чрезмерно пессимистично; FBCP является неотъемлемым следствием 'extends'. Вы просто думаете, что этого не произойдет. И, наверное, этого не будет; Я бы не стал поспорить с этим домом. 2. «Я не называю каких-то сеттеров». И можете ли вы гарантировать, что никогда не будете? 3) Это детали, которые вы оставили вне вопроса. –

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)); 
+0

Проблема в том, что мне нужен только один объект формата, который выполняет всю логику. Причина этого в том, что javax.swing.text.NumberFormatter ожидает объект java.text.NumberFormat в своем конструкторе. Поэтому я пошел на наследование вместо композиции. Я не переопределял остальные методы, так как нас интересуют только двойные входы. Тем не менее, я немного обеспокоен неудобным результатом наследования - 2 NumberFormats внутри третьего ( – d56

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); 
    } 
}; 

Это уменьшает хрупкая проблема базового класса, потому что мы используем базовый класс, который должен быть унаследован. Но все еще существует проблема всех методов мертвой конфигурации. В идеале вы бы переопределили их, чтобы либо распространять свои изменения, либо бросать какое-то исключение.

Смежные вопросы