Java API为什么使用int而不是short或byte?

141
为什么Java API使用int,而不是仅使用short或甚至byte
例如,在Calendar类中,DAY_OF_WEEK字段使用int
如果差异太小,那么这些数据类型(shortint)为什么还存在?
7个回答

173
一些原因已经被指出,例如"...(几乎)所有的byte, short操作都会将这些基本类型提升为int"。然而,显而易见的下一个问题是:为什么这些类型会被提升为int
所以更深层次的答案可能与Java虚拟机指令集有关。正如在Java虚拟机规范中的表格中总结的那样,所有的整数算术运算,如加法、除法和其他运算,仅适用于int类型和long类型,而不适用于较小的类型。
(旁注:较小的类型(byteshort)基本上只用于数组。像new byte[1000]这样的数组将占用1000字节,而像new int[1000]这样的数组将占用4000字节)

当然,人们可能会问:"...显然下一个问题是:为什么这些指令只提供给int(和long)?"

其中一个原因在上面提到的JVM规范中提到:

如果每个类型的指令都支持Java虚拟机的所有运行时数据类型,则会有比一个字节表示更多的指令

此外,Java虚拟机可以被视为真实处理器的抽象。为较小类型引入专用算术逻辑单元将不值得花费精力:它需要额外的晶体管,但仍然只能在一个时钟周期内执行一次加法。当JVM设计时,占主导地位的架构是32位,非常适合32位int。(涉及64位long值的操作作为特殊情况实现)。(注:最后一段有点过于简化,考虑到可能的向量化等内容,但应该在不深入处理器设计主题的情况下提供基本思路)

编辑:一个简短的补充,侧重于问题中的示例,但从更一般的意义上来说:人们还可以问是否存储小型类型的字段会有益处。例如,人们可能认为通过将Calendar.DAY_OF_WEEK存储为byte可以节省内存。但是,在这里,Java类文件格式发挥作用:所有类文件中的字段都占用至少一个“插槽”,其大小为一个int(32位)。 (“宽”字段,doublelong,占用两个插槽)。因此,显式声明字段为shortbyte也不会节省任何内存。


我猜测操作数被提升为int的逻辑也与C和C ++中使用的原理有关。 - Shafik Yaghmour
@Marco13 “因此,明确将字段声明为short或byte也不会节省任何内存。”这是真的吗?我认为这不正确。 - ACV
1
严格来说,一个实现可能选择存储更紧凑的形式,但是"虚拟"暴露的格式(即通过虚拟机)将会把这些值视为至少具有int的大小。如果您有其他实现的参考,请更新答案并相应地插入链接。 - Marco13

43

(几乎) 所有关于 byte, short 的操作都会将它们提升为 int 类型,例如,你不能这样写:

short x = 1;
short y = 2;

short z = x + y; //error

当使用int时,算术运算更加容易和直接,不需要进行强制转换。

在空间方面,这只会有非常小的影响。 byteshort会使事情变得复杂,我不认为这种微小优化值得,因为我们正在讨论固定数量的变量。

byte在编写嵌入式设备程序或处理文件/网络时是相关和有用的。此外,这些基元是有限的,如果将来计算可能超出它们的限制怎么办?尝试考虑一下Calendar类的扩展,它可能演变成更大的数字。

还要注意,在64位处理器中,局部变量将保存在寄存器中,并且不会使用任何资源,因此使用intshort和其他基元根本没有任何区别。此外,许多Java实现对齐变量*(和对象)。


*如果它们是局部变量、变量或甚至实例变量,byteshort占用与int相同的空间。为什么?因为在(大多数)计算机系统中,变量地址是对齐的,因此例如如果使用单个字节,则实际上会得到两个字节-一个用于变量本身,另一个用于填充。

另一方面,在数组中,byte占用1个字节,short占用2个字节,int占用4个字节,因为在数组中只需要对开头和可能的结尾进行对齐。这将在您想要使用例如System.arraycopy()时有所不同,然后您真的会注意到性能差异。


3
有趣的事实:如果你对这两个值都使用final修饰符,它就能正常工作。 :) - alexander
@alexander 为什么? - elect
4
在这种情况下,编译器可以确定它们的和是一个有效的“short”类型。 - Maroun

9

使用整数进行算术运算比使用短整型更容易。假设常量确实是由short值建模的。那么您需要以以下方式使用API:

short month = Calendar.JUNE;
month = month + (short) 1; // is july

请注意显式转换。当 short 值用于算术运算时,它们会被隐式地提升为 int 值。(在操作数栈上,shorts 甚至表示为 ints。) 这样使用将非常麻烦,因此常量通常更喜欢使用 int 值。
相比之下,存储效率的提高是微不足道的,因为这种常量只有一个固定的数量。我们谈论的是 40 个常量。将它们的存储从 int 更改为 short 将可以节省你 40 * 16 位 = 80 字节。可以参考此答案了解更多信息。

5
如果您采用把整数常量存储在它们适合的最小类型中的哲学,那么Java将会面临一个严重的问题:每当程序员编写使用整数常量的代码时,他们必须仔细检查常量的类型是否重要,如果重要,则查找文档中的类型并/或执行所需的类型转换。
既然我们已经概述了一个严重的问题,那么采用这种哲学可以带来什么好处呢?如果这种改变的唯一运行时可观察效果是通过反射查看常量时得到的类型(当然,还有由于懒惰/不慎重的程序员未正确考虑常量类型而引入的错误),那我不会感到意外。
权衡利弊非常容易:这是一种糟糕的哲学。

5
虚拟机的设计复杂度取决于它能够执行多少种操作。对于诸如“乘法”这样的指令,拥有四个实现版本——分别为32位整数、64位整数、32位浮点数和64位浮点数——比将其加上小型数字类型的版本更容易。更有趣的设计问题是为什么应该有四种类型,而不是更少(使用64位整数执行所有整数计算和/或使用64位浮点数执行所有浮点数计算)。使用32位整数的原因是Java预计在许多平台上运行,其中32位类型的操作与16位或8位类型一样快,但对64位类型的操作会明显变慢。即使在16位类型更快的平台上,使用32位量的额外成本也会因仅使用32位类型而带来的简便性而抵消。至于在32位值上执行浮点运算的优势则不太明显。有些平台可以通过将所有操作数转换为高精度类型、相加,然后将结果转换回32位浮点数以进行存储来最快地执行计算,而另一些平台则使用32位浮点值执行所有计算更有效。Java的创始人们决定要求所有平台都按照相同的方式进行操作,并且他们应该倾向于那些32位浮点计算更快的硬件平台,即使这严重降低了典型PC和许多没有浮点单元的机器上的浮点数学的速度和精度。请注意,根据b、c和d的值,当计算类似于上述的float a=b+c+d;表达式时,使用高精度中间计算有时会产生显着更准确的结果,而如果所有中间操作数都以float精度计算,则有时会得到略微不太准确的值。无论如何,Sun决定一切都应该以相同的方式完成,并选择使用最小精度的float值。
注意,小数据类型的主要优点在于大量存储在数组中时才显现出来;即使没有比64位小的单个变量的优势,也值得拥有可以更紧凑地存储较小值的数组;将本地变量设置为byte而不是long可以节省7个字节;将1,000,000个数字的数组中的每个数字都保存为byte而不是long可以节省7,000,000个字节。由于每种数组类型只需要支持一些操作(最重要的是读取一个项目、存储一个项目、在数组中复制一系列项目或从一个数组中复制一系列项目),因此拥有更多的数组类型的增加复杂性并不像直接可用的离散数值的更多类型的复杂性那样严重。

2
实际上,这会有一点优势。如果您拥有一个

class MyTimeAndDayOfWeek {
    byte dayOfWeek;
    byte hour;
    byte minute;
    byte second;
}

在典型的JVM上,它所需空间与包含单个int的类一样多。内存消耗会四舍五入到8或16字节的下一个倍数(如果我没记错的话,这是可配置的),因此真正节省空间的情况相当罕见。
如果相应的Calendar方法返回一个byte,那么该类将稍微更易于使用。但是,只有get(int)这样的Calendar方法,并且必须返回int,因为还有其他字段。对较小类型的每个操作都会提升为int,因此需要进行大量转换。
很可能,您要么放弃并切换到int,要么编写如下的setter:
void setDayOfWeek(int dayOfWeek) {
    this.dayOfWeek = checkedCastToByte(dayOfWeek);
}

无论是哪种类型的DAY_OF_WEEK,都没有关系。


我怀疑值会被打包成那样,因为在现代处理器上进行非对齐内存访问会导致严重的性能损失。有关详细信息,请参见https://dev59.com/Rmcs5IYBdhLWcg3w5IBa。 - Stefan Paul Noack

2
使用比CPU总线大小更小的变量意味着需要更多的周期。例如,在更新内存中的单个字节时,64位CPU需要读取整个64位字,仅修改更改的部分,然后写回结果。
此外,当变量存储在寄存器中时,使用较小的数据类型需要额外的开销,因为必须显式考虑较小数据类型的行为。由于整个寄存器已被使用,因此对于方法参数和局部变量,使用较小的数据类型不会带来任何收益。
尽管如此,这些数据类型可能对于表示需要特定宽度的数据结构(例如网络数据包)或在大型数组中节省空间而牺牲速度非常有用。

非常有趣的东西!+1 我认为这些优化应该由 JVM 完成。作为开发人员,我只需要关注我想要执行的操作、它所提供的语义值和值的范围。 - Willi Mentzel
@WilliMentzel 即使在语义上使用 Byte 表示月份中的某一天也不是必要的,因为对编译器来说它的意思是“请添加特殊行为以确保在 255 处溢出”,而不是“提醒一下,这个值非常小”。最简单的类型并不总是最受限制的类型。我曾经和一个非常好的同事讨论过,如果你知道元素是不同的,那么使用 Set 还是 List 更好,这个讨论沿着同样的线路进行;-) - Stefan Paul Noack

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