基本类型


#Java 笔记


简介

在 Java 中,基本类型(primitive types)是 Java 提供的最基本的数据类型,它们直接表示值,不是对象。Java 有 8 种基本类型,分别如下:

  1. byte

    • 占用 1 字节(8 位)
    • 取值范围:-128 到 127
    • 用途:节省内存,适合存储大量数据时使用。
  2. short

    • 占用 2 字节(16 位)
    • 取值范围:-32,768 到 32,767
    • 用途:用于节省内存的情况下存储整数数据。
  3. int

    • 占用 4 字节(32 位)
    • 取值范围:-2^31 到 2^31 - 1(约 -21 亿到 21 亿)
    • 用途:最常用的整数类型。
  4. long

    • 占用 8 字节(64 位)
    • 取值范围:-2^63 到 2^63 - 1
    • 用途:用于存储比 int 类型更大的整数。
  5. float

    • 占用 4 字节(32 位)
    • 取值范围:±1.4E−45 到 ±3.4028235E38(有效数字最多 7 位)
    • 用途:用于存储单精度浮点数。
  6. double

    • 占用 8 字节(64 位)
    • 取值范围:±4.9E−324 到 ±1.7976931348623157E308(有效数字最多 15 位)
    • 用途:用于存储双精度浮点数,精度较高,通常用于科学计算。
  7. char

    • 占用 2 字节(16 位)
    • 取值范围:0 到 65,535(对应 Unicode 字符集)
    • 用途:用于存储单一字符,使用 Unicode 编码表示字符。
  8. boolean

    • 占用 1 位(尽管实际内存分配可能会更大)
    • 取值范围:truefalse
    • 用途:用于表示逻辑值(真或假)。

基本类型的特点:

  • 基本类型是值类型,变量直接存储值。
  • 没有方法,不能调用方法(如 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);
}


( 本文完 )