Sicheres Casting von Long zu Int in Java

489

Was ist die idiomatischste Methode in Java, um zu überprüfen, ob eine Besetzung von longbis intkeine Informationen verliert?

Dies ist meine aktuelle Implementierung:

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
quelle
34
Zwei Codepfade. Eines ist Vermächtnis und braucht Ints. Diese alten Daten sollten alle in ein int passen, aber ich möchte eine Ausnahme auslösen, wenn diese Annahme verletzt wird. Der andere Codepfad verwendet Longs und benötigt keine Besetzung.
Brigham
197
Ich liebe es, wie Leute immer fragen, warum Sie tun wollen, was Sie tun wollen. Wenn jeder seinen vollständigen Anwendungsfall in diesen Fragen erläutern würde, könnte niemand sie lesen, geschweige denn beantworten.
BT
24
BT - Ich hasse es wirklich, aus diesem Grund online Fragen zu stellen. Wenn Sie helfen möchten, ist das großartig, aber spielen Sie nicht 20 Fragen und zwingen Sie sie, sich zu rechtfertigen.
Mason240
59
Nicht einverstanden mit BT und Mason240 hier: Es ist oft wertvoll, dem Fragesteller eine andere Lösung zu zeigen, an die er nicht gedacht hat. Das Markieren von Code-Gerüchen ist ein nützlicher Dienst. Es ist ein langer Weg von "Ich bin neugierig, warum ...", um sie zu zwingen, sich selbst zu rechtfertigen.
Tommy Herbert
13
Es gibt viele Dinge, die Sie mit Longs nicht tun können, z. B. das Indizieren eines Arrays.
Skot

Antworten:

580

Mit Java 8 wurde eine neue Methode hinzugefügt , um genau das zu tun.

import static java.lang.Math.toIntExact;

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

Wirft einen ArithmeticExceptionim Falle eines Überlaufs.

Sehen: Math.toIntExact(long)

Java 8 wurden mehrere andere überlaufsichere Methoden hinzugefügt. Sie enden mit genau .

Beispiele:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)
Pierre-Antoine
quelle
5
Wir haben auch addExactund multiplyExact. Bemerkenswert ist, dass Division ( MIN_VALUE/-1) und Absolutwert ( abs(MIN_VALUE)) keine sicheren Bequemlichkeitsmethoden haben.
Aleksandr Dubinsky
Aber was ist ein Unterschied zwischen der Verwendung Math.toIntExact()anstelle der üblichen Besetzung int? Die Umsetzung von Math.toIntExact()nur Casts longzu int.
Yamashiro Rion
@YamashiroRion Tatsächlich überprüft die Implementierung von toIntExact zunächst, ob die Umwandlung zu einem Überlauf führen würde. In diesem Fall wird eine ArithmeticException ausgelöst. Nur wenn der Cast sicher ist, führt er einen Cast von long nach int durch, den er zurückgibt. Mit anderen Worten, wenn Sie versuchen, eine lange Zahl umzuwandeln, die nicht als int dargestellt werden kann (z. B. eine Zahl, die streng über 2 147 483 647 liegt), wird eine ArithmeticException ausgelöst. Wenn Sie dasselbe mit einer einfachen Umwandlung tun, ist Ihr resultierender int-Wert falsch.
Pierre-Antoine
306

Ich denke, ich würde es so einfach machen wie:

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

Ich denke, das drückt die Absicht klarer aus als das wiederholte Casting ... aber es ist etwas subjektiv.

Hinweis von potenziellem Interesse - in C # wäre es nur:

return checked ((int) l);
Jon Skeet
quelle
7
Ich würde die Reichweitenprüfung immer als durchführen (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Es fällt mir schwer, mich mit anderen Methoden auseinanderzusetzen. Schade, dass Java nicht hat unless.
Tom Hawtin - Tackline
5
+1. Dies fällt genau unter die Regel "Ausnahmen sollten für außergewöhnliche Bedingungen verwendet werden".
Adam Rosenfield
4
(In einer modernen Allzwecksprache wäre es: "Eh? Aber Ints haben eine beliebige Größe?")
Tom Hawtin - Tackline
7
@ Tom: Persönliche Vorlieben, denke ich - ich bevorzuge es, so wenig Negative wie möglich zu haben. Wenn ich ein "Wenn" mit einem Körper betrachte, der eine Ausnahme auslöst, würde ich gerne Bedingungen sehen, die es außergewöhnlich aussehen lassen - wie den Wert "am unteren Ende" von int.
Jon Skeet
6
@ Tom: In diesem Fall würde ich das Negativ entfernen, den Cast / Return in den "if" -Körper einfügen und anschließend eine Ausnahme auslösen, wenn Sie sehen, was ich meine.
Jon Skeet
132

Mit der Ints- Klasse von Google Guava kann Ihre Methode geändert werden in:

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

Aus den verknüpften Dokumenten:

checkedCast

public static int checkedCast(long value)

Gibt den int-Wert zurück, der nach valueMöglichkeit gleich ist.

Parameter: value - Beliebiger Wert im Bereich des intTyps

Rückgabe: Der intWert, der gleich istvalue

Würfe: IllegalArgumentException - wenn valuegrößer Integer.MAX_VALUEoder kleiner alsInteger.MIN_VALUE

Das brauchst du übrigens nicht safeLongToInt Wrapper nicht, es sei denn, Sie möchten ihn an Ort und Stelle lassen, um die Funktionalität ohne umfangreiches Refactoring zu ändern.

Prasope
quelle
3
Guava Ints.checkedCastmacht übrigens genau das, was OP macht
Teilweise bewölkt
14
+1 für die Guava-Lösung, obwohl es nicht wirklich notwendig ist, sie in eine andere Methode einzuschließen, rufen Sie einfach Ints.checkedCast(l)direkt auf.
dimo414
8
Guava hat auch einen Ints.saturatedCastWert, der den nächsten Wert zurückgibt, anstatt eine Ausnahme auszulösen.
Jake Walsh
Ja, es ist sicher, die vorhandene API als meinen Fall zu verwenden, die Bibliothek, die sich bereits im Projekt befindet: Ausnahme auslösen, wenn sie ungültig ist: Ints.checkedCast (long) und Ints.saturatedCast (long), um die nächste für die Konvertierung von long in int zu erhalten.
Osify
29

Mit BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds
Jaime Saiz
quelle
Ich mag dieses, hat jemand etwas gegen diese Lösung?
Rui Marques
12
Nun, es wird ein BigDecimal zugewiesen und weggeworfen, nur um herauszufinden, was eine Dienstprogrammmethode sein sollte. Ja, das ist nicht der beste Prozess.
Riking
@Riking in dieser Hinsicht ist es besser zu verwenden BigDecimal.valueOf(aLong), anstatt anzuzeigen, new BigDecimal(aLong)dass keine neue Instanz erforderlich ist. Ob die Ausführungsumgebung bei dieser Methode zwischengespeichert wird, ist implementierungsspezifisch, genau wie das mögliche Vorhandensein von Escape Analysis. In den meisten Fällen im wirklichen Leben hat dies keinen Einfluss auf die Leistung.
Holger
17

Hier ist eine Lösung, falls Sie sich nicht für den Wert interessieren, falls er größer ist als benötigt;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}
Vitaliy Kulikov
quelle
scheint, du liegst falsch ... es wird gut funktionieren, dann negativ. auch was bedeutet das too low? Bitte geben Sie den Anwendungsfall an.
Vitaliy Kulikov
Diese Lösung ist die schnelle und sichere, dann sprechen wir von Long zu Int, um dem Ergebnis zu gehorchen.
Vitaliy Kulikov
11

NICHT: Dies ist keine Lösung!

Mein erster Ansatz war:

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

Damit wird jedoch nur das Long in ein Int umgewandelt, wodurch möglicherweise neue LongInstanzen erstellt oder aus dem Long-Pool abgerufen werden.


Die Nachteile

  1. Long.valueOfErstellt eine neue LongInstanz, wenn die Nummer nicht im LongPoolbereich liegt [-128, 127].

  2. Die intValueImplementierung macht nichts weiter als:

    return (int)value;

Das kann also noch schlimmer sein, als nur das longzu werfen int.

Andreas
quelle
4
Ihr Versuch zu helfen wird geschätzt, aber ein Beispiel für etwas zu geben, das nicht funktioniert, ist nicht ganz dasselbe wie eine Lösung anzubieten, die funktioniert. Wenn Sie bearbeiten würden, um einen korrekten Weg hinzuzufügen, könnte dies ziemlich gut sein; Andernfalls ist es nicht wirklich geeignet, als Antwort veröffentlicht zu werden.
Pops
4
Okay, warum nicht sowohl DOs als auch DONTs? Tbh, manchmal wünschte ich mir, ich hätte eine Liste, wie man Dinge nicht macht (DONTs), um zu überprüfen, ob ich ein solches Muster / einen solchen Code verwendet habe. Auf jeden Fall kann ich diese "Antwort" löschen.
Andreas
1
Gutes Anti-Muster. Wie auch immer, es wäre großartig gewesen, wenn Sie erklärt hätten, was passiert, wenn der Long-Wert für int außerhalb des Bereichs liegt. Ich denke, es wird eine ClassCastException oder so etwas geben?
Peter Wippermann
2
@PeterWippermann: Ich habe noch ein paar Infos hinzugefügt. Halten Sie sie für verständlich bzw. erklärend genug?
Andreas
7

Ich behaupte, dass der offensichtliche Weg, um zu sehen, ob das Wirken eines Wertes den Wert verändert hat, darin besteht, das Ergebnis zu wirken und zu überprüfen. Ich würde jedoch die unnötige Besetzung beim Vergleich entfernen. Ich bin auch nicht besonders an Ein-Buchstaben-Variablennamen interessiert (Ausnahme xund y, aber nicht, wenn sie Zeile und Spalte bedeuten (manchmal jeweils)).

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

Eigentlich möchte ich diese Konvertierung aber möglichst vermeiden. Natürlich ist es manchmal nicht möglich, aber in diesen Fällen IllegalArgumentExceptionist es mit ziemlicher Sicherheit die falsche Ausnahme, wenn es um Client-Code geht.

Tom Hawtin - Tackline
quelle
1
Dies tun die neuesten Versionen von Google Guava Ints :: CheckedCast.
Lexicalscope
2

Java-Integer-Typen werden als signiert dargestellt. Mit einem Eingang zwischen 2 31 und 2 32 (oder -2 31 und -2 32 ) wäre die Besetzung erfolgreich, aber Ihr Test würde fehlschlagen.

Es ist zu prüfen, ob alle hohen Bits von longgleich sind:

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("...");
    }
}
Mob
quelle
3
Ich verstehe nicht, was Signiertheit damit zu tun hat. Können Sie ein Beispiel nennen, das keine Informationen verliert, aber den Test nicht besteht? 2 ^ 31 würde in Integer.MIN_VALUE (dh -2 ^ 31) umgewandelt, sodass Informationen verloren gehen.
Jon Skeet
@ Jon Skeet: Vielleicht reden ich und das OP aneinander vorbei. (int) 0xFFFFFFFFund (long) 0xFFFFFFFFLhaben unterschiedliche Werte, aber beide enthalten die gleichen "Informationen", und es ist fast trivial, den ursprünglichen langen Wert aus dem int zu extrahieren.
Mob
Wie können Sie den ursprünglichen Long-Wert aus dem Int extrahieren, wenn der Long-Wert zunächst -1 anstelle von 0xFFFFFFFF hätte sein können?
Jon Skeet
Es tut mir leid, wenn ich nicht klar bin. Ich sage, wenn das lange und das int beide die gleichen 32 Informationsbits enthalten und wenn das 32. Bit gesetzt ist, dann unterscheidet sich der int-Wert vom langen Wert, aber das ist es ist leicht, den langen Wert zu bekommen.
Mob
@mob worauf bezieht sich das? OPs Code meldet korrekt, dass lange Werte> 2 ^ {31} nicht in Ints umgewandelt werden können
teilweise bewölkt
0
(int) (longType + 0)

aber Long kann das Maximum nicht überschreiten :)

Maury
quelle
1
Das + 0fügt dieser Konvertierung nichts hinzu. Es könnte funktionieren, wenn Java die Verkettung numerischer Typen ähnlich wie Zeichenfolgen behandelt, aber da Sie keine Add-Operation ohne Grund ausführen.
Sixones
-7

Eine andere Lösung kann sein:

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

Ich habe dies für Fälle versucht, in denen der Client einen POST ausführt und die Server-DB nur Ganzzahlen versteht, während der Client einen Long hat.

Rajat Anantharam
quelle
Sie erhalten eine NumberFormatException für "real long" -Werte: Dies Integer.valueOf(Long.MAX_VALUE.toString()); führt dazu, java.lang.NumberFormatException: For input string: "9223372036854775807" dass die Out-of-Range-Ausnahme so gut wie verschleiert wird, da sie jetzt genauso behandelt wird wie eine Zeichenfolge, die Buchstaben enthält.
Andreas
2
Es wird auch nicht kompiliert, da Sie nicht immer einen Wert zurückgeben.
Patrick M