为什么Java中短整数相除的结果类型不是短整数?

14
请提供需要翻译的内容。
public class ShortDivision {
    public static void main(String[] args) {
        short i = 2;
        short j = 1;
        short k = i/j;
    }
}

编译这个代码会产生错误

ShortDivision.java:5: possible loss of precision
found   : int
required: short
        short k = i/j;

因为表达式i/j的类型明显为int,因此必须强制转换为short。


1
因为将int转换为short可能会导致精度丢失。 (-32768 / -1) - Mark Byers
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
21

根据Java规范:

5.6.2 二进制数值提升(Binary Numeric Promotion)

当运算符对一对操作数进行二进制数值提升时,每个操作数必须表示为数值类型的值,并按照以下规则依次使用扩宽转换(§5.1.2)进行必要的操作数转换:

如果任何一个操作数的类型为double,则另一个操作数将被转换为double类型。

否则,如果任何一个操作数的类型为float,则另一个操作数将被转换为float类型。

否则,如果任何一个操作数的类型为long,则另一个操作数将被转换为long类型。

否则,两个操作数都将被转换为int类型。

对于二进制操作,小整数类型被提升为 int 类型,操作结果也是 int 类型。


编辑:为什么会这样呢?简答来说,Java从C语言中借鉴了这种行为。较长的答案可能与现代计算机都能进行至少32位本地计算有关,实际上一些机器执行8位和16位操作可能会更困难。

另请参见:在C#中进行字节或运算得到int类型


进一步说明:二进制数值的提升是在某些运算符的操作数上执行的:
  • 乘法运算符*,除法运算符/和取模运算符%。
- akf
好的,我理解语言规范说语言应该以这种方式行事(否则编译器首先不会生成错误)。但是我不理解背后的动机。为什么他们在做除法之前要提升类型而不是直接除以短片? - flodin
我的赌注是“这就是C语言的方式”。Java的设计目标是吸引C++程序员——这就要求事情保持一致。 - Thorbjørn Ravn Andersen
谢谢提供的链接,看起来那是个足够合理的解释。 - flodin

2

关于动机:让我们想象一下这种行为的替代方案,看看它们为什么不起作用:

替代方案1:结果应始终与输入相同。

对于将int和short相加,结果应该是什么?

对于两个short的乘积,结果通常适合一个int,但因为我们截断为short,大多数乘法会悄无声息地失败。之后强制转换为int也无济于事。

替代方案2:结果应始终为能表示所有可能输出的最小类型。

如果返回类型是short,则答案并非总能表示为short。

short可以容纳-32,768到32,767的值。那么这个结果会导致溢出:

short result = -32768 / -1; // 32768: not a short
所以你的问题是:为什么将两个 int 相加不会返回 long?那么两个 int 相乘应该是什么呢?是 long 吗?还是 BigNumber 来覆盖整数最小值平方的情况? 另一种选择是:选择大多数人在大多数时间内可能最想要的东西。 因此,结果应该是: - 对于两个 short 相乘或任何 int 操作,应返回 int。 - 如果是对 short 进行加减、将 short 除以任何整数类型或两个 byte 相乘,则应返回 short。 - 如果是将 byte 向右移位,则应返回 byte,如果是向左移位,则应返回 int。 - 等等... 如果没有基本逻辑,记住所有特殊情况会很困难。更简单的方法是:整数运算的结果始终是 int。

人们想要的并不总是重新设计后的好主意。一个很好的例子就是QWERTY键盘排列。最初设计是为了“减慢”打字员的速度,以免打字机卡住。但是,我已经习惯了它,无法想象使用其他任何东西。 - Peter Lawrey
@Peter Lawrey:没错。如果亨利·福特问人们他应该制造什么样的汽车,许多人会要求一辆有6匹马而不是4匹马的马车。倾听用户的需求是好事,但有时你需要给他们一些与他们想要的不同的东西,因为他们甚至无法想象一个与他们习惯的解决方案不同的解决方案。 - Mark Byers
对于没有运算符重载的语言,为什么不将结果设为(最大可能结果)和(接收容器大小)中的较小者呢?在大多数情况下,这样的规则会产生与使用方法#1得到算术正确结果的任何表达式一样小或更小的代码。例如,如果变量是32位的,则 p =(x * y)/ z; 将执行一个32x32->64乘法和一个64/32->32除法。如果 pz 是16位的,则它将执行32x32->64乘法,64->32 溢出检查减少,以及32/16 -> 16除法。 - supercat

0

这只是一种设计选择,以保持与C/C++的一致性,当Java被设计时,它们是主导语言。

例如,i * j可以实现为类型从byte => short、short => int和int => long进行提升,这将避免溢出,但它并没有这样做。(在某些语言中确实如此)如果需要当前行为,则可以使用强制转换,但会丢失一些位。

同样,i / j可以从byte/short => float或int/long => double进行提示。


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