Pengecoran aman untuk int di Jawa


489

Apa cara paling idiomatis di Jawa untuk memverifikasi bahwa para pemain dari longke inttidak kehilangan informasi apa pun?

Ini adalah implementasi saya saat ini:

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
Dua jalur kode. Salah satunya adalah warisan dan kebutuhan int. Data warisan itu HARUS cocok dengan int, tapi saya ingin melempar pengecualian jika asumsi itu dilanggar. Jalur kode lainnya akan menggunakan rindu dan tidak akan membutuhkan pemeran.
Brigham

197
Saya suka bagaimana orang selalu mempertanyakan mengapa Anda ingin melakukan apa yang ingin Anda lakukan. Jika semua orang menjelaskan kasus penggunaan penuh mereka dalam pertanyaan-pertanyaan ini, tidak ada yang akan bisa membacanya, apalagi menjawabnya.
BT

24
BT - Saya sangat benci mengajukan pertanyaan online untuk alasan ini. Jika Anda ingin membantu, itu bagus, tetapi jangan mainkan 20 pertanyaan dan paksakan diri mereka sendiri.
Mason240

59
Tidak setuju dengan BT dan Mason240 di sini: sering kali bermanfaat untuk menunjukkan kepada si penanya solusi lain yang belum mereka pikirkan. Menandai bau kode adalah layanan yang bermanfaat. Ini jauh dari 'Saya ingin tahu mengapa ...' untuk 'memaksa mereka untuk membenarkan diri mereka sendiri'.
Tommy Herbert

13
Ada banyak hal yang tidak dapat Anda lakukan dengan long misalnya indeks sebuah array.
skot

Jawaban:


580

Metode baru telah ditambahkan dengan Java 8 untuk melakukan hal itu.

import static java.lang.Math.toIntExact;

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

Akan melempar ArithmeticExceptionjika terjadi luapan.

Lihat: Math.toIntExact(long)

Beberapa metode overflow aman lainnya telah ditambahkan ke Java 8. Mereka berakhir dengan tepat .

Contoh:

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

5
Kami juga punya addExactdan multiplyExact. Yang perlu diperhatikan adalah pembagian ( MIN_VALUE/-1) dan nilai absolut ( abs(MIN_VALUE)) tidak memiliki metode kenyamanan yang aman.
Aleksandr Dubinsky

Tapi apa bedanya menggunakan Math.toIntExact()bukan pemain biasa int? Implementasi Math.toIntExact()hanya dilemparkan longke int.
Yamashiro Rion

@YamashiroRion Sebenarnya implementasi toIntExact pertama-tama memeriksa apakah para pemain akan menyebabkan overflow, dalam hal ini melempar ArithmeticException. Hanya jika gips aman maka gips melakukan dari panjang ke int yang dikembalikan. Dengan kata lain, jika Anda mencoba melemparkan angka panjang yang tidak dapat direpresentasikan sebagai int (mis. Angka apa pun yang benar-benar di atas 2 147 483 647) ia akan melempar ArithmeticException. Jika Anda melakukan hal yang sama dengan gips sederhana, nilai int Anda yang dihasilkan akan salah.
Pierre-Antoine

306

Saya pikir saya akan melakukannya sesederhana:

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

Saya pikir itu mengekspresikan maksud lebih jelas daripada casting berulang ... tapi itu agak subyektif.

Catatan minat potensial - dalam C # itu hanya akan menjadi:

return checked ((int) l);

7
Saya selalu melakukan pemeriksaan jangkauan sebagai (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Saya merasa sulit untuk mencari cara lain untuk melakukannya. Kasihan Java tidak punya unless.
Tom Hawtin - tackline

5
+1. Ini tepat berada di bawah aturan "pengecualian harus digunakan untuk kondisi luar biasa ".
Adam Rosenfield

4
(Dalam bahasa tujuan umum modern itu akan menjadi: "Eh? Tapi ints memiliki ukuran yang sewenang-wenang?")
Tom Hawtin - tackline

7
@ Tom: Preferensi pribadi, saya kira - Saya lebih suka memiliki sesedikit mungkin negatif. Jika saya melihat "jika" dengan tubuh yang melempar pengecualian, saya ingin melihat kondisi yang membuatnya tampak luar biasa - seperti nilainya "dari ujung" int.
Jon Skeet

6
@ Tom: Dalam hal ini saya akan menghapus yang negatif, letakkan gips / kembali di dalam tubuh "jika", dan kemudian melemparkan pengecualian setelah itu, jika Anda melihat apa yang saya maksud.
Jon Skeet

132

Dengan kelas Inva Google Guava , metode Anda dapat diubah menjadi:

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

Dari dokumen tertaut:

checkedCast

public static int checkedCast(long value)

Mengembalikan nilai int yang sama dengan value, jika memungkinkan.

Parameter: value - nilai apa pun dalam kisaran intjenis

Pengembalian: dengan intnilai yang sama denganvalue

Melempar: IllegalArgumentException - jika valuelebih besar dari Integer.MAX_VALUEatau kurang dariInteger.MIN_VALUE

Kebetulan, Anda tidak memerlukan safeLongToIntpembungkus, kecuali Anda ingin membiarkannya di tempat untuk mengubah fungsi tanpa tentu saja refactoring yang luas.


3
Guava's Ints.checkedCastmelakukan persis apa yang OP lakukan, secara kebetulan
Partly Cloudy

14
+1 untuk solusi Guava, meskipun tidak perlu membungkusnya dengan metode lain, cukup hubungi Ints.checkedCast(l)langsung.
dimo414

8
Jambu biji juga memiliki Ints.saturatedCastyang akan mengembalikan nilai terdekat daripada melemparkan pengecualian.
Jake Walsh

Ya, aman menggunakan api yang ada sebagai kasus saya, pustaka yang sudah ada dalam proyek: untuk melempar pengecualian jika tidak valid: Ints.checkedCast (long) dan Ints.saturatedCast (long) untuk mendapatkan yang terdekat untuk mengkonversi panjang ke int.
Osify

29

Dengan BigDecimal:

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

Saya suka yang ini, ada yang menentang solusi ini?
Rui Marques

12
Yah, itu mengalokasikan dan membuang BigDecimal hanya untuk mendapatkan apa yang seharusnya menjadi metode utilitas, jadi ya, itu bukan proses terbaik.
Bersepeda

@ Bersepeda dalam hal itu, lebih baik menggunakan BigDecimal.valueOf(aLong), alih-alih new BigDecimal(aLong), untuk menyatakan bahwa instance baru tidak diperlukan. Apakah lingkungan eksekusi tidak caching pada metode itu, apakah implementasi spesifik, seperti halnya kemungkinan adanya Analisis Escape. Dalam kebanyakan kasus kehidupan nyata, ini tidak berdampak pada kinerja.
Holger

17

berikut ini solusinya, jika Anda tidak peduli dengan nilai jika itu lebih besar dari yang dibutuhkan;)

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

tampaknya, Anda salah ... itu akan berfungsi dengan baik kemudian negatif. juga, apa artinya too low? tolong berikan kasus penggunaan.
Vitaliy Kulikov

solusi ini adalah yang cepat dan aman, maka kita berbicara untuk memberikan Long to Int untuk mematuhi hasil.
Vitaliy Kulikov

11

DONT: Ini bukan solusi!

Pendekatan pertama saya adalah:

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

Tapi itu hanya sekedar melemparkan long ke int, berpotensi menciptakan Longinstance baru atau mengambilnya dari Long pool.


Kekurangannya

  1. Long.valueOfmembuat Longcontoh baru jika nomor tersebut tidak berada dalam Longkisaran kumpulan [-128, 127].

  2. The intValuepelaksanaan tidak apa-apa lebih dari:

    return (int)value;

Jadi ini bisa dianggap lebih buruk daripada hanya melemparkan longke int.


4
Upaya Anda untuk membantu dihargai, tetapi memberikan contoh sesuatu yang tidak berhasil tidak sama dengan memberikan solusi yang berhasil. Jika Anda ingin mengedit untuk menambahkan cara yang benar, ini bisa sangat bagus; jika tidak, itu tidak benar-benar cocok untuk diposting sebagai jawaban.
Pops

4
Oke, mengapa tidak memiliki kedua DO dan DONT? Tbh, kadang-kadang saya berharap saya memiliki daftar bagaimana tidak melakukan sesuatu (DONT) untuk memeriksa apakah saya menggunakan pola / kode seperti itu. Bagaimanapun, saya dapat menghapus "jawaban" ini.
Andreas

1
Anti-polanya bagus. Pokoknya akan lebih bagus jika Anda menjelaskan apa yang terjadi jika nilai panjangnya di luar kisaran untuk int? Saya kira akan ada ClassCastException atau sesuatu seperti ini?
Peter Wippermann

2
@ PeterWippermann: Saya telah menambahkan beberapa info lagi. Apakah Anda menganggap mereka resp dimengerti. cukup jelas?
Andreas

7

Saya mengklaim bahwa cara yang jelas untuk melihat apakah casting nilai mengubah nilainya adalah dengan melemparkan dan memeriksa hasilnya. Saya akan, bagaimanapun, menghapus pemain yang tidak perlu saat membandingkan. Saya juga tidak terlalu tertarik pada satu nama variabel huruf (pengecualian xdan y, tetapi tidak ketika itu berarti baris dan kolom (kadang-kadang masing-masing)).

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

Namun, sungguh saya ingin menghindari konversi ini jika memungkinkan. Jelas kadang-kadang itu tidak mungkin, tetapi dalam kasus-kasus IllegalArgumentExceptionitu hampir pasti pengecualian yang salah untuk membuang sejauh kode klien yang bersangkutan.


1
Inilah yang dilakukan versi terbaru Google Guava Ints :: checkedCast.
lexicalscope

2

Jenis integer Java direpresentasikan sebagai ditandatangani. Dengan input antara 2 31 dan 2 32 (atau -2 31 dan -2 32 ) para pemain akan berhasil tetapi tes Anda akan gagal.

Yang perlu diperiksa adalah apakah semua bit tinggi longsemua sama:

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
Saya tidak melihat apa kaitannya dengan keterikatan. Bisakah Anda memberikan contoh yang tidak informasi kehilangan tetapi tidak gagal tes? 2 ^ 31 akan dilemparkan ke Integer.MIN_VALUE (yaitu -2 ^ 31) sehingga informasi telah hilang.
Jon Skeet

@ Jon Skeet: Mungkin saya dan OP saling berbicara satu sama lain. (int) 0xFFFFFFFFdan (long) 0xFFFFFFFFLmemiliki nilai yang berbeda, tetapi keduanya mengandung "informasi" yang sama, dan hampir sepele untuk mengekstrak nilai panjang asli dari int.
mob

Bagaimana Anda bisa mengekstrak nilai panjang asli dari int, ketika panjang bisa -1 untuk memulai, bukan 0xFFFFFFFF?
Jon Skeet

Maaf jika saya tidak jelas. Saya mengatakan bahwa jika panjang dan int keduanya berisi 32 bit informasi yang sama, dan jika bit ke-32 diatur, maka nilai int berbeda dari nilai panjang, tetapi itu mudah untuk mendapatkan nilai panjang.
mob

@ Bob apa ini referensi? Kode OP dengan benar melaporkan bahwa nilai-nilai lama> 2 ^ {31} tidak dapat dilemparkan ke int
Partly Cloudy

0
(int) (longType + 0)

tapi Panjang tidak bisa melebihi maksimum :)


1
The + 0menambahkan tidak ada konversi ini, mungkin bekerja jika Java diperlakukan jenis Rangkaian numerik dalam cara yang mirip dengan string, tetapi karena tidak Anda melakukan operasi add tanpa alasan.
Sixones

-7

Satu solusi lain dapat:

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

Saya telah mencoba ini untuk kasus-kasus di mana klien melakukan POST dan server DB hanya memahami bilangan bulat sementara klien memiliki panjang.


Anda akan mendapatkan NumberFormatException pada nilai "nyata panjang": Integer.valueOf(Long.MAX_VALUE.toString()); hasil java.lang.NumberFormatException: For input string: "9223372036854775807" yang cukup banyak mengaburkan Pengecualian Di Luar Jangkauan karena sekarang diperlakukan dengan cara yang sama dengan string yang berisi huruf diperlakukan.
Andreas

2
Itu juga tidak dikompilasi karena Anda tidak selalu mengembalikan nilai.
Patrick M
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.