Bezpieczne przesyłanie do int w Javie

488

Jaki jest najbardziej idiomatyczny sposób w Javie, aby zweryfikować, czy rzutowanie z longna intnie traci żadnych informacji?

To jest moja obecna implementacja:

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
źródło
34
Dwie ścieżki kodu. Jednym z nich jest dziedzictwo i wymaga int. Wszystkie starsze dane MUSZĄ pasować do int, ale chcę zgłosić wyjątek, jeśli założenie to zostanie naruszone. Druga ścieżka kodu będzie używać długich i nie będzie potrzebować rzutowania.
Brigham,
197
Uwielbiam to, jak ludzie zawsze pytają, dlaczego chcesz robić to, co chcesz. Gdyby wszyscy wyjaśnili swój pełny przypadek użycia w tych pytaniach, nikt nie byłby w stanie ich przeczytać, a tym bardziej odpowiedzieć na nie.
BT
24
BT - Z tego powodu naprawdę nie lubię zadawać pytań przez Internet. Jeśli chcesz pomóc, to świetnie, ale nie graj w 20 pytań i nie zmuszaj ich do usprawiedliwienia się.
Mason240,
59
Nie zgadzaj się tutaj z BT i Mason240: często warto zadać pytającemu inne rozwiązanie, o którym nie pomyśleli. Oznaczanie zapachów kodu jest przydatną usługą. To długa droga od „Jestem ciekawy, dlaczego ...” „zmusić ich do usprawiedliwienia się”.
Tommy Herbert
13
Jest wiele rzeczy, których nie można zrobić z długimi, np. Indeksować tablicę.
skot

Odpowiedzi:

579

W tym celu dodano nową metodę do Java 8 .

import static java.lang.Math.toIntExact;

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

Rzuci ArithmeticExceptionw przypadku przepełnienia.

Widzieć: Math.toIntExact(long)

Kilka innych bezpiecznych metod przelewu zostało dodanych do Java 8. Kończą się dokładnie .

Przykłady:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)
Pierre-Antoine
źródło
5
Mamy również addExacti multiplyExact. Warto zauważyć, że dzielenie ( MIN_VALUE/-1) i wartość bezwzględna ( abs(MIN_VALUE)) nie mają bezpiecznych metod wygody.
Aleksandr Dubinsky
Ale jaka jest różnica w użyciu Math.toIntExact()zamiast zwykłej obsady int? Implementacja Math.toIntExact()just castuje longdo int.
Yamashiro Rion
@YamashiroRion W rzeczywistości implementacja toIntExact najpierw sprawdza, czy rzutowanie doprowadzi do przepełnienia, w którym to przypadku zgłasza wyjątek ArithmeticException. Tylko jeśli rzut jest bezpieczny, wykonuje rzut z długiego na int, który zwraca. Innymi słowy, jeśli spróbujesz rzucić długą liczbę, która nie może być reprezentowana jako liczba całkowita (np. Dowolna liczba ściśle powyżej 2 147 483 647), wygeneruje wyjątek ArithmeticException. Jeśli zrobisz to samo za pomocą prostego rzutowania, wynikowa wartość int będzie niepoprawna.
Pierre-Antoine
306

Myślę, że zrobiłbym to tak prosto jak:

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

Myślę, że to wyraźniej wyraża intencję niż powtarzające się castowanie ... ale jest to nieco subiektywne.

Uwaga potencjalnego zainteresowania - w języku C # byłoby to po prostu:

return checked ((int) l);
Jon Skeet
źródło
7
Zawsze sprawdzałbym zasięg jako (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Trudno mi znaleźć inne sposoby na zrobienie tego. Szkoda Java nie ma unless.
Tom Hawtin - tackline
5
+1. Jest to dokładnie objęte zasadą „wyjątki należy stosować w wyjątkowych warunkach”.
Adam Rosenfield
4
(W nowoczesnym języku ogólnego przeznaczenia byłoby to: „Eh? Ale ints mają dowolny rozmiar?”)
Tom Hawtin - tackline
7
@Tom: Chyba osobiste preferencje - wolę mieć jak najmniej negatywów. Jeśli patrzę na „jeśli” z ciałem, które rzuca wyjątek, chciałbym zobaczyć warunki, które sprawiają, że wygląda on wyjątkowo - na przykład wartość „z dołu” int.
Jon Skeet,
6
@Tom: W takim przypadku usunę negatyw, wstawię rzut / powrót do ciała „if”, a następnie wrzuci wyjątek, jeśli zrozumiesz, co mam na myśli.
Jon Skeet,
132

Dzięki klasie Ints Google Guava możesz zmienić metodę na:

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

Z powiązanych dokumentów:

CheckCast

public static int checkedCast(long value)

Zwraca wartość int, która jest równa value, jeśli to możliwe.

Parametry: value - dowolna wartość z zakresu inttypu

Powroty:int wartość zgodnąvalue

Rzuty: IllegalArgumentException - jeśli valuejest większy Integer.MAX_VALUElub mniejszy niżInteger.MIN_VALUE

Nawiasem mówiąc, nie potrzebujesz safeLongToIntotoki, chyba że chcesz zostawić ją na miejscu w celu zmiany funkcjonalności bez obszernego refaktoryzacji.

prasopy
źródło
3
Ints.checkedCastNawiasem mówiąc, Guava robi dokładnie to, co robi OP
Częściowe zachmurzenie
14
+1 za rozwiązanie Guava, choć nie ma potrzeby owijania go inną metodą, wystarczy zadzwonić Ints.checkedCast(l)bezpośrednio.
dimo414,
8
Guava ma również opcję, Ints.saturatedCastktóra zwróci najbliższą wartość zamiast zgłaszania wyjątku.
Jake Walsh
Tak, bezpiecznie jest używać istniejącego interfejsu API w moim przypadku, biblioteki już w projekcie: aby zgłosić wyjątek, jeśli jest niepoprawny: Ints.checkedCast (długi) i Ints.saturatedCast (długi), aby uzyskać najbliższy konwertujący długi na int.
Osify
29

Z BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds
Jaime Saiz
źródło
Podoba mi się, czy ktoś ma coś przeciwko temu rozwiązaniu?
Rui Marques
12
Cóż, to przydzielanie i wyrzucanie BigDecimal tylko po to, aby uzyskać metodę użyteczną, więc tak, to nie jest najlepszy proces.
Riking
@ Riking w tym względzie, lepiej użyć BigDecimal.valueOf(aLong)zamiast new BigDecimal(aLong), aby zaznaczyć, że nowa instancja nie jest wymagana. To, czy środowisko wykonawcze buforuje przy użyciu tej metody, zależy od implementacji, podobnie jak ewentualna obecność analizy ucieczki. W większości rzeczywistych przypadków nie ma to wpływu na wydajność.
Holger
17

oto rozwiązanie, na wypadek gdybyś nie dbał o wartość, gdyby była większa niż potrzebna;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}
Witalij Kulikow
źródło
wygląda na to, że się mylisz ... będzie działało dobrze, a następnie negatywnie. co też znaczy too low? proszę podać przypadek użycia.
Vitaliy Kulikov
to rozwiązanie jest szybkie i bezpieczne, dlatego rozmawiamy o oddaniu Long to Int, aby zastosować się do wyniku.
Vitaliy Kulikov
11

DONT: To nie jest rozwiązanie!

Moje pierwsze podejście było:

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

Ale to tylko rzuca long na int, potencjalnie tworząc nowe Longinstancje lub odzyskując je z puli Long.


Wady

  1. Long.valueOftworzy nową Longinstancję, jeśli liczba nie mieści się w Longzakresie puli [-128, 127].

  2. intValueRealizacja nie robi nic więcej niż:

    return (int)value;

Można to uznać za jeszcze gorsze niż zwykłe przesłanie longdo int.

Andreas
źródło
4
Doceniamy twoją próbę pomocy, ale podanie przykładu czegoś, co nie działa, nie jest tym samym, co zapewnienie rozwiązania, które działa. Jeśli edytujesz, aby dodać poprawny sposób, może to być całkiem dobre; w przeciwnym razie nie nadaje się do opublikowania jako odpowiedzi.
Wyskakuje
4
Okej, dlaczego nie mieć zarówno DO, jak i DONT? Thh, czasami chciałbym mieć listę, jak nie robić rzeczy (DONT), aby sprawdzić, czy użyłem takiego wzorca / kodu. W każdym razie mogę usunąć tę „odpowiedź”.
Andreas
1
Dobry anty-wzór. W każdym razie byłoby wspaniale, gdybyś wyjaśnił, co się stanie, jeśli wartość długa jest poza zakresem dla int? Myślę, że będzie wyjątek ClassCastException czy coś takiego?
Peter Wippermann
2
@PeterWippermann: Dodałem więcej informacji. Czy uważasz je za zrozumiałe? wystarczająco wyjaśniający?
Andreas
7

Twierdzę, że oczywistym sposobem sprawdzenia, czy rzutowanie wartości zmieniło wartość, byłoby rzutowanie i sprawdzenie wyniku. Chciałbym jednak usunąć niepotrzebną obsadę podczas porównywania. Nie przepadam też za nazwami zmiennych o jednej literze (wyjątek xi y, ale nie kiedy oznaczają one wiersz i kolumnę (czasami odpowiednio)).

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

Jednak naprawdę chciałbym uniknąć tej konwersji, jeśli to w ogóle możliwe. Oczywiście czasami nie jest to możliwe, ale w takich przypadkach IllegalArgumentExceptionprawie na pewno jest to zły wyjątek, jeśli chodzi o podanie kodu klienta.

Tom Hawtin - tackline
źródło
1
To właśnie robią ostatnie wersje Google Guava Ints :: selectedCast.
leksykalny,
2

Typy całkowite Java są reprezentowane jako podpisane. Przy wartości wejściowej między 2 31 a 2 32 (lub -2 31 i -2 32 ) rzutowanie się powiedzie, ale test się nie powiedzie.

Należy sprawdzić, czy wszystkie wysokie bity longsą takie same:

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("...");
    }
}
tłum
źródło
3
Nie rozumiem, co ma z tym wspólnego podpisanie. Czy możesz podać przykład, który nie traci informacji, ale nie powiedzie się test? 2 ^ 31 zostanie przekazane na Integer.MIN_VALUE (tj. -2 ^ 31), więc informacje zostaną utracone.
Jon Skeet,
@Jon Skeet: Może ja i OP rozmawiamy obok siebie. (int) 0xFFFFFFFFi (long) 0xFFFFFFFFLmają różne wartości, ale oba zawierają te same „informacje”, a wyodrębnienie długiej wartości z wartości int jest prawie banalne.
tłum
W jaki sposób można wyodrębnić pierwotną długą wartość z wartości int, kiedy początkowa wartość mogłaby wynosić -1 zamiast 0xFFFFFFFF?
Jon Skeet,
Przepraszam, jeśli nie jestem jasny. Mówię, że jeśli zarówno long, jak i int zawierają te same 32 bity informacji, a jeśli ustawiony jest 32-bit, to wartość int różni się od wartości długiej, ale że łatwo uzyskać długą wartość
mob
@mob, co to ma znaczyć? Kod OP poprawnie zgłasza, że ​​długie wartości> 2 ^ {31} nie mogą być rzutowane na ints
niewielkie zachmurzenie
0
(int) (longType + 0)

ale Long nie może przekroczyć maksimum :)

Maury
źródło
1
+ 0Dodaje nic do tego nawrócenia, to może działać, jeśli traktowane numeryczną typu Java konkatenacji w sposób podobny do strun, ale ponieważ nie robisz operację dodawania bez powodu.
sixones
-7

Innym rozwiązaniem może być:

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

Próbowałem tego w przypadkach, w których klient wykonuje test POST, a serwer DB rozumie tylko liczby całkowite, podczas gdy klient ma wartość Long.

Rajat Anantharam
źródło
Otrzymasz wyjątek NumberFormatException na „prawdziwie długich” wartościach: Integer.valueOf(Long.MAX_VALUE.toString()); powoduje to, java.lang.NumberFormatException: For input string: "9223372036854775807"że prawie zaciemnia wyjątek poza zakresem, ponieważ jest on teraz traktowany w taki sam sposób, jak traktowany jest ciąg zawierający litery.
Andreas
2
Nie kompiluje się również, ponieważ nie zawsze zwracana jest wartość.
Patrick M