Casting sicuro da lungo a int in Java


489

Qual è il modo più idiomatico in Java per verificare che un cast da longa intnon perda alcuna informazione?

Questa è la mia attuale implementazione:

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

34
Due percorsi di codice. Uno è legato e ha bisogno di ints. Quei dati legacy DOVREBBERO rientrare tutti in un int, ma voglio lanciare un'eccezione se tale presupposto viene violato. L'altro percorso del codice utilizzerà long e non avrà bisogno del cast.
Brigham,

197
Adoro il modo in cui le persone si chiedono sempre perché vuoi fare quello che vuoi fare. Se tutti spiegassero il loro caso d'uso completo in queste domande, nessuno sarebbe in grado di leggerli, e tanto meno rispondere.
BT,

24
BT - Odio fare domande online per questo motivo. Se vuoi aiutare, è fantastico, ma non rispondere a 20 domande e costringerle a giustificarsi.
Mason240,

59
Non sono d'accordo con BT e Mason240 qui: spesso è utile mostrare all'interrogatore un'altra soluzione a cui non hanno pensato. Contrassegnare gli odori di codice è un servizio utile. È molto lontano da "Sono curioso di sapere perché ..." per "costringerli a giustificarsi".
Tommy Herbert,

13
Ci sono molte cose che non puoi fare con i long, ad esempio indicizzare un array.
salta il

Risposte:


580

Un nuovo metodo è stato aggiunto con Java 8 per fare proprio questo.

import static java.lang.Math.toIntExact;

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

Lancerà un ArithmeticExceptioncaso di overflow.

Vedere: Math.toIntExact(long)

Diversi altri metodi sicuri di overflow sono stati aggiunti a Java 8. Finiscono con esatto .

Esempi:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)

5
Abbiamo anche addExacte multiplyExact. Da notare che division ( MIN_VALUE/-1) e il valore assoluto ( abs(MIN_VALUE)) non hanno metodi di praticità sicuri.
Aleksandr Dubinsky,

Ma qual è la differenza nell'usare Math.toIntExact()invece del solito cast int? L'implementazione di Math.toIntExact()solo cast longa int.
Yamashiro Rion,

@YamashiroRion In realtà l'implementazione di toIntExact controlla innanzitutto se il cast porterebbe a un overflow, nel qual caso genera una ArithmeticException. Solo se il cast è sicuro, esegue il cast da lungo a int che restituisce. In altre parole, se provi a lanciare un numero lungo che non può essere rappresentato come int (ad esempio un numero strettamente superiore a 2 147 483 647), verrà generata un'eccezione aritmetica. Se fai lo stesso con un cast semplice, il valore int risultante sarà errato.
Pierre-Antoine,

306

Penso che lo farei semplicemente come:

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

Penso che esprima l'intento più chiaramente del casting ripetuto ... ma è in qualche modo soggettivo.

Nota di potenziale interesse - in C # sarebbe solo:

return checked ((int) l);

7
Farei sempre il controllo della gamma come (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Trovo difficile orientarmi in altri modi per farlo. Peccato che Java non abbia unless.
Tom Hawtin - tackline

5
+1. Ciò rientra esattamente nella regola "le eccezioni dovrebbero essere utilizzate per condizioni eccezionali ".
Adam Rosenfield,

4
(In un moderno linguaggio generale sarebbe: "Eh? Ma gli ints hanno dimensioni arbitrarie?")
Tom Hawtin - tackline

7
@Tom: preferenza personale, immagino - preferisco avere meno negativi possibili. Se sto guardando un "se" con un corpo che genera un'eccezione, mi piacerebbe vedere le condizioni che lo rendono eccezionale - come se il valore fosse "fuori dall'estremità inferiore" di int.
Jon Skeet,

6
@Tom: In tal caso rimuoverei il negativo, metterei il cast / return all'interno del corpo "if", e poi lancerei un'eccezione, se vedi cosa intendo.
Jon Skeet,

132

Con la classe Ints di Google Guava , il tuo metodo può essere modificato in:

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

Dai documenti collegati:

checkedCast

public static int checkedCast(long value)

Restituisce il valore int uguale a value, se possibile.

Parametri: value - qualsiasi valore nell'intervallo del inttipo

Restituisce: il intvalore che è ugualevalue

Genera: IllegalArgumentException - se valueè maggiore Integer.MAX_VALUEo minore diInteger.MIN_VALUE

Per inciso, non è necessario il safeLongToIntwrapper, a meno che non si desideri lasciarlo in posizione per cambiare la funzionalità senza ovviamente un ampio refactoring.


3
Guava Ints.checkedCastfa esattamente quello che fa OP, per inciso
parzialmente nuvoloso

14
+1 per la soluzione Guava, anche se non è necessario avvolgerlo in un altro metodo, basta chiamare Ints.checkedCast(l)direttamente.
dimo414,

8
Guava ha anche Ints.saturatedCastche restituirà il valore più vicino invece di lanciare un'eccezione.
Jake Walsh,

Sì, è sicuro usare l'API esistente come nel mio caso, la libreria già nel progetto: generare un'eccezione se non valida: Ints.checkedCast (long) e Ints.saturatedCast (long) per ottenere il più vicino per la conversione long in int.
Osify,

29

Con BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds

Mi piace questo, qualcuno ha qualcosa contro questa soluzione?
Rui Marques,

12
Bene, sta allocando e buttando via un BigDecimal solo per arrivare a quello che dovrebbe essere un metodo di utilità, quindi sì, non è il processo migliore.
Riking

@Ricordando al riguardo, è meglio usare BigDecimal.valueOf(aLong), invece di new BigDecimal(aLong), per indicare che non è richiesta una nuova istanza. Se l'ambiente di esecuzione memorizza nella cache quel metodo, è specifico dell'implementazione, proprio come la possibile presenza di Escape Analysis. Nella maggior parte dei casi, ciò non ha alcun impatto sulle prestazioni.
Holger,

17

ecco una soluzione, nel caso in cui non ti interessi del valore nel caso sia più grande del necessario;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}

sembra che ti sbagli ... funzionerà bene, quindi negativo. inoltre, cosa significa too low? per favore, fornire un caso d'uso.
Vitaliy Kulikov,

questa soluzione è veloce e sicura, quindi stiamo parlando di lanciare Long to Int per obbedire al risultato.
Vitaliy Kulikov,

11

NON: Questa non è una soluzione!

Il mio primo approccio è stato:

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

Ma questo semplicemente lancia il long a un int, potenzialmente creando nuove Longistanze o recuperandole dal pool Long.


Gli svantaggi

  1. Long.valueOfcrea una nuova Longistanza se il numero non rientra Longnell'intervallo del pool [-128, 127].

  2. L' intValueimplementazione non fa altro che:

    return (int)value;

Quindi questo può essere considerato anche peggio del semplice lancio longdi int.


4
Il tuo tentativo di aiutare è apprezzato, ma dare un esempio di qualcosa che non funziona non è la stessa cosa che fornire una soluzione che funzioni. Se modificassi per aggiungere un modo corretto, questo potrebbe essere abbastanza buono; in caso contrario, non è davvero adatto per essere pubblicato come risposta.
apre il

4
Okay, perché non avere sia DO che DONT? Tbh, a volte vorrei avere un elenco di come non fare le cose (DONT) per verificare se ho usato un tale modello / codice. Ad ogni modo, posso eliminare questa "risposta".
Andreas,

1
Buon anti-pattern. Ad ogni modo sarebbe stato fantastico se mi avessi spiegato cosa succede se il valore lungo è fuori range per int? Immagino che ci sarà una ClassCastException o qualcosa del genere?
Peter Wippermann,

2
@PeterWippermann: ho aggiunto qualche informazione in più. Li consideri comprensibili resp. abbastanza esplicativo?
Andreas,

7

Sostengo che il modo ovvio per vedere se il cast di un valore è cambiato il valore sarebbe quello di lanciare e verificare il risultato. Vorrei, tuttavia, rimuovere il cast non necessario durante il confronto. Inoltre non sono troppo appassionato di nomi di variabili di una lettera (eccezione xe y, ma non quando significano riga e colonna (a volte rispettivamente)).

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

Tuttavia, vorrei davvero evitare questa conversione, se possibile. Ovviamente a volte non è possibile, ma in quei casi IllegalArgumentExceptionè quasi certamente l'eccezione sbagliata da lanciare per quanto riguarda il codice client.


1
Questo è ciò che fanno le versioni recenti di Google Guava Ints :: checkedCast.
lessicalscope

2

I tipi interi Java sono rappresentati come firmati. Con un input compreso tra 2 31 e 2 32 (o -2 31 e -2 32 ) il cast avrebbe esito positivo ma il test non avrebbe avuto esito positivo.

Cosa controllare è se tutti i bit alti longsono tutti uguali:

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("...");
    }
}

3
Non vedo cosa abbia a che fare la firma. Potresti fare un esempio che non perde informazioni ma non supera il test? 2 ^ 31 verrebbe trasmesso a Integer.MIN_VALUE (ovvero -2 ^ 31), quindi le informazioni sono state perse.
Jon Skeet,

@Jon Skeet: forse io e l'OP stiamo parlando l'uno accanto all'altro. (int) 0xFFFFFFFFe (long) 0xFFFFFFFFLhanno valori diversi, ma contengono entrambi le stesse "informazioni" ed è quasi banale estrarre il valore originale lungo dall'int.
mob

Come si può estrarre il valore long originale dall'int, quando il long avrebbe potuto essere -1 per cominciare, invece di 0xFFFFFFFF?
Jon Skeet,

Scusate se non sono chiaro. Sto dicendo che se long e int contengono entrambi gli stessi 32 bit di informazioni e se il 32 bit è impostato, allora il valore int è diverso dal valore long, ma che è facile ottenere il valore lungo
mob

@mob a cosa si riferisce questo? Il codice OP riporta correttamente che valori lunghi> 2 ^ {31} non possono essere convertiti in ints
Parzialmente nuvoloso

0
(int) (longType + 0)

ma Long non può superare il massimo :)


1
Non + 0aggiunge nulla a questa conversione, potrebbe funzionare se Java trattasse la concatenazione di tipi numerici in modo simile alle stringhe, ma poiché non si esegue un'operazione di aggiunta senza motivo.
sixones,

-7

Un'altra soluzione può essere:

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

Ho provato questo per i casi in cui il client sta eseguendo un POST e il DB del server comprende solo numeri interi mentre il client ha un valore Long.


Otterrai una NumberFormatException su valori "molto lunghi": il Integer.valueOf(Long.MAX_VALUE.toString()); risultato è java.lang.NumberFormatException: For input string: "9223372036854775807" che offusca praticamente l'eccezione fuori intervallo perché ora viene trattata nello stesso modo in cui viene trattata una stringa contenente lettere.
Andreas,

2
Inoltre non viene compilato perché non si restituisce sempre un valore.
Patrick M
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.