Transmitir de forma segura long to int en Java

489

¿Cuál es la forma más idiomática en Java para verificar que un envío desde longa intno pierda ninguna información?

Esta es mi implementación actual:

public static int safeLongToInt(long l) {
    int i = (int)l;
    if ((long)i != l) {
        throw new IllegalArgumentException(l + " cannot be cast to int without changing its value.");
    }
    return i;
}
Brigham
fuente
34
Dos rutas de código. Uno es legado y necesita ints. Esos datos heredados DEBEN encajar en un int, pero quiero lanzar una excepción si se viola esa suposición. La otra ruta de código usará longs y no necesitará la conversión.
Brigham
197
Me encanta cómo la gente siempre se pregunta por qué quieres hacer lo que quieres hacer. Si todos explicaran su caso de uso completo en estas preguntas, nadie podría leerlos, y mucho menos responderlos.
BT
24
BT: Realmente odio hacer preguntas en línea por este motivo. Si quieres ayudar, eso es genial, pero no juegues 20 preguntas y las obligues a justificarse.
Mason240
59
No estoy de acuerdo con BT y Mason240 aquí: a menudo es valioso mostrarle al interlocutor otra solución en la que no han pensado. Marcar olores de código es un servicio útil. Es un largo camino desde 'Tengo curiosidad de por qué ...' hasta 'obligarlos a justificarse'.
Tommy Herbert
13
Hay muchas cosas que no puedes hacer con largos, por ejemplo, indexar una matriz.
skot

Respuestas:

580

Se ha agregado un nuevo método con Java 8 para hacer precisamente eso.

import static java.lang.Math.toIntExact;

long foo = 10L;
int bar = toIntExact(foo);

Lanzará un ArithmeticExceptionen caso de desbordamiento.

Ver: Math.toIntExact(long)

Se han agregado varios otros métodos seguros de desbordamiento a Java 8. Terminan con exacto .

Ejemplos:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)
Pierre-Antoine
fuente
55
También tenemos addExacty multiplyExact. Es de destacar que la división ( MIN_VALUE/-1) y el valor absoluto ( abs(MIN_VALUE)) no tienen métodos de conveniencia seguros.
Aleksandr Dubinsky
Pero, ¿cuál es la diferencia de usar en Math.toIntExact()lugar del yeso habitual int? La implementación de Math.toIntExact()solo echa longa int.
Yamashiro Rion
@YamashiroRion En realidad, la implementación de toIntExact primero verifica si el lanzamiento conduciría a un desbordamiento, en cuyo caso arroja una ArithmeticException. Solo si el lanzamiento es seguro, entonces realiza el lanzamiento de largo a int, lo que devuelve. En otras palabras, si intenta emitir un número largo que no puede representarse como int (por ejemplo, cualquier número estrictamente superior a 2 147 483 647) arrojará una ArithmeticException. Si hace lo mismo con un elenco simple, su valor int resultante será incorrecto.
Pierre-Antoine
306

Creo que lo haría tan simple como:

public static int safeLongToInt(long l) {
    if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
        throw new IllegalArgumentException
            (l + " cannot be cast to int without changing its value.");
    }
    return (int) l;
}

Creo que eso expresa la intención más claramente que el casting repetido ... pero es algo subjetivo.

Nota de interés potencial: en C # solo sería:

return checked ((int) l);
Jon Skeet
fuente
77
Siempre haría la verificación de rango como (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Me resulta difícil entender otras formas de hacerlo. Lástima que Java no tiene unless.
Tom Hawtin - tackline
55
+1. Esto cae exactamente bajo la regla de "las excepciones deben usarse para condiciones excepcionales ".
Adam Rosenfield
44
(En un lenguaje moderno de uso general sería: "¿Eh? ¿Pero los de tamaño arbitrario?")
Tom Hawtin - línea de ataque el
77
@Tom: Preferencia personal, supongo, prefiero tener la menor cantidad de negativos posible Si estoy mirando un "si" con un cuerpo que arroja una excepción, me gustaría ver condiciones que lo hagan ver excepcional, como el valor "fuera del extremo inferior" de int.
Jon Skeet el
66
@Tom: En ese caso, eliminaría el negativo, colocaría el yeso / retorno dentro del cuerpo "if" y luego lanzaría una excepción, si entiendes lo que quiero decir.
Jon Skeet
132

Con la clase Ints de Google Guava , su método se puede cambiar a:

public static int safeLongToInt(long l) {
    return Ints.checkedCast(l);
}

De los documentos vinculados:

chequeado

public static int checkedCast(long value)

Devuelve el valor int que es igual a value, si es posible.

Parámetros: value - cualquier valor en el rango del inttipo

Devuelve: el intvalor que equivale avalue

Lanza: IllegalArgumentException si valuees mayor Integer.MAX_VALUEo menor queInteger.MIN_VALUE

Por cierto, no necesita el safeLongToIntcontenedor, a menos que desee dejarlo en su lugar para cambiar la funcionalidad sin una refactorización extensa, por supuesto.

prasopes
fuente
3
La guayaba Ints.checkedCasthace exactamente lo que hace OP, por cierto
Parcialmente nublado
14
+1 para la solución de guayaba, aunque no hay necesidad real de incluirlo en otro método, solo llame Ints.checkedCast(l)directamente.
dimo414
8
La guayaba también tiene Ints.saturatedCastcuál devolverá el valor más cercano en lugar de lanzar una excepción.
Jake Walsh
Sí, es seguro usar la API existente como mi caso, la biblioteca que ya está en el proyecto: para lanzar una excepción si no es válida: Ints.checkedCast (long) e Ints.saturatedCast (long) para obtener la más cercana para convertir long a int.
Osify
29

Con BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds
Jaime Saiz
fuente
Me gusta este, ¿alguien tiene algo en contra de esta solución?
Rui Marques
12
Bueno, está asignando y tirando un BigDecimal solo para llegar a lo que debería ser un método de utilidad, así que sí, ese no es el mejor proceso.
Riking
@Riking en ese sentido, es mejor usar BigDecimal.valueOf(aLong), en lugar de new BigDecimal(aLong), denotar que no se requiere una nueva instancia. Si el entorno de ejecución almacena en caché en ese método, es específico de la implementación, al igual que la posible presencia de Escape Analysis. En la mayoría de los casos de la vida real, esto no tiene impacto en el rendimiento.
Holger
17

Aquí hay una solución, en caso de que no le importe el valor en caso de que sea más grande de lo necesario;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}
Vitaliy Kulikov
fuente
parece que te equivocas ... funcionará bien y luego será negativo. Además, ¿qué significa too low? por favor, proporcione el caso de uso.
Vitaliy Kulikov
esta solución es la rápida y segura, entonces estamos hablando de enviar Long a Int para obedecer el resultado.
Vitaliy Kulikov
11

NO: ¡Esto no es una solución!

Mi primer acercamiento fue:

public int longToInt(long theLongOne) {
  return Long.valueOf(theLongOne).intValue();
}

Pero eso simplemente convierte el largo en un int, potencialmente creando nuevas Longinstancias o recuperándolas del grupo Long.


Los inconvenientes

  1. Long.valueOfcrea una nueva Longinstancia si el número no está dentro Longdel rango del grupo [-128, 127].

  2. La intValueimplementación no hace más que:

    return (int)value;

Por lo tanto, esto puede considerarse incluso peor que simplemente lanzarlo longa int.

Andreas
fuente
44
Apreciamos su intento de ayudar, pero dar un ejemplo de algo que no funciona no es lo mismo que proporcionar una solución que sí funcione. Si edita para agregar una forma correcta, esto podría ser bastante bueno; de lo contrario, no es realmente adecuado para ser publicado como respuesta.
Aparece el
44
Bien, ¿por qué no tener tanto DOs como DONTs? Tbh, a veces desearía tener una lista de cómo no hacer las cosas (NO) para verificar si usé ese patrón / código. De todos modos, puedo eliminar esta "respuesta".
Andreas
1
Buen antipatrón. De todos modos, habría sido genial si explicaras lo que sucede si el valor largo está fuera del rango para int. ¿Supongo que habrá una ClassCastException o algo así?
Peter Wippermann
2
@ PeterWippermann: He agregado más información. ¿Los considera comprensibles resp. lo suficientemente explicativo?
Andreas
7

Afirmo que la forma obvia de ver si la conversión de un valor cambió el valor sería lanzar y verificar el resultado. Sin embargo, eliminaría el yeso innecesario al comparar. Tampoco me interesan los nombres de variables de una letra (excepción xy y, pero no cuando significan fila y columna (a veces, respectivamente)).

public static int intValue(long value) {
    int valueInt = (int)value;
    if (valueInt != value) {
        throw new IllegalArgumentException(
            "The long value "+value+" is not within range of the int type"
        );
    }
    return valueInt;
}

Sin embargo, realmente me gustaría evitar esta conversión si es posible. Obviamente, a veces no es posible, pero en esos casos IllegalArgumentExceptiones casi seguro que la excepción equivocada es lanzar en lo que respecta al código del cliente.

Tom Hawtin - tackline
fuente
1
Esto es lo que hacen las versiones recientes de Google Guava Ints :: checkCast.
lexicalscope
2

Los tipos enteros de Java se representan con signo. Con una entrada entre 2 31 y 2 32 (o -2 31 y -2 32 ), el lanzamiento tendrá éxito pero su prueba fallará.

Lo que debe verificar es si todos los bits altos de la longson todos iguales:

public static final long LONG_HIGH_BITS = 0xFFFFFFFF80000000L;
public static int safeLongToInt(long l) {
    if ((l & LONG_HIGH_BITS) == 0 || (l & LONG_HIGH_BITS) == LONG_HIGH_BITS) {
        return (int) l;
    } else {
        throw new IllegalArgumentException("...");
    }
}
multitud
fuente
3
No veo lo que la firma tiene que ver con eso. ¿Podría dar un ejemplo que no pierda información pero no pase la prueba? 2 ^ 31 se convertiría en Integer.MIN_VALUE (es decir, -2 ^ 31), por lo que se perdió información.
Jon Skeet
@ Jon Skeet: Tal vez yo y el OP estamos hablando entre nosotros. (int) 0xFFFFFFFFy (long) 0xFFFFFFFFLtienen valores diferentes, pero ambos contienen la misma "información", y es casi trivial extraer el valor largo original del int.
mob
¿Cómo puede extraer el valor largo original de int, cuando el largo podría haber sido -1 para comenzar, en lugar de 0xFFFFFFFF?
Jon Skeet
Lo siento si no estoy claro. Estoy diciendo que si tanto el largo como el int contienen los mismos 32 bits de información, y si se establece el bit 32, entonces el valor int es diferente del valor largo, pero eso es fácil de obtener el valor de largo.
mob
@mob ¿a qué se refiere esto? El código de OP informa correctamente que los valores largos> 2 ^ {31} no se pueden convertir en entradas
Parcialmente nublado
0
(int) (longType + 0)

pero Long no puede superar el máximo :)

Maury
fuente
1
No + 0agrega nada a esta conversión, podría funcionar si Java tratara la concatenación de tipo numérico de una manera similar a las cadenas, pero dado que no hace una operación de agregar sin ninguna razón.
sixones
-7

Otra solución puede ser:

public int longToInt(Long longVariable)
{
    try { 
            return Integer.valueOf(longVariable.toString()); 
        } catch(IllegalArgumentException e) { 
               Log.e(e.printstackstrace()); 
        }
}

He intentado esto para los casos en que el cliente está haciendo una POST y el servidor DB solo entiende números enteros mientras el cliente tiene un Long.

Rajat Anantharam
fuente
Obtendrá una NumberFormatException en valores "muy largos": esto Integer.valueOf(Long.MAX_VALUE.toString()); da como resultado java.lang.NumberFormatException: For input string: "9223372036854775807" que ofusca la Excepción fuera de rango porque ahora se trata de la misma manera que se trata una cadena que contiene letras.
Andreas
2
Tampoco se compila porque no siempre devuelve un valor.
Patrick M