การร่ายอย่างปลอดภัยเป็นเวลานานถึงจาวาใน Java


489

อะไรคือวิธีที่ใช้สำนวนที่สุดใน Java ในการตรวจสอบว่าการส่งจากlongไปยังintจะไม่สูญเสียข้อมูลใด ๆ

นี่คือการใช้งานปัจจุบันของฉัน:

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
สองเส้นทางรหัส หนึ่งคือมรดกและความต้องการ ints ข้อมูลดั้งเดิมนั้นควรอยู่ใน int แต่ฉันต้องการทิ้งข้อยกเว้นหากการสันนิษฐานนั้นถูกละเมิด อีกเส้นทางรหัสจะใช้เวลานานและไม่จำเป็นต้องส่ง
Brigham

197
ฉันชอบที่ผู้คนมักถามว่าทำไมคุณถึงอยากทำในสิ่งที่คุณอยากทำ หากทุกคนอธิบายกรณีการใช้งานอย่างสมบูรณ์ในคำถามเหล่านี้จะไม่มีใครสามารถอ่านได้ตอบคำถามน้อยกว่านี้
BT

24
BT - ฉันเกลียดการถามคำถามออนไลน์ด้วยเหตุผลนี้ หากคุณต้องการความช่วยเหลือนั่นยอดเยี่ยม แต่อย่าเล่น 20 คำถามและบังคับให้พวกเขาพิสูจน์ตัวเอง
Mason240

59
ไม่เห็นด้วยกับ BT และ Mason240 ที่นี่: บ่อยครั้งที่มีค่าในการแสดงให้ผู้ถามถึงวิธีการแก้ปัญหาอื่นที่พวกเขาไม่ได้คิด การตั้งค่าสถานะกลิ่นรหัสเป็นบริการที่มีประโยชน์ มันไกลจาก 'ฉันอยากรู้ว่าทำไม ... ' ถึง 'บังคับให้พวกเขาพิสูจน์ตัวเอง'
Tommy Herbert

13
มีหลายสิ่งที่คุณทำไม่ได้กับความยาวเช่นทำดัชนีอาร์เรย์
skot

คำตอบ:


580

มีการเพิ่มวิธีการใหม่ด้วยJava 8เพื่อทำเช่นนั้น

import static java.lang.Math.toIntExact;

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

จะโยนArithmeticExceptionในกรณีที่มีการล้น

ดู: Math.toIntExact(long)

อื่น ๆ อีกหลายล้นวิธีการที่ปลอดภัยได้รับการเพิ่ม Java 8. พวกเขาจบลงด้วยการที่แน่นอน

ตัวอย่าง:

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

5
เรายังมีและaddExact multiplyExactสิ่งที่ควรทราบคือแผนก ( MIN_VALUE/-1) และค่าสัมบูรณ์ ( abs(MIN_VALUE)) ไม่มีวิธีอำนวยความสะดวกที่ปลอดภัย
Aleksandr Dubinsky

แต่ความแตกต่างของการใช้งานMath.toIntExact()แทนที่จะใช้การส่งแบบปกติintคืออะไร การดำเนินการMath.toIntExact()ปลดเปลื้องเพียงเพื่อlong int
Yamashiro Rion

@YamashiroRion ที่จริงแล้วการติดตั้ง toIntExact ก่อนอื่นจะตรวจสอบว่าตัวละครจะนำไปสู่การโอเวอร์โฟลว์หรือไม่ในกรณีที่มันโยน ArithmeticException เฉพาะในกรณีที่นักแสดงมีความปลอดภัยก็จะทำการโยนจากระยะยาวไปยัง int ซึ่งจะส่งกลับ กล่าวอีกนัยหนึ่งถ้าคุณพยายามที่จะโยนจำนวนยาวที่ไม่สามารถแสดงเป็น int (เช่นหมายเลขใด ๆ ที่สูงกว่า 2 147 483 647 อย่างเคร่งครัด) มันจะโยน ArithmeticException หากคุณทำแบบเดียวกันกับนักแสดงธรรมดาค่า int ที่ได้ของคุณจะผิด
Pierre-Antoine

306

ฉันคิดว่าฉันทำได้เหมือน:

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

ฉันคิดว่ามันเป็นการแสดงออกถึงเจตนาที่ชัดเจนยิ่งกว่าการคัดเลือกนักแสดงซ้ำ ... แต่มันค่อนข้างเป็นอัตวิสัย

หมายเหตุที่น่าสนใจ - ใน C # มันจะเป็น:

return checked ((int) l);

7
(!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE))ฉันมักจะทำตรวจสอบช่วงเป็น ฉันพบว่ามันยากที่จะทำให้ฉันคิดวิธีอื่น ๆ สงสาร Java unlessไม่ได้
Tom Hawtin - tackline

5
+1 สิ่งนี้อยู่ภายใต้กฎ "ข้อยกเว้นควรใช้สำหรับเงื่อนไขพิเศษ "
Adam Rosenfield

4
(ในภาษาวัตถุประสงค์ทั่วไปที่ทันสมัยจะเป็น: "ใช่มั้ย แต่ ints มีขนาดโดยพลการ?")
Tom Hawtin - tackline

7
@ ทอม: การตั้งค่าส่วนตัวฉันเดา - ฉันชอบที่จะมีเชิงลบน้อยที่สุด ถ้าฉันมองหาที่ "ถ้า" กับร่างกายซึ่งพ่นยกเว้นผมต้องการที่จะเห็นเงื่อนไขที่ทำให้มันมีลักษณะพิเศษ - เหมือนเป็นค่า "ปิดท้าย" intของ
Jon Skeet

6
@ ทอม: ในกรณีนี้ฉันจะลบเชิงลบให้นำแสดงโดย / คืนภายใน "ถ้า" ร่างกายแล้วโยนข้อยกเว้นหลังจากนั้นถ้าคุณเห็นสิ่งที่ฉันหมายถึง
Jon Skeet

132

ด้วยคลาสIntsของ Google Guava วิธีการของคุณสามารถเปลี่ยนเป็น:

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

จากเอกสารที่เชื่อมโยง:

checkedCast

public static int checkedCast(long value)

ส่งคืนค่า int ที่เท่ากับvalueถ้าเป็นไปได้

พารามิเตอร์: value - ค่าใด ๆ ในช่วงของintประเภท

ผลตอบแทน:intค่าที่เท่าเทียมกันvalue

พ่น: IllegalArgumentException - ถ้าvalueมากกว่าInteger.MAX_VALUEหรือน้อยกว่าInteger.MIN_VALUE

อนึ่งคุณไม่จำเป็นต้องใช้safeLongToIntเครื่องห่อหุ้มเว้นเสียแต่ว่าคุณต้องการทิ้งไว้เพื่อเปลี่ยนฟังก์ชันการทำงานโดยไม่ต้องทำการเปลี่ยนโครงสร้างใหม่อย่างกว้างขวาง


3
Guava's Ints.checkedCastทำสิ่งที่ OP ทำโดยบังเอิญ
เมฆบางส่วน

14
+1 สำหรับโซลูชัน Guava แม้ว่าจะไม่จำเป็นต้องห่อไว้ด้วยวิธีอื่นเพียงโทรหาInts.checkedCast(l)โดยตรง
dimo414

8
ฝรั่งยังมีInts.saturatedCastที่จะคืนค่าที่ใกล้ที่สุดแทนการโยนข้อยกเว้น
Jake Walsh

ใช่มันปลอดภัยที่จะใช้ api ที่มีอยู่เป็นกรณีของฉันไลบรารีอยู่ในโครงการแล้ว: เพื่อโยนข้อยกเว้นถ้าไม่ถูกต้อง: Ints.checkedCast (ยาว) และ Ints.saturatedCast (ยาว) เพื่อให้ได้แปลงที่ใกล้ที่สุดเป็น int
Osify

29

ด้วย BigDecimal:

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

ฉันชอบอันนี้ทุกคนมีบางสิ่งบางอย่างกับโซลูชั่นนี้?
Rui Marques

12
ก็คือการจัดสรรและทิ้ง BigDecimal เพียงเพื่อให้ได้สิ่งที่ควรเป็นวิธีการใช้ประโยชน์ดังนั้นใช่นั่นไม่ใช่กระบวนการที่ดีที่สุด
Riking

@Riking ในเรื่องนั้นดีกว่าที่จะใช้BigDecimal.valueOf(aLong)แทนnew BigDecimal(aLong)เพื่อแสดงว่าไม่จำเป็นต้องมีอินสแตนซ์ใหม่ ไม่ว่าสภาพแวดล้อมในการดำเนินการจะทำการแคชที่วิธีการนั้นหรือไม่นั้นเป็นการใช้งานที่เฉพาะเจาะจงเช่นเดียวกับการปรากฏตัวที่เป็นไปได้ของ Escape Analysis ในกรณีที่เกิดขึ้นจริงส่วนใหญ่จะไม่มีผลกระทบต่อประสิทธิภาพการทำงาน
Holger

17

นี่คือทางออกในกรณีที่คุณไม่สนใจคุณค่าในกรณีที่มันมีขนาดใหญ่กว่านั้นจำเป็นต้องใช้;)

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

ดูเหมือนว่าคุณผิด ... มันจะทำงานได้ดีและเป็นลบ ยังหมายความว่าtoo lowอย่างไร โปรดระบุกรณีใช้
Vitaliy Kulikov

วิธีแก้ปัญหานี้เป็นวิธีที่รวดเร็วและปลอดภัยจากนั้นเรากำลังพูดถึงการลองใช้ Long to Int เพื่อเชื่อฟังผลการค้นหา
Vitaliy Kulikov

11

อย่า: นี่ไม่ใช่ทางออก!

วิธีแรกของฉันคือ:

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

แต่นั่นเป็นเพียงการใช้เวลานานถึง int อาจสร้างLongอินสแตนซ์ใหม่หรือดึงข้อมูลจาก Long pool


ข้อเสียเปรียบ

  1. Long.valueOfสร้างLongอินสแตนซ์ใหม่ถ้าตัวเลขไม่อยู่ในLongช่วงพูลของ [-128, 127]

  2. การintValueใช้งานไม่ได้ทำอะไรมากไปกว่า:

    return (int)value;

ดังนั้นนี้ถือได้ว่าเลวร้ายยิ่งกว่าเพียงแค่หล่อไปlongint


4
ความพยายามในการช่วยเหลือของคุณนั้นได้รับการชื่นชม แต่การยกตัวอย่างบางอย่างที่ไม่ได้ผลนั้นไม่เหมือนกับการให้โซลูชันที่ใช้งานได้ หากคุณต้องการแก้ไขเพื่อเพิ่มวิธีที่ถูกต้องนี่อาจเป็นสิ่งที่ดีทีเดียว มิฉะนั้นจะไม่เหมาะสมที่จะโพสต์เป็นคำตอบ
ปรากฏ

4
โอเคทำไมไม่มีทั้ง DO และ DOT ล่ะ? ขอบคุณบางครั้งฉันหวังว่าฉันจะมีรายการของวิธีการทำสิ่งต่าง ๆ (DONTs) เพื่อตรวจสอบว่าฉันใช้รูปแบบ / รหัสดังกล่าว อย่างไรก็ตามฉันสามารถลบ "คำตอบ" นี้ได้
Andreas

1
ต่อต้าน - รูปแบบที่ดี อย่างไรก็ตามมันจะดีมากถ้าคุณอธิบายว่าจะเกิดอะไรขึ้นถ้าค่ายาวอยู่นอกช่วงสำหรับ int? ฉันเดาว่าจะมี ClassCastException หรืออะไรแบบนี้
Peter Wippermann

2
@PeterWippermann: ฉันได้เพิ่มข้อมูลเพิ่มเติม คุณพิจารณาพวกเขาด้วยความเคารพหรือไม่ อธิบายได้เพียงพอหรือไม่
Andreas

7

ฉันอ้างว่าวิธีที่ชัดเจนในการดูว่าการเปลี่ยนค่าเปลี่ยนค่าจะเป็นการส่งและตรวจสอบผลลัพธ์หรือไม่ อย่างไรก็ตามฉันจะลบการร่ายที่ไม่จำเป็นเมื่อเปรียบเทียบ ฉันยังไม่กระตือรือร้นเกินไปในชื่อตัวแปรตัวอักษรหนึ่งตัว (ยกเว้นxและyแต่ไม่ใช่เมื่อพวกเขาหมายถึงแถวและคอลัมน์ (บางครั้งตามลำดับ))

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

อย่างไรก็ตามจริง ๆ แล้วฉันต้องการหลีกเลี่ยงการแปลงนี้ถ้าเป็นไปได้ทั้งหมด เห็นได้ชัดว่าบางครั้งมันเป็นไปไม่ได้ แต่ในกรณีเหล่านั้นIllegalArgumentExceptionเกือบจะแน่นอนว่าเป็นข้อยกเว้นที่ผิดที่จะโยนเท่าที่ลูกค้ารหัสที่เกี่ยวข้อง


1
นี่คือสิ่งที่ Google Guava Ints รุ่นล่าสุด: checkedCast ทำ
lexicalscope

2

ประเภทจำนวนเต็ม Java แสดงเป็นเซ็น ด้วยอินพุตระหว่าง 2 31และ 2 32 (หรือ -2 31และ -2 32 ) นักแสดงจะสำเร็จ แต่การทดสอบของคุณจะล้มเหลว

สิ่งที่ต้องตรวจสอบคือว่าบิตสูงlongทั้งหมดนั้นเหมือนกันทั้งหมดหรือไม่:

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
ฉันไม่เห็นว่าการลงนามเกี่ยวข้องกับอะไร คุณช่วยยกตัวอย่างที่ไม่ทำให้ข้อมูลสูญหาย แต่ไม่ผ่านการทดสอบ? 2 ^ 31 จะถูกส่งไปยัง Integer.MIN_VALUE (เช่น -2 ^ 31) ดังนั้นข้อมูลจึงสูญหาย
Jon Skeet

@ จอน Skeet: บางทีฉันและ OP กำลังคุยกันอยู่ (int) 0xFFFFFFFFและ(long) 0xFFFFFFFFLมีค่าแตกต่างกัน แต่ทั้งคู่มี "ข้อมูล" เดียวกันและเกือบจะไม่สำคัญที่จะดึงค่ายาวดั้งเดิมจาก int
mob

คุณสามารถดึงค่า long ดั้งเดิมจาก int ได้อย่างไรเมื่อ long อาจมีค่า -1 เพื่อเริ่มต้นด้วยแทนที่จะเป็น 0xFFFFFFFF
Jon Skeet

ขออภัยถ้าฉันยังไม่ชัดเจนฉันกำลังบอกว่าถ้า long และ int ทั้งคู่มีข้อมูล 32 บิตเหมือนกันและหากตั้ง 32 บิตไว้ค่า int จะแตกต่างจากค่า long แต่ก็เป็น เป็นเรื่องง่ายที่จะได้รับค่ายาว
ม็อบ

@mob นี่คืออะไรในการอ้างอิงถึง? รหัสของ OP รายงานอย่างถูกต้องว่าค่ายาว> 2 ^ {31} ไม่สามารถร่ายไปยัง ints ได้
เมฆบางส่วน

0
(int) (longType + 0)

แต่ความยาวต้องไม่เกินค่าสูงสุด :)


1
+ 0เพิ่มอะไรที่จะแปลงนี้ก็อาจจะทำงานหากได้รับการรักษา Java ชนิดเรียงต่อกันเป็นตัวเลขในทำนองเดียวกันกับสตริง แต่เพราะมันไม่ได้ทำของคุณการดำเนินการเพิ่มโดยไม่มีเหตุผล
sixones

-7

อีกวิธีหนึ่งสามารถ:

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

ฉันได้ลองทำสิ่งนี้แล้วในกรณีที่ลูกค้ากำลังทำ POST และฐานข้อมูลเซิร์ฟเวอร์เข้าใจเฉพาะจำนวนเต็มในขณะที่ลูกค้ามี Long


คุณจะได้รับ NumberFormatException จากค่า "real long": Integer.valueOf(Long.MAX_VALUE.toString()); ผลลัพธ์java.lang.NumberFormatException: For input string: "9223372036854775807" จะทำให้ obfuscates Out-Of-Range-Exception ค่อนข้างมากเพราะตอนนี้ได้รับการจัดการในลักษณะเดียวกันกับสตริงที่มีตัวอักษรได้รับการปฏิบัติ
Andreas

2
นอกจากนี้ยังไม่ได้รวบรวมเพราะคุณไม่ได้คืนค่าเสมอ
Patrick M
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.