Truyền an toàn dài đến int trong Java


489

Cách thức thành ngữ nhất trong Java để xác minh rằng việc truyền từ longđến intkhông mất bất kỳ thông tin nào?

Đây là triển khai hiện tại của tôi:

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
Hai đường dẫn mã. Một là di sản và cần ints. Dữ liệu kế thừa đó NÊN tất cả phù hợp với một int, nhưng tôi muốn đưa ra một ngoại lệ nếu giả định đó bị vi phạm. Đường dẫn mã khác sẽ sử dụng lâu dài và sẽ không cần diễn viên.
Brigham

197
Tôi thích cách mọi người luôn đặt câu hỏi tại sao bạn muốn làm những gì bạn muốn làm. Nếu tất cả mọi người giải thích trường hợp sử dụng đầy đủ của họ trong những câu hỏi này, không ai có thể đọc chúng, ít trả lời chúng hơn.
BT

24
BT - Tôi thực sự ghét hỏi bất kỳ câu hỏi trực tuyến vì lý do này. Nếu bạn muốn giúp đỡ, điều đó thật tuyệt, nhưng đừng chơi 20 câu hỏi và buộc họ tự biện minh.
Mason240

59
Không đồng ý với BT và Mason240 ở đây: thường rất có giá trị để chỉ cho người hỏi một giải pháp khác mà họ chưa từng nghĩ tới. Đánh dấu lên mã mùi là một dịch vụ hữu ích. Đó là một chặng đường dài từ 'Tôi tò mò về lý do tại sao ...' để 'buộc họ phải tự biện minh'.
Tommy Herbert

13
Có rất nhiều điều bạn không thể làm với thời gian dài, ví dụ như lập chỉ mục một mảng.
skot

Câu trả lời:


580

Một phương thức mới đã được thêm vào với Java 8 để làm việc đó.

import static java.lang.Math.toIntExact;

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

Sẽ ném một ArithmeticExceptiontrong trường hợp tràn.

Xem: Math.toIntExact(long)

Một số phương thức an toàn tràn khác đã được thêm vào Java 8. Chúng kết thúc chính xác .

Ví dụ:

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

5
Chúng tôi cũng có addExactmultiplyExact. Đáng lưu ý là phép chia ( MIN_VALUE/-1) và giá trị tuyệt đối ( abs(MIN_VALUE)) không có các phương thức tiện lợi an toàn.
Alexanderr Dubinsky

Nhưng sự khác biệt của việc sử dụng Math.toIntExact()thay vì diễn viên thông thường là intgì? Việc thực hiện các Math.toIntExact()chỉ phôi longđể int.
Yamashiro Rion

@YamashiroRion Trên thực tế, việc triển khai toIntExact trước tiên kiểm tra xem diễn viên có dẫn đến tràn không, trong trường hợp đó, nó ném ra ArithaturesException. Chỉ khi diễn viên an toàn thì nó mới thực hiện truyền từ dài đến int mà nó trả về. Nói cách khác, nếu bạn cố gắng tạo một số dài không thể được biểu diễn dưới dạng int (ví dụ: bất kỳ số nào đúng trên 2 147 483 647), nó sẽ đưa ra một Số học ngoại lệ. Nếu bạn làm tương tự với một diễn viên đơn giản, giá trị int kết quả của bạn sẽ bị sai.
Pierre-Antoine

306

Tôi nghĩ rằng tôi sẽ làm điều đó đơn giản như:

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

Tôi nghĩ rằng điều đó thể hiện ý định rõ ràng hơn so với việc lặp đi lặp lại ... nhưng nó hơi chủ quan.

Lưu ý về mối quan tâm tiềm năng - trong C #, nó sẽ chỉ là:

return checked ((int) l);

7
Tôi sẽ luôn luôn kiểm tra phạm vi như (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Tôi cảm thấy khó khăn để có được đầu óc của tôi xung quanh các cách khác để làm điều đó. Đáng tiếc Java không có unless.
Tom Hawtin - tackline

5
+1. Điều này rơi chính xác theo quy tắc "ngoại lệ nên được sử dụng cho các điều kiện đặc biệt ".
Adam Rosenfield

4
(Trong một ngôn ngữ có mục đích chung hiện đại, nó sẽ là: "Eh? Nhưng ints có kích thước tùy ý?")
Tom Hawtin - tackline

7
@Tom: Sở thích cá nhân, tôi đoán - Tôi thích có càng ít tiêu cực càng tốt. Nếu tôi đang nhìn vào một chữ "nếu" với một cơ thể ném ra một ngoại lệ, tôi muốn thấy các điều kiện làm cho nó trông thật đặc biệt - như giá trị bị "tắt ở cuối" int.
Jon Skeet

6
@Tom: Trong trường hợp đó, tôi sẽ loại bỏ âm bản, đặt diễn viên / trở lại bên trong cơ thể "nếu", sau đó ném ngoại lệ sau đó, nếu bạn hiểu ý tôi là gì.
Jon Skeet

132

Với lớp Ints của Google Guava , phương thức của bạn có thể được thay đổi thành:

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

Từ các tài liệu được liên kết:

đã kiểm tra

public static int checkedCast(long value)

Trả về giá trị int bằng value, nếu có thể.

Tham số: value - bất kỳ giá trị nào trong phạm vi của intloại

Returns: các intgiá trị mà bình đẳngvalue

Ném: IllegalArgumentException - nếu valuelớn hơn Integer.MAX_VALUEhoặc nhỏ hơnInteger.MIN_VALUE

Ngẫu nhiên, bạn không cần safeLongToInttrình bao bọc, trừ khi bạn muốn để nó thay thế chức năng mà không cần tái cấu trúc rộng rãi.


3
Guava Ints.checkedCastthực hiện chính xác những gì OP làm, tình cờ
Một phần mây

14
+1 cho giải pháp Guava, mặc dù không thực sự cần bọc nó bằng phương pháp khác, chỉ cần gọi Ints.checkedCast(l)trực tiếp.
dimo414

8
Quả ổi cũng có Ints.saturatedCastgiá trị gần nhất thay vì ném ngoại lệ.
Jake Walsh

Có, an toàn khi sử dụng api hiện tại như trường hợp của tôi, thư viện đã có trong dự án: ném ngoại lệ nếu không hợp lệ: Ints.checkedCast (dài) và Ints.saturatedCast (dài) để có được gần nhất để chuyển đổi dài sang int.
Osify

29

Với BigDecimal:

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

Tôi thích cái này, có ai có gì chống lại giải pháp này không?
Rui Marques

12
Chà, nó phân bổ và vứt bỏ BigDecimal chỉ để tìm ra phương pháp tiện ích, vì vậy, đó không phải là quy trình tốt nhất.
Đạp xe

@ Đi xe đạp về vấn đề đó, tốt hơn là sử dụng BigDecimal.valueOf(aLong), thay vì new BigDecimal(aLong), để biểu thị rằng một trường hợp mới là không bắt buộc. Liệu môi trường thực thi có lưu bộ đệm vào phương thức đó hay không, có phải là triển khai cụ thể không, giống như sự hiện diện có thể có của Phân tích Thoát. Trong hầu hết các trường hợp thực tế, điều này không ảnh hưởng đến hiệu suất.
Holger

17

Đây là một giải pháp, trong trường hợp bạn không quan tâm đến giá trị trong trường hợp nó lớn hơn thì cần thiết;)

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

có vẻ như bạn đã sai ... nó sẽ hoạt động tốt sau đó tiêu cực. Ngoài ra, có nghĩa là too lowgì? xin vui lòng, cung cấp trường hợp sử dụng.
Vitaliy Kulikov

giải pháp này là giải pháp nhanh và an toàn, sau đó chúng tôi đang nói chuyện để đưa Long đến Int để tuân theo kết quả.
Vitaliy Kulikov

11

ĐỪNG: Đây không phải là một giải pháp!

Cách tiếp cận đầu tiên của tôi là:

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

Nhưng điều đó chỉ đơn thuần là chuyển từ dài sang int, có khả năng tạo ra các Longthể hiện mới hoặc truy xuất chúng từ nhóm Long.


Hạn chế

  1. Long.valueOftạo một thể hiện mới Longnếu số đó không nằm trong Longphạm vi nhóm [-128, 127].

  2. Việc intValuethực hiện không có gì hơn:

    return (int)value;

Vì vậy, đây có thể được coi thậm chí còn tồi tệ hơn chỉ đúc các longđến int.


4
Nỗ lực giúp đỡ của bạn được đánh giá cao, nhưng đưa ra một ví dụ về một thứ không hoạt động không hoàn toàn giống như cung cấp một giải pháp hiệu quả. Nếu bạn chỉnh sửa để thêm một cách chính xác, điều này có thể khá tốt; mặt khác, nó không thực sự phù hợp để được đăng lên như một câu trả lời.
Pops

4
Được rồi, tại sao không có cả DO và KHÔNG? Tbh, đôi khi tôi ước mình có một danh sách làm thế nào để không làm mọi thứ (KHÔNG) để kiểm tra xem tôi có sử dụng một mẫu / mã như vậy không. Dù sao, tôi có thể xóa "câu trả lời" này.
Andreas

1
Chống mẫu tốt. Dù sao, nó sẽ là tuyệt vời nếu bạn giải thích điều gì xảy ra nếu giá trị dài nằm ngoài phạm vi cho int? Tôi đoán sẽ có ClassCastException hoặc một cái gì đó như thế này?
Peter Wippermann

2
@PeterWippermann: Tôi đã thêm một số thông tin. Bạn có xem họ tôn trọng dễ hiểu. giải thích đủ chưa?
Andreas

7

Tôi khẳng định rằng cách rõ ràng để xem việc truyền giá trị có thay đổi giá trị hay không sẽ là truyền và kiểm tra kết quả. Tôi sẽ, tuy nhiên, loại bỏ các diễn viên không cần thiết khi so sánh. Tôi cũng không quá quan tâm đến tên biến một chữ cái (ngoại lệ xy, nhưng không phải khi chúng có nghĩa là hàng và cột (đôi khi tương ứng)).

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

Tuy nhiên, thực sự tôi muốn tránh chuyển đổi này nếu có thể. Rõ ràng đôi khi điều đó là không thể, nhưng trong những trường hợp đó IllegalArgumentExceptiongần như chắc chắn là ngoại lệ sai khi ném liên quan đến mã khách hàng.


1
Đây là những gì các phiên bản gần đây của Google Guava Ints :: checkCast làm.
vựng

2

Các kiểu số nguyên Java được biểu diễn dưới dạng đã ký. Với đầu vào giữa 2 31 và 2 32 (hoặc -2 31 và -2 32 ), diễn viên sẽ thành công nhưng thử nghiệm của bạn sẽ thất bại.

Điều cần kiểm tra là liệu tất cả các bit cao của longtất cả có giống nhau không:

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
Tôi không thấy sự ký kết có liên quan gì với nó. Bạn có thể đưa ra một ví dụ không mất thông tin nhưng không làm bài kiểm tra? 2 ^ 31 sẽ được chuyển sang Integer.MIN_VALUE (tức là -2 ^ 31) để thông tin bị mất.
Jon Skeet

@Jon Skeet: Có lẽ tôi và OP đang nói chuyện với nhau. (int) 0xFFFFFFFF(long) 0xFFFFFFFFLcó các giá trị khác nhau, nhưng cả hai đều chứa cùng một "thông tin" và việc trích xuất giá trị dài ban đầu từ int.
mob

Làm thế nào bạn có thể trích xuất giá trị dài ban đầu từ int, khi giá trị dài có thể là -1 để bắt đầu, thay vì 0xFFFFFFFF?
Jon Skeet

Xin lỗi nếu tôi không rõ. Tôi đang nói rằng nếu cả dài và int đều chứa cùng 32 thông tin và nếu bit thứ 32 được đặt, thì giá trị int khác với giá trị dài, nhưng nó thật dễ dàng để có được giá trị lâu dài.
mob

@mob cái này có liên quan đến cái gì? Mã của OP báo cáo chính xác rằng các giá trị dài> 2 ^ {31} không thể được chuyển thành ints
Một phần mây vào

0
(int) (longType + 0)

nhưng Long không thể vượt quá mức tối đa :)


1
Không + 0thêm gì vào chuyển đổi này, nó có thể hoạt động nếu Java xử lý nối kiểu số theo cách tương tự với chuỗi, nhưng vì nó không thực hiện thao tác thêm mà không có lý do.
Sixones

-7

Một giải pháp khác có thể là:

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

Tôi đã thử điều này trong trường hợp máy khách đang thực hiện POST và máy chủ DB chỉ hiểu các số nguyên trong khi máy khách có Long.


Bạn sẽ nhận được NumberFormatException về các giá trị "thực sự dài": Integer.valueOf(Long.MAX_VALUE.toString()); kết quả java.lang.NumberFormatException: For input string: "9223372036854775807" là điều đó làm xáo trộn khá nhiều Ngoại lệ vì nó được xử lý theo cách tương tự như một chuỗi chứa các chữ cái được xử lý.
Andreas

2
Nó cũng không biên dịch vì bạn không luôn trả về giá trị.
Patrick M
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.