简介
在 Java 中,基本类型(primitive types)是 Java 提供的最基本的数据类型,它们直接表示值,不是对象。Java 有 8 种基本类型,分别如下:
byte
- 占用 1 字节(8 位)
- 取值范围:-128 到 127
- 用途:节省内存,适合存储大量数据时使用。
short
- 占用 2 字节(16 位)
- 取值范围:-32,768 到 32,767
- 用途:用于节省内存的情况下存储整数数据。
int
- 占用 4 字节(32 位)
- 取值范围:-2^31 到 2^31 - 1(约 -21 亿到 21 亿)
- 用途:最常用的整数类型。
long
- 占用 8 字节(64 位)
- 取值范围:-2^63 到 2^63 - 1
- 用途:用于存储比
int
类型更大的整数。
float
- 占用 4 字节(32 位)
- 取值范围:±1.4E−45 到 ±3.4028235E38(有效数字最多 7 位)
- 用途:用于存储单精度浮点数。
double
- 占用 8 字节(64 位)
- 取值范围:±4.9E−324 到 ±1.7976931348623157E308(有效数字最多 15 位)
- 用途:用于存储双精度浮点数,精度较高,通常用于科学计算。
char
- 占用 2 字节(16 位)
- 取值范围:0 到 65,535(对应 Unicode 字符集)
- 用途:用于存储单一字符,使用 Unicode 编码表示字符。
boolean
- 占用 1 位(尽管实际内存分配可能会更大)
- 取值范围:
true
或false
- 用途:用于表示逻辑值(真或假)。
基本类型的特点:
- 基本类型是值类型,变量直接存储值。
- 没有方法,不能调用方法(如
int
类型没有length()
之类的方法)。 - 不属于对象,存储的是实际数据值,而不是指向数据值的引用。
Java 的基本类型与对象类型的区别:
- 基本类型直接存储数据值,而对象类型存储的是指向数据的引用。
- 对象类型是从类(class)派生的,基本类型则是 Java 语言自带的,不需要通过类定义。
这些基本数据类型在 Java 中是与其他语言相比具有高度性能优化的,尤其是在内存和速度上的表现。
布尔类型
Java中布尔有两种:基本类型 boolean 和对象类型 Boolean。
打印基本类型 boolean
boolean value = false;
System.out.println(""+value);
System.out.println(String.format("%s", value));
System.out.println(String.format("%b", value));
上述代码,运行后输出:
false
false
false
对象类型 Boolean 的实现和常用方法
Boolean 类对 boolean 做了一层封装,类中设置了一个字段存放boolean值:
// 摘自 Java 8 源码
private final boolean value;
以下几种形式都是 true 对应的Boolean:
System.out.println(Boolean.TRUE);
System.out.println(new Boolean(true));
System.out.println(new Boolean("true"));
System.out.println(Boolean.valueOf(true));
System.out.println(Boolean.parseBoolean("true"));
上述代码,运行后输出:
true
true
true
true
true
既然是类,那么就要用 equals 判等,而不能用 ==
:
Boolean a = new Boolean(true);
Boolean b = new Boolean(true);
System.out.println(a == b); // 错误,会输出 false
System.out.println(a.equals(b)); // 正确
System.out.println(Objects.equals(a, b)); // 正确
上述代码,运行后输出:
false
true
true
既然是类,那么它还有一个值,即 null:
Boolean val = null;
System.out.println(val);
上述代码,运行后输出:
null
装箱与拆箱
Java 在处理 boolean 和 Boolean 时,会自动进行隐式的转换。
基本类型和对象类型之间判等:
System.out.println(true == Boolean.TRUE); // Boolean.TRUE 拆箱为 true
System.out.println(Boolean.TRUE.equals(true)); // true 装箱为 Boolean.parseBoolean(true)
上述代码,运行后输出:
true
true
若 Boolean 类型变量值为 null,是不能拆箱成 boolean的,会报 NPE:
Boolean val = null;
boolean val2 = val;
System.out.println(val2); // NullPointerException
上述代码,运行后输出:
java.lang.NullPointerException
at .....
同样的,下面的代码也会抛出 NPE 异常:
Boolean val = null;
if(val) {
System.out.println("Hi"); // NullPointerException
}
最常见的自动装箱,是函数参数/返回值是某种类型,但实际使用时用的是另外一个类型。
例如对于下面的函数:
public boolean negative(boolean val) {
return !val;
}
运行下面的代码:
System.out.println(negative(false));
System.out.println(negative(Boolean.FALSE));
输出:
true
true
什么时候不会自动装箱/拆箱
void process(boolean val) {
System.out.println("boolean");
}
@Test
public void test() {
process(true);
process(Boolean.TRUE); // 会自动拆箱
}
执行以上代码会输出:
boolean
boolean
但若是以下代码则不会发生自动拆箱,因为优先匹配符合要求的函数:
void process(boolean val) {
System.out.println("boolean");
}
void process(Boolean val) {
System.out.println("Boolean");
}
@Test
public void test() {
process(true);
process(Boolean.TRUE);
}
执行以上代码会输出:
boolean
Boolean
boolean 和 Boolean 之间的显式转换
将 boolean 转换为 Boolean 的常见方式是:
Boolean val = Boolean.valueOf(true);
将 Boolean 转换为 boolean 的常见方式是:
boolean val = Boolean.TRUE.booleanValue();
自动拆箱/装箱会增加耗时
看下面的代码示例:
int sum(int a, int b) {
return a + b;
}
Integer sum2(Integer a, Integer b) {
return a + b;
}
@Test
public void test() {
long start;
int result;
result = 0;
start = System.currentTimeMillis();
for(int i=0; i<100000; ++i) {
result = sum(result, i);
}
System.out.println(System.currentTimeMillis() - start); // 输出sum耗时
result = 0;
start = System.currentTimeMillis();
for(int i=0; i<100000; ++i) {
result = sum2(result, i);
}
System.out.println(System.currentTimeMillis() - start); //输出sum2耗时
}
多次运行后会发现,sum2的耗时是sum的2倍以上。为什么,因为 sum2 有自动装箱和拆箱操作。
Integer之间的比较
- 两个变量对应两个对象,它们之间用
==
判等结果的是false。 - 两个变量,引用同一个对象,它们直接用
==
判等结果的是true。 - null 之间用
==
判等结果的是true。 - 对象之间的值比较,用 equals 。要考虑两个都是null结果是true是否符合业务预期。
- Integer 之间,Integer和int 之间如果比较大小,则Integer会自动拆箱。
- Integer 之间的值比较,用 equals。
示例1
// 使用 Integer.valueOf 生成 Integer 对象
System.out.println( Integer.valueOf(1) >= Integer.valueOf(2) );
System.out.println( Integer.valueOf(1000) >= Integer.valueOf(2000) );
System.out.println( 1 >= Integer.valueOf(2) );
System.out.println( 1000 >= Integer.valueOf(2000) );
// 使用 new Integer 生成对象
System.out.println( new Integer(1) >= new Integer(2) );
System.out.println( new Integer(1000) >= new Integer(2000) );
System.out.println( 1 >= new Integer(2) );
System.out.println( 1000 >= new Integer(2000) );
System.out.println( new Integer(2) <= 1 );
System.out.println( new Integer(2000) <= 1000 );
System.out.println( new Integer(1) == new Integer(1) );
System.out.println( new Integer(1000) == new Integer(1000) );
结果都是 false。
示例2
System.out.println( 1 == new Integer(1) );
System.out.println( 1000 == new Integer(1000) );
System.out.println( new Integer(1) == 1 );
System.out.println( new Integer(1000) == 1000 );
结果都是 true。
示例3
这个示例是乍一看是不符合预期的:
System.out.println( Integer.valueOf(1) == Integer.valueOf(1) ); // true
System.out.println( Integer.valueOf(1000) == Integer.valueOf(1000) ); // false
Integer num1 = 1;
Integer num1000 = 1000;
System.out.println( num1 == Integer.valueOf(1) ); // true
System.out.println( num1000 == Integer.valueOf(1000) ); // false
System.out.println( (Integer)1 == Integer.valueOf(1) ); // true
System.out.println( (Integer)1000 == Integer.valueOf(1000) ); // false
直接上结论:
- Oracle JDK 在自动装箱时使用的 Integer.valueOf 将 int 变成 Integer。
- 使用 Integer.valueOf 时,对于[-128, 127]范围内的数字,会使用缓存起来的Integer对象,所以两个 Integer.valueOf(1) 指向的是同一个对象。具体见 Integer.valueOf 源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}