在Java中安全地将长整型转换为整型

545

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

11
我很好奇为什么你需要这样做。如果你有一个“long”类型的变量,为什么不直接使用它呢?这样做,你就永远不必担心这个问题了。 - Thomas Owens
36
两条代码路径。其中一条是传统的需要整数类型(ints)的路径。这个传统数据应该都适合使用整数类型,但如果这种假设被违反了,我想要抛出一个异常。另一条代码路径将使用长整型(longs),并且不需要转换类型。 - Brigham
15
使用长整型数据类型,有很多事情是无法实现的,例如无法对数组进行索引操作。 - skot
8
安全地将长整型转换为整型怎么可能成为代码异味呢? - Pacerier
1
@Pacerier:一般来说,如果你需要的是int类型,就不应该首先使用long类型,或者如果你需要的是long类型,就不应该将其向下转换为int类型。(记住,“代码气味”并不意味着“糟糕的代码”,它意味着“表明可能存在问题的代码”。) - ruakh
通常避免假设破碎的转换会如何工作。当然,C编译器可能会出现奇怪的情况。最好针对最大整数和最小整数进行测试,或者屏蔽掉底部32位。不检查整数溢出是Java从C继承的愚蠢行为。使用硬件陷阱不会产生任何成本。像Visual Basic这样更复杂的语言默认会进行检查,在某些情况下这拯救了我。 - Tuntable
10个回答

651

Java 8中新增了一种方法:

import static java.lang.Math.toIntExact;

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

在溢出的情况下会抛出一个 ArithmeticException 异常。

参见:Math.toIntExact(long)

Java 8 中添加了几个处理溢出安全的方法,它们以 exact 结尾。

示例:

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

7
жҲ‘们иҝҳжңү addExact е’Ң multiplyExact ж–№жі•гҖӮйңҖиҰҒжіЁж„Ҹзҡ„жҳҜпјҢйҷӨжі•ж“ҚдҪңпјҲдҫӢеҰӮ MIN_VALUE/-1пјүе’Ңз»қеҜ№еҖјж“ҚдҪңпјҲдҫӢеҰӮ abs(MIN_VALUE)пјүжІЎжңүе®үе…Ёзҡ„дҫҝжҚ·ж–№жі•гҖӮ - Aleksandr Dubinsky
但是使用Math.toIntExact()与通常的强制转换为int有什么区别呢?Math.toIntExact()的实现只是将long强制转换为int - Yamashiro Rion
3
@YamashiroRion 实际上,toIntExact的实现首先检查强制转换是否会导致溢出,如果是,则抛出ArithmeticException。只有在强制转换是安全的情况下,它才执行从long到int的强制转换,并返回结果。 换句话说,如果您尝试将无法表示为int的长数字(例如任何严格大于2 147 483 647的数字)进行强制转换,它将抛出ArithmeticException。如果您使用简单的强制转换进行相同操作,则得到的int值将是错误的。 - Pierre-Antoine

304

我认为我会简单地这样做:

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
这正好符合“例外情况应该使用例外规则”的原则。 - Adam Rosenfield
4
在现代通用编程语言中,它可能会被表达为:“啊?但整数具有任意大小?” - Tom Hawtin - tackline
7
@Tom:我想这是个人偏好吧,我更喜欢尽可能少出现负面情况。如果我看到一个带有抛出异常的主体的“if”语句,我希望看到的条件能够使其看起来异常 - 就像值“低于”int的底部端点一样。 - Jon Skeet
6
@Tom:在这种情况下,我会删除负号,将类型转换/返回放在“if”语句体内,然后在之后抛出异常,如果你明白我的意思的话。 - Jon Skeet
显示剩余2条评论

136

通过使用Google Guava的Ints类,您的方法可以更改为:

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

从链接的文档中可以得知:

checkedCast

public static int checkedCast(long value)

返回等于valueint 值,如果可能。

参数: value - int 类型范围内的任何值

返回值: 等于 valueint

抛出异常: IllegalArgumentException - 如果 value 大于 Integer.MAX_VALUE 或小于 Integer.MIN_VALUE

顺便说一下,你不需要使用 safeLongToInt 包装器,当然,除非你想保留它,以便在更改功能时不需要进行大量重构。


3
Guava的Ints.checkedCast恰好做了OP所做的事情,顺便提一句。 - Partly Cloudy
15
赞同使用Guava解决方案,不过没有必要将其包装在另一个方法中,直接调用Ints.checkedCast(l)即可。 - dimo414
8
Guava还有Ints.saturatedCast,它会返回最接近的值而不是抛出异常。 - Jake Walsh
是的,作为我的情况,使用现有的API是安全的,库已经在项目中:使用Ints.checkedCast(long)和Ints.saturatedCast(long)来抛出异常以获取将long转换为int的最近值。 - Osify

29

使用 BigDecimal:

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

13
这段话的意思是:这个过程需要分配和丢弃一个 BigDecimal 对象,仅仅为了得到一个本应该作为工具方法提供的功能,所以这不是最佳的处理方式。 - Riking
在这方面,最好使用 BigDecimal.valueOf(aLong) 而不是 new BigDecimal(aLong),以表示不需要创建新实例。方法的执行环境是否缓存取决于实现,就像可能存在逃逸分析一样。在大多数实际情况下,这对性能没有影响。 - Holger

17

这里有一个解决方案,如果你不在意值是否比需要的大的话;)

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

似乎你错了...它会在负数时正常工作。另外,“太低”是什么意思?请提供使用案例。 - Vitaliy Kulikov
这个解决方案既快又安全,我们可以将Long转换为Int以遵守结果。 - Vitaliy Kulikov

11

DONT: 这不是一个解决方案!

我的第一个尝试是:

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

但这只是将long强制转换为int,可能会创建新的Long实例或从Long pool中检索它们。


缺点

  1. 如果数字不在Long池范围[-128,127]内,则Long.valueOf会创建一个新的Long实例。

  2. intValue的实现仅仅是:

    return (int)value;
    
    因此,这甚至可以被认为比将 long 强制转换为 int 更糟。

4
感谢你的帮助,但是举一个不行的例子并不等同于提供一个可行的解决方案。如果您能编辑并添加一个正确的方法,那就非常好;否则,这篇回答就不太适合发布了。 - Pops
4
为什么不同时提供“要做”和“不要做”的内容呢?说实话,有时候我希望有一份“如何不做某些事情”(DONTs)的清单来检查我是否使用了这样的模式/代码。无论如何,我可以删除这个“回答”。 - Andreas
1
好的反模式。不过,如果长值超出int范围,你能解释一下会发生什么吗?我猜会有一个ClassCastException或类似的东西? - Peter Wippermann
2
@PeterWippermann:我添加了更多信息。您认为它们足够易懂或解释清楚吗? - Andreas

7

我认为判断值是否被强制转换更改的显而易见的方法是进行转换并检查结果。然而,在比较时,我会删除不必要的类型转换。我也不太喜欢单个字母的变量名(例外是xy,但当它们分别表示行和列时除外)。

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整数类型表示为有符号。如果输入介于231和232之间(或-231和-232之间),则强制转换将成功,但测试将失败。

需要检查的是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
@Jon Skeet:也许我和OP之间存在误解。(int) 0xFFFFFFFF(long) 0xFFFFFFFFL 具有不同的值,但它们都包含相同的“信息”,从 int 中提取原始 long 值几乎是微不足道的。 - mob
当 long 值一开始不是 0xFFFFFFFF 而是 -1 时,你如何从 int 中提取原始的 long 值? - Jon Skeet
抱歉如果我表达不清楚。我的意思是,如果 long 和 int 都包含相同的 32 位信息,并且第 32 位被设置,那么 int 值与 long 值不同,但很容易获取 long 值。 - mob
@mob 这是什么相关的?OP的代码正确地报告了长整型值>2^{31}不能转换为整型。 - Partly Cloudy
在Java中,任何原始类型都可以转换为int。 - mob

0
(int) (longType + 0)

但是 Long 类型不能超过最大值 :)


1
“+ 0”对此转换没有任何作用,如果Java以类似于字符串的方式处理数字类型串联可能会起作用,但由于它并不是这样做的,因此您正在进行无效的加法操作。 - sixones

-7
另一个解决方案可以是:
public int longToInt(Long longVariable)
{
    try { 
            return Integer.valueOf(longVariable.toString()); 
        } catch(IllegalArgumentException e) { 
               Log.e(e.printstackstrace()); 
        }
}

我尝试过在客户端进行POST请求,但是服务器数据库只接受整数而客户端使用了长整型。


当使用“real long”值时,您将遇到NumberFormatException: Integer.valueOf(Long.MAX_VALUE.toString()); 结果为java.lang.NumberFormatException: For input string: "9223372036854775807",这几乎混淆了超出范围异常,因为它现在被视为包含字母的字符串一样处理。 - Andreas
2
也不能编译,因为您并不总是返回一个值。 - Patrick M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接