Um erro muito comum e bastante perigoso é utilizar o SimpleDateFormat ou NumberFormat como atributo estático de classe. Já vi dezenas de códigos assim em sistemas em produção:
public static final SimpleDateFormat FORMATTER = new SimpleDateFormat( "dd/MM/yyyy");
Qual é o perigo disso? Essas classes não são thread-safe. O que significa isso? Significa que você não pode compartilhar o mesmo objeto (FORMATTER) em partes diferentes de código que podem ser concorrentes. Isso é muito comum em sistemas Web. Se você já fez isso em uma Servlet ou ManagedBean, saiba que o seu código tem grandes chances de não funcionar conforme o esperado. Esse é o famoso: "Funciona na minha máquina!". Para demonstrar esse problema vamos utilizar o seguinte código:
package br.com.thiagovespa.sample.date; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SimpleDateFormatThreadUnsafe implements Runnable { public static final SimpleDateFormat FORMATTER = new SimpleDateFormat( "dd/MM/yyyy"); public static final String[] toParse = { "21/12/2012", "04/09/1982", "01/01/2013", "25/12/2012" }; public static void main(String[] args) { final int threadPoolSize = toParse.length; ExecutorService execService = Executors .newFixedThreadPool(threadPoolSize); for (int i = 0; i < threadPoolSize; i++) { execService.submit(new SimpleDateFormatThreadUnsafe()); } } @Override public void run() { for (;;) { try { String dateToParse = toParse[new Random().nextInt(3)]; Date parsedDate = FORMATTER.parse(dateToParse); String parsedDateToString = FORMATTER.format(parsedDate); if (!dateToParse.equals(parsedDateToString)) { System.out.println("Erro. Esperado: " + dateToParse + ". Obtido: " + parsedDateToString); } } catch (ParseException e) { e.printStackTrace(); } } } }
O problema se encontra na linha 11. Estamos criando 4 threads concorrentes que vão converter (parse) de um valor do tipo String para Date (linha 31) e transformar o mesmo valor (format) de Date para String (linha 32). Na teoria o valor da linha 30 deveria ser o mesmo da linha 32, mas como o objeto SimpleDateFormat não é thread-safe temos a seguinte saída:
Erro. Esperado: 01/01/2013. Obtido: 04/01/2013 Erro. Esperado: 01/01/2013. Obtido: 04/01/2013 Erro. Esperado: 04/09/1982. Obtido: 21/12/2012 Erro. Esperado: 04/09/1982. Obtido: 04/09/2013 Erro. Esperado: 04/09/1982. Obtido: 04/12/2012 Erro. Esperado: 21/12/2012. Obtido: 04/12/2012 Erro. Esperado: 01/01/2013. Obtido: 13/01/2013 Erro. Esperado: 04/09/1982. Obtido: 21/12/0001 Erro. Esperado: 21/12/2012. Obtido: 21/12/0001 Erro. Esperado: 04/09/1982. Obtido: 21/09/1982 Erro. Esperado: 01/01/2013. Obtido: 01/12/2011 Erro. Esperado: 21/12/2012. Obtido: 01/12/2011 Erro. Esperado: 04/09/1982. Obtido: 01/01/2013 Erro. Esperado: 01/01/2013. Obtido: 21/12/2012 Erro. Esperado: 01/01/2013. Obtido: 01/01/1982 Erro. Esperado: 01/01/2013. Obtido: 04/09/1982
O pior é que não é lançada nenhuma exception. O valor fica diferente do esperado. Normalmente, esse problema só vai aparecer quando o sistema estiver em produção com vários usuários acessando (várias threads). A solução é sincronizar ou instanciar um objeto para cada conversão ao invés de tentar reutilizá-lo. Portanto, antes de dizer: "Funciona na minha máquina!", pergunte antes se a classe que você está utilizando é thread-safe.