Cadena a Int en java: es probable que los datos sean malos, es necesario evitar excepciones

StackOverflow https://stackoverflow.com/questions/174502

  •  05-07-2019
  •  | 
  •  

Pregunta

Al ver que Java no tiene tipos anulables, tampoco tiene un TryParse (), ¿Cómo maneja la validación de entrada sin lanzar excepciones?

La forma habitual:

String userdata = /*value from gui*/
int val;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException nfe)
{
   // bad data - set to sentinel
   val = Integer.MIN_VALUE;
}

Podría usar una expresión regular para verificar si se puede analizar, pero también parece una gran cantidad de gastos generales.

¿Cuál es la mejor práctica para manejar esta situación?

EDITAR: Justificación: Se ha hablado mucho sobre SO sobre el manejo de excepciones, y la actitud general es que las excepciones deben usarse solo en escenarios inesperados. Sin embargo, creo que la entrada de un usuario malo es ESPERADA, no es raro Sí, realmente es un punto académico.

Otras ediciones:

Algunas de las respuestas demuestran exactamente lo que está mal con SO. Ignora la pregunta que se le hace y responde otra pregunta que no tiene nada que ver. La pregunta no es sobre la transición entre capas. La pregunta no es preguntar qué devolver si el número no se puede analizar. Por todo lo que sabes, val = Integer.MIN_VALUE; es exactamente la opción correcta para la aplicación de la que se tomó este fragmento de código completamente libre de contexto.

¿Fue útil?

Solución

Eso es prácticamente todo, aunque devolver MIN_VALUE es algo cuestionable, a menos que esté seguro de que es lo correcto para usar lo que esencialmente está usando como un código de error. Sin embargo, al menos documentaría el comportamiento del código de error.

También podría ser útil (dependiendo de la aplicación) para registrar la entrada incorrecta para que pueda rastrear.

Otros consejos

Le pregunté a si había una fuente abierta las bibliotecas de utilidades que tenían métodos para hacer este análisis por usted y la respuesta es sí!

Desde Apache Commons Lang puede usar NumberUtils.toInt :

// returns defaultValue if the string cannot be parsed.
int i = org.apache.commons.lang.math.NumberUtils.toInt(s, defaultValue);

Desde Google Guava puede usar Ints.tryParse :

// returns null if the string cannot be parsed
// Will throw a NullPointerException if the string is null
Integer i = com.google.common.primitives.Ints.tryParse(s);

No hay necesidad de escribir sus propios métodos para analizar números sin lanzar excepciones.

Para los datos proporcionados por el usuario, Integer.parseInt suele ser el método incorrecto porque no admite la internacionalización. El paquete java.text es su amigo (detallado).

try {
    NumberFormat format = NumberFormat.getIntegerInstance(locale);
    format.setParseIntegerOnly(true);
    format.setMaximumIntegerDigits(9);
    ParsePosition pos = new ParsePosition(0);
    int val = format.parse(str, pos).intValue();
    if (pos.getIndex() != str.length()) {
        // ... handle case of extraneous characters after digits ...
    }
    // ... use val ...
} catch (java.text.ParseFormatException exc) {
    // ... handle this case appropriately ...
}

¿Cuál es el problema con su enfoque? No creo que hacerlo de esa manera afecte en absoluto el rendimiento de su aplicación. Esa es la forma correcta de hacerlo. No optimices prematuramente .

Estoy seguro de que es de mala forma, pero tengo un conjunto de métodos estáticos en una clase de Utilidades que hacen cosas como Utilities.tryParseInt (valor de cadena) que devuelve 0 si la cadena no es analizable y Utilities.tryParseInt (String value, int defaultValue) que le permite especificar un valor para usar si parseInt () lanza una excepción.

Creo que hay ocasiones en que devolver un valor conocido en una entrada incorrecta es perfectamente aceptable. Un ejemplo muy artificial: le pide al usuario una fecha en el formato AAAAMMDD y le dan una entrada incorrecta. Puede ser perfectamente aceptable hacer algo como Utilities.tryParseInt (date, 19000101) o Utilities.tryParseInt (date, 29991231); dependiendo de los requisitos del programa.

Voy a repetir el punto que Stinkyminky estaba haciendo hacia el final del post:

Un enfoque generalmente bien aceptado que valida la entrada del usuario (o la entrada de los archivos de configuración, etc.) consiste en utilizar la validación antes de procesar realmente los datos. En la mayoría de los casos, este es un buen movimiento de diseño, aunque puede dar lugar a múltiples llamadas a algoritmos de análisis.

Una vez que sepa que ha validado correctamente la entrada del usuario, entonces es seguro analizarla e ignorarla, registrarla o convertirla en RuntimeException la NumberFormatException.

Tenga en cuenta que este enfoque requiere que considere su modelo en dos partes: el modelo de negocio (donde realmente nos importa tener valores en formato int o flotante) y el modelo de interfaz de usuario (donde realmente queremos permitir que el usuario ponga en lo que quieran).

Para que los datos migren del modelo de interfaz de usuario al modelo de negocio, deben pasar por un paso de validación (esto puede ocurrir campo por campo, pero la mayoría de los escenarios requieren la validación de todo el objeto que es siendo configurado).

Si la validación falla, se le presenta al usuario una retroalimentación que le informa de lo que ha hecho mal y se le da la oportunidad de solucionarlo.

Las bibliotecas de enlaces como JGoodies Binding y JSR 295 hacen que este tipo de cosas sea mucho más fácil de implementar de lo que parece, y muchos marcos web proporcionan construcciones que separan las aportaciones del usuario del modelo de negocio real, solo se completan los objetos de negocio una vez que se completa la validación .

En términos de validación de archivos de configuración (el otro caso de uso presentado en algunos de los comentarios), una cosa es especificar un valor predeterminado si no se especifica un valor en particular, pero si los datos tienen un formato incorrecto (alguien escribe un 'oh' en lugar de un 'cero', o se copió de MS Word y todos los tic-tac obtuvieron un carácter funky de Unicode), entonces se necesita algún tipo de retroalimentación del sistema (incluso si solo falla la aplicación al lanzar un excepción de tiempo de ejecución).

Así es como lo hago:

public Integer parseInt(String data) {
  Integer val = null;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Entonces el nulo señala datos inválidos. Si desea un valor predeterminado, puede cambiarlo a:

public Integer parseInt(String data,int default) {
  Integer val = default;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Creo que la mejor práctica es el código que muestra.

No optaría por la alternativa de expresión regular debido a la sobrecarga.

Pruebe org.apache.commons.lang.math.NumberUtils.createInteger (String s) . Eso me ayudó mucho. Hay métodos similares para dobles, largos, etc.

Podría usar un entero, que se puede establecer en nulo si tiene un valor incorrecto. Si está utilizando Java 1.6, proporcionará boxeo automático / unboxing para usted.

Semántica más limpia (Java 8 OptionalInt)

Para Java 8+, probablemente usaría RegEx para pre-filtrar (para evitar la excepción como lo anotó) y luego ajustaría el resultado en una primitiva opcional (para tratar el " problema " defecto "):

public static OptionalInt toInt(final String input) {
    return input.matches("[+-]?\\d+") 
            ? OptionalInt.of(Integer.parseInt(input)) 
            : OptionalInt.empty();
}

Si tiene muchas entradas de String, puede considerar devolver un IntStream en lugar de OptionalIncode para que pueda flatMap () .

Referencias

El código anterior es malo porque es equivalente al siguiente.

// this is bad
int val = Integer.MIN_VALUE;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException ignoreException) { }

La excepción se ignora por completo. Además, el token mágico es malo porque un usuario puede pasar -2147483648 (Integer.MIN_VALUE).

La pregunta genérica analizable no es beneficiosa. Más bien, debe ser relevante para el contexto. Su aplicación tiene un requisito específico. Puedes definir tu método como

private boolean isUserValueAcceptable(String userData)
{
   return (    isNumber(userData)    
          &&   isInteger(userData)   
          &&   isBetween(userData, Integer.MIN_VALUE, Integer.MAX_VALUE ) 
          );
}

Donde puede documentar el requisito y puede crear reglas verificables y bien definidas.

Si puedes evitar las excepciones probando de antemano como dijiste (isParsable ()) podría ser mejor, pero no todas las bibliotecas se diseñaron con eso en mente.

Utilicé tu truco y apesta porque los rastros de pila en mi sistema integrado se imprimen independientemente de si los detectas o no :(

El mecanismo de excepción es valioso, ya que es la única forma de obtener un indicador de estado en combinación con un valor de respuesta. Además, el indicador de estado está estandarizado. Si hay un error se obtiene una excepción. De esa manera usted no tiene que pensar en un indicador de error. La controversia no es tanto con excepciones, sino con excepciones marcadas (por ejemplo, las que debe capturar o declarar).

Personalmente creo que escogiste uno de los ejemplos donde las excepciones son realmente valiosas. Es un problema común que el usuario ingrese un valor incorrecto, y normalmente necesitará volver al usuario para obtener el valor correcto. Normalmente no vuelve al valor predeterminado si le pregunta al usuario; Eso le da al usuario la impresión de que su entrada importa.

Si no desea lidiar con la excepción, simplemente envuélvala en una RuntimeException (o clase derivada) y le permitirá ignorar la excepción en su código (y eliminar su aplicación cuando ocurra; a veces está bien. ).

Algunos ejemplos sobre cómo manejaría las excepciones de NumberFormat: En los datos de configuración de la aplicación web:

loadCertainProperty(String propVal) {
  try
  {
    val = Integer.parseInt(userdata);
    return val;
  }
  catch (NumberFormatException nfe)
  { // RuntimeException need not be declared
    throw new RuntimeException("Property certainProperty in your configuration is expected to be " +
                               " an integer, but was '" + propVal + "'. Please correct your " +
                               "configuration and start again");
    // After starting an enterprise application the sysadmin should always check availability
    // and can now correct the property value
  }
}

En una GUI:

public int askValue() {
  // TODO add opt-out button; see Swing docs for standard dialog handling
  boolean valueOk = false;
  while(!valueOk) {
    try {
      String val = dialog("Please enter integer value for FOO");
      val = Integer.parseInt(userdata);
      return val; 
    } catch (NumberFormatException nfe) {
      // Ignoring this; I don't care how many typo's the customer makes
    }
  }
}

En un formulario web: devuelva el formulario al usuario con un mensaje de error útil y la posibilidad de correcto. La mayoría de los marcos ofrecen una forma estandarizada de validación.

Integer.MIN_VALUE como NumberFormatException es una mala idea.

Puede agregar una propuesta a Project Coin para agregar este método a Integer

@Nullable public static Integer parseInteger (String src) ... devolverá nulo por mala entrada

Luego ponga el enlace a su propuesta aquí y todos votaremos por ella.

PS: mira esto http://msdn.microsoft.com/en-us/library/bb397679.aspx Esto es lo feo e hinchado que podría ser

Pon algunas frases if en frente de este if (null! = userdata)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top