为什么在Java中进行byte和short的除法运算会得到int类型的结果?

4
在Java中,如果我们将byteshortint相除,总是会得到一个int。如果操作数中有一个是long,则结果为long
我的问题是——为什么byteshort的除法结果不是byteshort,而总是int?我问的是Java语言中这种设计决策的技术原理,而不是“因为JLS这样规定”。
请考虑以下代码示例:
    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

1
理由并不是技术上的问题,我非常确定。 - Marko Topolnik
规则在任何类C语言中都是相同的。此外,JVM是用C和C++编写的,因此它遵循相同的规则。为什么在C语言中进行加法时会提升整数类型? - phuclv
5个回答

8

byteA除以byteB只能得到一个字节,对吗?

实际上可以得到不止一个字节:

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

明白了。那为什么不用 short 呢? - lexicore
在Java中,“not a byte”是因为它没有无符号的概念。而在其他支持无符号的语言中,即使是128,仍然只是一个字节(“char”)。 - txtechhelp
请注意,在Java中,char是一个16位的无符号类型。不错。 - Bathsheba
@lexicore 你说得对,当将Integer.MIN_VALUE除以-1时,我的示例无法正常工作(由于操作数不会提升为long,因此会导致int溢出)。 - Eran
@Eran -2147483648/-1 的结果是 -2147483648。太酷了。今天我的逻辑被打破了。 - lexicore

5
主要原因是机器通常只有为其本地整数类型(和浮点数)添加指令。这就是为什么对于许多语言,算术表达式中最不常用的类型是 "int"(通常与基本机器整数类型在某种程度上相对应的类型)。
例如,i386规范如下:
ADD 对两个操作数(DEST 和 SRC)执行整数加法。 将加法的结果分配给第一个操作数(DEST),并相应地设置标志。 当立即字节添加到字或双字操作数时,将立即值符号扩展到字或双字操作数的大小。
这意味着任何字节值在内部都会被扩展为整数(或类似)。毕竟,处理器是32/64位,然后以这些大小执行任何算术运算。如果可以在字节中进行算术运算,这通常被认为没有用处。
JVM 规范说明(对于加法)你有:iadd、ladd、fadd、dadd。这只是反映底层机器通常的行为。其他选择也可能是可能的,但可能会牺牲性能。

3

我猜这是从C语言中借鉴的,可能是通过C++实现的。

在这些语言中,如果参数类型比int更小,则始终将一个或多个参数提升为int。这发生在表达式被评估之前。通常情况下,由于生成的操作被转换为要分配的类型,而且如果没有副作用,编译器可能会优化掉所有中间步骤,因此很容易忽略这一点。

但在Java中,它并不太恶性。(在C和C ++中,它可能会让你感到困扰:两个大的unsigned short相乘可能会导致int溢出,其行为是未定义的。)

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


3
我相信这样做的理由是简单规则造成最小惊奇。结果始终是两者中更宽的类型(最小值为int),不取决于操作。
更好的方法可能是总是扩大+,*,-,除非明确(或可以隐含地)缩小。即不要溢出或下溢,除非使用转换。例如,/始终可以是double或long操作,除非进行强制转换。
但C和因此Java没有这样做。
简而言之,它有一个简单的规则来处理这个问题,无论是好是坏。
参见我的抱怨这里

1
你确定这是两个参数类型中更宽的那个吗?我的理解是,如果其中一个参数大于 int,那么情况就是这样,但是如果两个参数都是 int 或更小,则结果始终为 int。但我不是 Java 类型提升方面的专家。 - Bathsheba
至少在Java中它不会引入大量未定义行为;-) - Bathsheba
1
未定义行为。 - Bathsheba
1
在你的博客文章中:“char * char虽然被允许,但并不是一种有意义的操作”---我强烈反对这种说法。char是Java中唯一的无符号整数类型,我们都应该学会喜欢和珍惜它! - Marko Topolnik
@MarkoTopolnik char很有用,但是将其用于存储非字符可能会令人困惑。 - Peter Lawrey
显示剩余2条评论

1
无论何时你定义一个byte类型的变量,你输入的内容都应该是byte类型。这意味着它只能是byte范围内的数字(-128到127)。
然而,当你输入一个表达式,例如byteA/byteB,这与输入字面值(例如数字127)不同。
这是Java的本质——整数是用于整数的默认数据类型。
默认情况下,当你给一个表达式赋值时,Java会将其转换为默认数据类型(整数),尽管表达式的结果可能是byte的有效值。
因此,当你定义一个byte并将表达式作为该byte的值进行赋值时,Java将需要将其转换为整数:
int byteAByteB = byteA / byteB;

然而,您可以通过将分配的表达式强制转换为字节来绕过这一点,从而迫使Java将其视为字节。

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

这样做是告诉Java将其作为一个字节处理。(也可以使用short等方式完成。)

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