为啥字节和短除法会在 Java 中产生 int?
Posted
技术标签:
【中文标题】为啥字节和短除法会在 Java 中产生 int?【英文标题】:Why byte and short division results in int in Java?为什么字节和短除法会在 Java 中产生 int? 【发布时间】:2016-12-08 08:03:32 【问题描述】:在 Java 中,如果我们将byte
s、short
s 或int
s 相除,我们总是得到int
。如果其中一个操作数是long
,我们将得到long
。
我的问题是 - 为什么 byte
或 short
除法不会导致 byte
或 short
?为什么总是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 规范说(添加)你有:iadd
、ladd
、fadd
、dadd
。这只是反映了底层机器通常表现得这样的事实。任何其他选择都是可能的,但可能是以性能下降为代价的。
【讨论】:
【参考方案3】:我相信理由是简单的规则不会产生意外。结果总是两者中较宽的类型(最小为int
),它不依赖于操作。
更好的方法可能是始终扩大+
、*
、-
,除非明确(或可能隐含)缩小。即除非你使用强制转换,否则不要溢出或下溢。例如,/
可以始终是 double
或 long
操作,除非强制转换。
但是 C 和 Java 不这样做。
简而言之,它有一个简单的规则来处理这个问题,无论好坏。
在这里查看我的咆哮http://vanillajava.blogspot.co.uk/2015/02/inconsistent-operation-widen-rules-in.html
【讨论】:
你确定它是两者中较宽的类型吗?我的理解是,如果其中一个参数大于int
,但如果两者都是int
s 或更小,则结果始终是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 short
s 相乘会溢出 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 应用程序中产生错误?