问题描述

遇到一个题目:

经过强制类型转换以后,变量a,b的值分别为多少?
short a = 128;
byte b = (byte) a;
a = ?, b = ?

答案是:a = 128, b = -128

这涉及到 Java primitive type 的 conversion,打算借此稍稍研究一下。

分析过程

下面分析中会涉及到一些与题目无关的细节,想直接看题目解答的请跳到 "题目中的数值在内存中的表示"

明确 Java 中的 Conversion

Java 的 conversion 有多种,这里我们只讨论 primitive conversion。

primitive conversion 分为两种,一是 Widening Primitive Conversion(扩展型基本数据类型转换),二是 Narrowing Primitive Conversion(窄化型基本数据类型转换)。

ps: 翻译成中文还是感觉怪怪的,下面还是用英文表示吧。

1. Widening Primitive Conversion

JLS 定义了 19 种 widening pc,简单来说,就是位数低的向高的转换,如下

  • byte to short, int, long, float, or double
  • short to int, long, float, or double
  • char to int, long, float, or double
  • int to long, float, or double
  • long to float or double
  • float to double

widening pc 不会丢失数值的整体大小信息

2. Narrowing Primitive Conversion

这个问题涉及到的 int -> short 和 short -> byte 的转换就包含在 JLS 定义的 22 种 narrowing pc 之中。

  • short to byte or char
  • char to byte or short
  • int to byte, short, or char
  • long to byte, short, char, or int
  • float to byte, short, char, int, or long
  • double to byte, short, char, int, long, or float

需要注意的是 narrowing pc 是有可能丢失数值的整体信息以及损失精度和范围的。

可能有人会注意到,上面的 widening pc 和 narrowing pc 都没有包含 byte -> char 的转换

Chapter 5. Conversions and Promotions

The following conversion combines both widening and narrowing primitive conversions:
byte to char
First, the byte is converted to an int via widening primitive conversion (§5.1.2), and then the resulting int is converted to a char by narrowing primitive conversion (§5.1.3).

这是因为这是一种特殊地、同时结合了 widening pc 和 narrowing pc 的转换,byte 会先转换成 int(widening pc),然后将这个 int 结果转换成 char(narrowing pc)。

具体分析

那么我们还是回到这个问题

short a = 128;
byte b = (byte) a;

首先,在 Java 中,整数默认为 int 类型,也就是说,在 short a = 128; 中,会发生 int -> short 的 narrowing pc,是有可能损失精度的,由于 int 是高位(32位),short 是低位(16位),所以在转换时会 truncate。同样,对于 byte b = (byte) a; 也有可能因为 truncate 而损失精度。

先回顾原码、反码、补码的概念

在 Java 中数值是用补码表示的,在这里回顾一下原码、反码、补码的概念(以 3 为例吧):

  1. 原码(第一个为符号位,1 表示负数,0 表示正数)
  • 3 的原码:0000 0011
  • -3 的原码:1000 0011
  1. 反码(就是反过来,注意符号位不变):
  • 3 的反码:1111 1100
  • -3 的反码:1111 1100
  1. 补码(正数的补码 = 原码,负数的补码 = 反码 + 1)
  • 3 的补码:0000 0011
  • -3 的补码:1111 1101

题目中的数值在内存中的表示

二进制表示:

int a = 128 00000000 00000000 10000000 00000000

short a = 128 00000000 10000000 (强转后前面 16 位被截断)

可以看出来,a 的值输出应该还是 128

那么对于 byte b = (byte) a;

二进制表示:

short a = 128 00000000 10000000

byte b = 128 10000000 (强转后前面 8 位被截断)

但是,输出的 b 的值为什么不是 128 而是 -128 呢

Primitive Data Types (The Java™ Tutorials > Learning the Java Language > Language Basics)

byte: The byte data type is an 8-bit signed two's complement integer. It has a minimum value of -128 and a maximum value of 127 (inclusive). The byte data type can be useful for saving memory in large arrays, where the memory savings actually matters. They can also be used in place of int where their limits help to clarify your code; the fact that a variable's range is limited can serve as a form of documentation.

这是因为 byte 是一个 8 位有符号二进制补码整数,它包含的值的范围是 [-128, 127],因此 byte 类型的值是无法表示 128 的

那么在发生截断后,1000 0000 表示的就是一个补码,从这个补码可以看出来它的原码肯定是一个负数,那么我们根据这个补码反推得到它的反码:1111 1111,从而得到它的原码:1000 0000,可以看出这个值就是 -0,但是在我们的生活中是没有 -0 的,所以在计算机中就把它识别成 -128,因此这就是为什么 b 的值的输出是 -128 的原因。

ps: 关于 -0 对应于 -128 的具体解释,我懒得写了,因为感觉取决于个人的理解,不想细究,如果有人还有疑问,可以看看这个人的解释 byte类型取值范围为什么是127到-128? - 知乎

那么到这里,问题就基本解释清楚了,感觉自己还是很啰嗦,不过其实是为了回顾一些基础的知识,也希望对其他人有所帮助啦~


shelbylee
31 声望1 粉丝

Enjoy coding~