[!TIP] JDK 版本
以下代码均基于 JDK 17 版本。
首先看一下 Byte
类的定义:
java
@jdk.internal.ValueBased
public final class Byte extends Number implements Comparable<Byte>, Constable {
}
Byte
继承了 Number
抽象类,另外还实现了 Comparable<Byte>
和 Constable
接口。
Number
抽象类定义了将当前类型转换为几种基础数值类型的方法,其代码如下:
java
public abstract class Number implements java.io.Serializable {
public Number() {super();}
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue() { return (byte)intValue(); }
public short shortValue() { return (short)intValue(); }
}
Byte
类中的实现为:
java
@IntrinsicCandidate
public byte byteValue() { return value; }
public short shortValue() { return (short)value; }
public int intValue() { return (int)value; }
public long longValue() { return (long)value; }
public float floatValue() { return (float)value; }
public double doubleValue() { return (double)value; }
@java.io.Serial
private static final long serialVersionUID = -7183698231559129828L;
Byte
类型是范围最小的数值类型,所以向上强转时不会有溢出的问题。另外 serialVersionUID
静态字段是序列化时用来标记当前类的版本号的。
Comparable
是一个泛型接口,仅仅包含一个方法,其代码如下:
java
public interface Comparable<T> {
public int compareTo(T o);
}
这个接口虽然只有一个方法,但在 Java 中很多地方都会用到。这个方法不管 T
是什么类型,返回的始终是一个 int
类型的结果。当结果为正数时,表示当前对象比参数 o
大,为负数时表示当前对象比参数 o
小,为零时表示两者相等。
compareTo
方法的实现需要满足如下约定:
x.compareTo(y) == -y.compareTo(x)
x.compareTo(y) > 0
且y.compareTo(z) > 0
,则x.compareTo(z) > 0
x.compareTo(y) == 0
,则x.compareTo(z)
的结果和y.compareTo(z)
的结果相同
Byte
类中的实现为:
java
public int compareTo(Byte anotherByte) {
return compare(this.value, anotherByte.value);
}
public static int compare(byte x, byte y) {
return x - y;
}
Constable
是 Java 12 引入的,表示这是一种可常量化的类型,其实例可以名义上描述自身为一个 ConstantDesc
。
java
public interface Constable {
Optional<? extends ConstantDesc> describeConstable();
}
Byte
类中的实现为:
java
@Override
public Optional<DynamicConstantDesc<Byte>> describeConstable() {
return Optional.of(DynamicConstantDesc.ofNamed(BSM_EXPLICIT_CAST,
DEFAULT_NAME, CD_byte, intValue()));
}
上面代码中的三个常量都定义在 ConstantDescs
类下:
java
public static final DirectMethodHandleDesc BSM_EXPLICIT_CAST
= ofConstantBootstrap(CD_ConstantBootstraps, "explicitCast",
CD_Object, CD_Object);
public static final String DEFAULT_NAME = "_";
public static final ClassDesc CD_byte = ClassDesc.ofDescriptor("B");
虽然 Byte
类型的定义代码上没有显式继承 Object
类,但在 Java 中所有引用类型都是 Object
类型的子类。接下来是 Byte
类重写的继承自 Object
类的 hashCode
、equals
、toString
方法:
java
@Override
public int hashCode() { return Byte.hashCode(value); }
public static int hashCode(byte value) { return (int)value; }
public boolean equals(Object obj) {
// 顺便提一下,这里可以使用新的 instanceof 语法优化一下写法
// if (obj instanceof Byte b) {
// return value == b.byteValue();
// }
if (obj instanceof Byte) {
return value == ((Byte)obj).byteValue();
}
return false;
}
public String toString() { return Integer.toString((int)value); }
其中 hashCode
方法需要满足以下约定:
-
在 Java 应用程序的一次执行过程中,在对同一个对象上多次调用
hashCode
方法时,只要该对象的equals
比较中使用的信息没有被修改,它就必须始终返回相同的整数。这个整数不需要在同一个应用程序的不同执行之间保持一致。这条约定可以联想到
Object.hashCode()
方法。这是个原生方法,在 JVM 实现中其结果是基于对象的地址计算出来的,正好满足这个约定。 当然,Byte
类的hashCode
方法中直接返回了value
,因此也满足这个约定。 -
如果根据
equals
方法两个对象相等,那么对这两个对象中调用hashCode
方法必须产生相同的整数结果。 -
如果根据
equals
方法两个对象不相等,并不要求对这两个对象中的hashCode
方法必须产生不同的整数结果。然而,程序员应该意识到,对于不相等的对象产生不同的整数结果可能会提高哈希表的性能。
equals
方法则需要满足以下约定:
- 自反 :对于任何非空引用值
x
,x.equals(x)
应该返回true
。 - 对称 :对于任何非空引用值
x
和y
,如果x.equals(y)
返回true
,那么y.equals(x)
也应该返回true
。 - 传递 :对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
且y.equals(z)
返回true
,那么x.equals(z)
应该返回true
。 - 一致 :对于任何非空引用值
x
和y
,只要在对象的equals
比较中使用的信息没有被修改,多次调用x.equals(y)
应该始终返回true
或者始终返回false
。 - 对于任何非空引用值
x
,x.equals(null)
应该返回false
。
可以看出 equals
方法和 hashCode
方法的约定有很多类似和关联的地方。hashCode
常用来进行离散,以方便存储和查找,但是 hashCode
可能会产生冲突,因此在比较时仍然需要使用 equals
方法进行判断,不过在调用 equals
方法前先比较 hashCode
可以极大的提高比较效率。这也就是为什么 hashCode()
方法会有第 2 和 第 3 条约定的原因。因此,通常情况下,如果重写了 equals
方法,则必须也要重写 hashCode
方法,以同时满足这些约定,否则可能会导致一些意想不到的问题。
Byte
类内部创建了一个嵌套类 ByteCache
,这个类的作用是缓存所有的 Byte
对象实例,其代码如下:
java
private static class ByteCache {
private ByteCache() {}
static final Byte[] cache;
static Byte[] archivedCache;
static {
final int size = -(-128) + 127 + 1;
// Load and use the archived cache if it exists
CDS.initializeFromArchive(ByteCache.class);
if (archivedCache == null || archivedCache.length != size) {
Byte[] c = new Byte[size];
byte value = (byte)-128;
for(int i = 0; i < size; i++) {
c[i] = new Byte(value++);
}
archivedCache = c;
}
cache = archivedCache;
}
}
其中第 11 行代码的处理是优先从 CDS(Class Data Sharing)中加载缓存数据。
初始化 cache
字段的处理也比较有意思,用了 3 个 Byte[]
数组变量,分别是 c
、cache
和 archivedCache
。其中 archivedCache
字段是 JVM 中指定的用来加载缓存的字段。从如下的 JVM 源码可以看到几个基本数据类型对应的包装类使用的缓存都是这个字段名。
cpp
static ArchivableStaticFieldInfo closed_archive_subgraph_entry_fields[] = {
{"java/lang/Integer$IntegerCache", "archivedCache"},
{"java/lang/Long$LongCache", "archivedCache"},
{"java/lang/Byte$ByteCache", "archivedCache"},
{"java/lang/Short$ShortCache", "archivedCache"},
{"java/lang/Character$CharacterCache", "archivedCache"},
{"java/util/jar/Attributes$Name", "KNOWN_NAMES"},
{"sun/util/locale/BaseLocale", "constantBaseLocales"},
};
另外的 c
变量是在 archivedCache
没有值时创建的。为什么不直接将 archivedCache
初始化为 new Byte[size]
然后在对其赋值呢?虽然这里两种写法可能没什么区别,但还是推荐使用这种防御性写法。万一初始化的代码同时有两个线程在执行,那么当前的写法可以大概率地避免未初始化完成的 archivedCache
被访问(由于 archivedCache
字段没有使用 volatile
修饰,仍然有可能出问题,不过这里是静态类的静态代码块,理论上不会出现并发执行的情况)。
下面的 valueOf
方法就是基于 ByteCache
实现的,这样通过这种方法不管创建多少个 Byte
变量都不需要创建新的 Byte
对象实例。
java
@IntrinsicCandidate
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
public static Byte valueOf(String s, int radix)
throws NumberFormatException {
return valueOf(parseByte(s, radix));
}
public static Byte valueOf(String s) throws NumberFormatException {
return valueOf(s, 10);
}
第二个 valueOf
方法的重载调用了 parseByte
转型方法,Byte
的 parse
方法都是基于 Integer
类型的对应方法实现的,先转换为整型,然后再转换为 byte
型。当转换的整型结果超出 byte
范围时,也会抛出 NumberFormatException
异常,Integer.parseInt
方法本身也有可能抛出这个异常。
java
public static byte parseByte(String s, int radix)
throws NumberFormatException {
int i = Integer.parseInt(s, radix);
if (i < MIN_VALUE || i > MAX_VALUE)
throw new NumberFormatException(
"Value out of range. Value:\"" + s + "\" Radix:" + radix);
return (byte)i;
}
public static byte parseByte(String s) throws NumberFormatException {
return parseByte(s, 10);
}
这里用到了两个常量 MIN_VALUE
和 MAX_VALUE
,分别表示 Byte
类型支持的最小值和最大值。另外几个数值类型 Short
、Integer
和 Long
也有相同名称的常量,分别表示其各自可以允许的最小值和最大值。
java
public static final byte MIN_VALUE = -128;
public static final byte MAX_VALUE = 127;
decode
方法支持将十进制、十六进制和八进制的数字字符串转换为 Byte
类型。仍然是基于 Integer
的同名方法实现的。
该方法支持如下前缀:
0x
十六进制0X
十六进制#
十六进制0
八进制
java
public static Byte decode(String nm) throws NumberFormatException {
int i = Integer.decode(nm);
if (i < MIN_VALUE || i > MAX_VALUE)
throw new NumberFormatException(
"Value " + i + " out of range from input " + nm);
return valueOf((byte)i);
}
这个是 Byte
类用来保存值的 value
字段。这是一个 final
字段,表示其值初始化后不能再被修改。
java
private final byte value;
下面是两个构造函数。通过构造函数创建的 Byte
对象和通过 Byte.valueOf()
方法获取的对象,即使值是一样的,也不是同一个对象。
java
@Deprecated(since="9", forRemoval = true)
public Byte(byte value) {
this.value = value;
}
@Deprecated(since="9", forRemoval = true)
public Byte(String s) throws NumberFormatException {
this.value = parseByte(s, 10);
}
下面的几个方法是关于无符号数的,由于 Byte
本身是有符号的,转成有符号数时就可能会溢出,所以只提供了转整形和长整型的方法。
java
public static int compareUnsigned(byte x, byte y) {
return Byte.toUnsignedInt(x) - Byte.toUnsignedInt(y);
}
public static int toUnsignedInt(byte x) {
return ((int) x) & 0xff;
}
public static long toUnsignedLong(byte x) {
return ((long) x) & 0xffL;
}
至于转换的算法,可以说是相当简洁了,只是将 byte
变量转成整形或长整型,然后和 0xff
做按位与运算。
第一步的强制转换的作用是提升数据的位数,这里隐藏的处理是:
- 如果是正数,前补的二进制都是 0;
- 如果是负数,前补的二进制都是 1;
第二步的 &
运算就是将超过 byte
长度的二进制位(包括符号位)置零。
这里以 -1
为例说明一下其流程:
byte -1
的 二进制是1111 1111
;- 转换为
int
型后的二进制是1111 1111 1111 1111 1111 1111 1111 1111
; & 0xff
的结果为0000 0000 0000 0000 0000 0000 1111 1111
;
简单来说就相当于直接将 -1
的二进制值 1111 1111
以忽略符号位的方式直接读取,最终结果就是 255。
顺便提一下为什么 -1
的二进制是 1111 1111
,这关系到计算机中负数的存储和负数的计算。
在计算机中负数是以 补码 的形式存储的,而负数的补码是 反码 加一,负数的反码则是 原码 除符号位外按位取反。
十进制数 | 原码 | 反码 | 补码 |
---|---|---|---|
1 | 0000 0001 | 0000 0001 | 0000 0001 |
-1 | 1000 0001 | 1111 1110 | 1111 1111 |
-2 | 1000 0010 | 1111 1101 | 1111 1110 |
以 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 + ( − 2 ) = − 1 1 + (-2) = -1 </math>1+(−2)=−1 为例展示一下使用补码进行负数计算的过程。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 0000 0001 1 + 1111 1110 − 2 1111 1111 − 1 \begin{align*} &0000\enspace0001 &1&\\ +\enspace&1111\enspace1110 &-2&\\ \hline &1111\enspace1111 &-1& \end{align*} </math>+0000000111111110111111111−2−1
虽然比较反常识,但一般 CPU 中是只有加法器没有减法器的,计算机科学家们以这种巧妙的将减法运算转换为加法运算的方式实现了减法器的功能。
最后是 Byte
类提供的几个常量,SIZE
表示变量需要的 bit 数,BYTES
表示变量需要的字节数、TYPE
是用来获取其对应的基本类型的 Class
。
java
public static final int SIZE = 8;
public static final int BYTES = SIZE / Byte.SIZE;
@SuppressWarnings("unchecked")
public static final Class<Short> TYPE = (Class<Short>) Class.getPrimitiveClass("short");
TYPE
常量的用法可以参考一下 Class
类的 isPrimitive()
方法,这个方法可以判断一个类是否是基本类型。只有包装类的 TYPE
常量的 isPrimitive()
才是 true
。
java
Integer.TYPE.isPrimitive(); // true
Integer.valueOf(1).getClass().isPrimitive(); // false
下面是判断 Class
是否是基础类型对应的包装类的方法:
java
try {
return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
} catch (Exception e) {
return false;
}
以上就是关于 Byte
类源码阅读的全部内容了。