为啥字节和短除法会在 Java 中产生 int?

Posted

技术标签:

【中文标题】为啥字节和短除法会在 Java 中产生 int?【英文标题】:Why byte and short division results in int in Java?为什么字节和短除法会在 Java 中产生 int? 【发布时间】:2016-12-08 08:03:32 【问题描述】:

在 Java 中,如果我们将bytes、shorts 或ints 相除,我们总是得到int。如果其中一个操作数是long,我们将得到long

我的问题是 - 为什么 byteshort 除法不会导致 byteshort?为什么总是int

显然,我不是在寻找“因为 JLS 这么说”的答案,而是在询问 Java 语言中此设计决策的技术原理。

考虑这个代码示例:

    byte byteA = 127;
    byte byteB = -128;
    short shortA = 32767;
    short shortB = -32768;
    int intA = 2147483647;
    int intB = - -2147483648;
    long longA = 9223372036854775807L;
    long longB = -9223372036854775808L;


    int byteAByteB = byteA/byteB;
    int byteAShortB = byteA/shortB;
    int byteAIntB = byteA/intB;
    long byteALongB = byteA/longB;

    int shortAByteB = shortA/byteB;
    int shortAShortB = shortA/shortB;
    int shortAIntB = shortA/intB;
    long shortALongB = shortA/longB;

    int intAByteB = intA/byteB;
    int intAShortB = intA/shortB;
    int intAIntB = intA/intB;
    long intALongB = intA/longB;

    long longAByteB = longA/byteB;
    long longAShortB = longA/shortB;
    long longAIntB = longA/intB;
    long longALongB = longA/longB;

byteA 除以byteB 只能是一个字节,不是吗? 那么为什么byteAByteB 必须是int?为什么shortALongB 不能是short? 为什么intALongB 必须是long,结果总是适合int,不是吗?

更新

正如@Eran 指出的那样,(byte)-128/(byte)-1 导致128 不适合byte。但是那为什么不short呢?

更新 2

接下来,正如@Eran(再次)指出的那样,(int) -2147483648 / (int) -1 也不适合int,但结果仍然是int,而不是long

【问题讨论】:

理由不是技术性的,我很确定。 该规则在任何类 C 语言中都是相同的。此外,JVM 是用 C 和 C++ 编写的,因此它遵循相同的规则Why are integer types promoted during addition in C? 【参考方案1】:

byteA 除以 byteB 只能是一个字节,不是吗?

它可以不是字节:

byteA = -128;
byteB = -1;
int div = byteA/byteB; // == 128, not a byte

【讨论】:

点了。那为什么不short呢? not a byte .. 在 Java 中因为它没有无符号的概念 .. 128 在其他支持无符号的语言中仍然只是一个字节 (char) 请注意,char 是 Java 中的 16 位 unsigned 类型。不错。 @lexicore 你明白了,我的例子在将 Integer.MIN_VALUE 除以 -1 时不起作用(你会得到一个 int 溢出,因为操作数不会被提升为 long) . @Eran -2147483648/-1 结果为 -2147483648。凉爽的。打破了我今天的逻辑。【参考方案2】:

主要原因是机器通常只有为其本机整数类型(和浮点数)添加指令。这就是为什么对于许多语言来说,算术表达式中使用最少的类型是int(通常是在某种程度上对应于基本机器整数类型的类型)。

例如,i386 规范说:

ADD 执行两个操作数(DEST 和 SRC)的整数加法。 加法的结果分配给第一个操作数(DEST), 并相应地设置标志。当一个立即字节被添加到 一个字或双字操作数,立即值被符号扩展为 字或双字操作数的大小。

这意味着在内部任何字节值都被扩展为一个整数(或类似的)。毕竟这是合理的,因为处理器是 32/64 位,然后执行这些大小的任何算术。如果可以以字节为单位进行算术运算,这通常被认为没有用。

JVM 规范说(添加)你有:iaddladdfadddadd。这只是反映了底层机器通常表现得这样的事实。任何其他选择都是可能的,但可能是以性能下降为代价的。

【讨论】:

【参考方案3】:

我相信理由是简单的规则不会产生意外。结果总是两者中较宽的类型(最小为int),它不依赖于操作。

更好的方法可能是始终扩大+*-,除非明确(或可能隐含)缩小。即除非你使用强制转换,否则不要溢出或下溢。例如,/ 可以始终是 doublelong 操作,除非强制转换。

但是 C 和 Java 不这样做。

简而言之,它有一个简单的规则来处理这个问题,无论好坏。


在这里查看我的咆哮http://vanillajava.blogspot.co.uk/2015/02/inconsistent-operation-widen-rules-in.html

【讨论】:

你确定它是两者中较宽的类型吗?我的理解是,如果其中一个参数大于int,但如果两者都是ints 或更小,则结果始终是int。但我不是 Java 类型提升方面的专家。 至少虽然它没有在 Java 中引入大量 UB ;-) @Bathsheba UB...? 未定义的行为。 在您的博文中:char * char isn't a meaningful operation even though it is allowed---我强烈反对。 char是Java中唯一的无符号整数类型,我们都应该学会爱和珍惜!【参考方案4】:

这是从 C 中采用的东西,可能是通过 C++。

在这些语言中,一个或多个参数总是升级为int,如果它们的类型比int 更窄。这发生在 表达式被计算之前。很多时候它会被忽视,因为结果操作被转换为它被分配的类型,如果没有副作用,编译器可能会优化所有中间步骤。

虽然在 Java 中并不太有害。 (在 C 和 C++ 中,它会发现你:两个大的 unsigned shorts 相乘会溢出 int,其行为是未定义。)

请注意,如果其中一个参数大于int,则表达式的类型是参数类型中最大的。

【讨论】:

【参考方案5】:

当你定义一个字节类型的变量时,你在它对面输入的任何东西都应该是字节类型。

这意味着它只能是一个字节范围内的数字(-128 到 127)。

但是,当您输入 表达式 时,例如。 byteA/byteB 与输入 literal 不同,例如。数字 127

这是 Java 的本质 - 整数 是用于整数的默认数据类型。

默认情况下,当您对 表达式 进行赋值时,Java 会将其转换为默认数据类型(整数),尽管表达式的结果可能是字节。

所以发生的情况是,当您定义一个字节并将表达式分配为该字节的值时,Java 需要将其转换为整数:

int byteAByteB = byteA / byteB;

但是,您可以通过强制转换分配的表达式来解决这个问题,从而使 Java 将其视为一个字节。

byte byteAByteB = (byte) (byteA / byteB);

这样你就告诉 Java 将其视为一个字节。 (也可以用short等来做)

【讨论】:

以上是关于为啥字节和短除法会在 Java 中产生 int?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 SQLite 替换功能会在我的 Android 应用程序中产生错误?

java做除法运算,为啥除不开时也会得到整数呢

Java - 为啥 char 不应该被隐式转换为字节(和短)原语?

向单元格动态添加视图会在表格中产生问题

遍历对象属性会在不同的浏览器中产生不同的结果[重复]

Java中的字节和短点(我已经阅读了其他问题)