avatar

子墨的博客

The future is already here — it's just not very evenly distributed.

  • 首页
  • 随记
  • 我
Home 包装类型
文章

包装类型

Posted 2021-04-10 Updated 2025-03- 13
By 子墨
105~135 min read

我们已经知道,Java的数据类型分两种:

  • 基本类型:byte,short,int,long,boolean,float,double,char;

  • 引用类型:所有class和interface类型。

引用类型可以赋值为null,表示空,但基本类型不能赋值为null:

String s = null;
int n = null; // compile error!

那么,如何把一个基本类型视为对象(引用类型)?

比如,想要把int基本类型变成一个引用类型,我们可以定义一个Integer类,它只包含一个实例字段int,这样,Integer类就可以视为int的包装类(Wrapper Class):

public class Integer {
    private int value;

    public Integer(int value) {
        this.value = value;
    }

    public int intValue() {
        return this.value;
    }
}

定义好了Integer类,我们就可以把int和Integer互相转换:

Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();

实际上,因为包装类型非常有用,Java核心库为每种基本类型都提供了对应的包装类型:

基本类型

对应的引用类型

boolean

java.lang.Boolean

byte

java.lang.Byte

short

java.lang.Short

int

java.lang.Integer

long

java.lang.Long

float

java.lang.Float

double

java.lang.Double

char

java.lang.Character

我们可以直接使用,并不需要自己去定义:

// Integer:
public class Main {
    public static void main(String[] args) {
        int i = 100;
        // 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
        Integer n1 = new Integer(i);
        // 通过静态方法valueOf(int)创建Integer实例:
        Integer n2 = Integer.valueOf(i);
        // 通过静态方法valueOf(String)创建Integer实例:
        Integer n3 = Integer.valueOf("100");
        System.out.println(n3.intValue());
    }
}

Auto Boxing

因为int和Integer可以互相转换:

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

所以,Java编译器可以帮助我们自动在int和Integer之间转型:

Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()

这种直接把int变为Integer的赋值写法,称为自动装箱(Auto Boxing),反过来,把Integer变为int的赋值写法,称为自动拆箱(Auto Unboxing)。

注意

自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。

装箱和拆箱会影响代码的执行效率,因为编译后的class代码是严格区分基本类型和引用类型的。并且,自动拆箱执行时可能会报NullPointerException:

// NullPointerException
public class Main {
    public static void main(String[] args) {
        Integer n = null;
        int i = n;
    }
}

不变类

所有的包装类型都是不变类。我们查看Integer的源码可知,它的核心代码如下:

public final class Integer {
    private final int value;
}

因此,一旦创建了Integer对象,该对象就是不变的。

对两个Integer实例进行比较要特别注意:绝对不能用==比较,因为Integer是引用类型,必须使用equals()比较:

// == or equals?
public class Main {
    public static void main(String[] args) {
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true
    }
}

仔细观察结果的童鞋可以发现,==比较,较小的两个相同的Integer返回true,较大的两个相同的Integer返回false,这是因为Integer是不变类,编译器把Integer x = 127;自动变为Integer x = Integer.valueOf(127);,为了节省内存,Integer.valueOf()对于较小的数,始终返回相同的实例,因此,==比较“恰好”为true,但我们绝不能因为Java标准库的Integer内部有缓存优化就用==比较,必须用equals()方法比较两个Integer。

最佳实践

按照语义编程,而不是针对特定的底层实现去“优化”。

因为Integer.valueOf()可能始终返回同一个Integer实例,因此,在我们自己创建Integer的时候,以下两种方法:

  • 方法1:Integer n = new Integer(100);

  • 方法2:Integer n = Integer.valueOf(100);

方法2更好,因为方法1总是创建新的Integer实例,方法2把内部优化留给Integer的实现者去做,即使在当前版本没有优化,也有可能在下一个版本进行优化。

我们把能创建“新”对象的静态方法称为静态工厂方法。Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。

最佳实践

创建新对象时,优先选用静态工厂方法而不是new操作符。

如果我们考察Byte.valueOf()方法的源码,可以看到,标准库返回的Byte实例全部是缓存实例,但调用者并不关心静态工厂方法以何种方式创建新实例还是直接返回缓存的实例。

进制转换

Integer类本身还提供了大量方法,例如,最常用的静态方法parseInt()可以把字符串解析成一个整数:

int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析

Integer还可以把整数格式化为指定进制的字符串:

// Integer:
public class Main {
    public static void main(String[] args) {
        System.out.println(Integer.toString(100)); // "100",表示为10进制
        System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
        System.out.println(Integer.toHexString(100)); // "64",表示为16进制
        System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
        System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
    }
}

注意:上述方法的输出都是String,在计算机内存中,只用二进制表示,不存在十进制或十六进制的表示方法。int n = 100在内存中总是以4字节的二进制表示:

┌────────┬────────┬────────┬────────┐
│00000000│00000000│00000000│01100100│
└────────┴────────┴────────┴────────┘

我们经常使用的System.out.println(n);是依靠核心库自动把整数格式化为10进制输出并显示在屏幕上,使用Integer.toHexString(n)则通过核心库自动把整数格式化为16进制。

这里我们注意到程序设计的一个重要原则:数据的存储和显示要分离。

Java的包装类型还定义了一些有用的静态变量

// boolean只有两个值true/false,其包装类型只需要引用Boolean提供的静态字段:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
// long类型占用的bit和byte数量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)

最后,所有的整数和浮点数的包装类型都继承自Number,因此,可以非常方便地直接通过包装类型获取各种基本类型:

// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

处理无符号整型

在Java中,并没有无符号整型(Unsigned)的基本数据类型。byte、short、int和long都是带符号整型,最高位是符号位。而C语言则提供了CPU支持的全部数据类型,包括无符号整型。无符号整型和有符号整型的转换在Java中就需要借助包装类型的静态方法完成。

例如,byte是有符号整型,范围是-128~+127,但如果把byte看作无符号整型,它的范围就是0~255。我们把一个负的byte按无符号整型转换为int:

// Byte
public class Main {
    public static void main(String[] args) {
        byte x = -1;
        byte y = 127;
        System.out.println(Byte.toUnsignedInt(x)); // 255
        System.out.println(Byte.toUnsignedInt(y)); // 127
    }
}

因为byte的-1的二进制表示是11111111,以无符号整型转换后的int就是255。

类似的,可以把一个short按unsigned转换为int,把一个int按unsigned转换为long。

小结

Java核心库提供的包装类型可以把基本类型包装为class;

自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);

装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException;

包装类型的比较必须使用equals();

整数和浮点数的包装类型都继承自Number;

包装类型提供了大量实用方法。

Java
License:  CC BY 4.0
Share

Further Reading

May 8, 2021

设计模式-命令模式

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。 命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。 命令模式结构示意图: 介绍 意图

May 8, 2021

设计模式-责任链模式

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 责任链模式通过将多个处理器(处理对象)以链式结构连接起来,使得请求沿着这条链传递,直到有一个处理器处

May 8, 2021

设计模式-代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。 代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。 在代理模式中,我们创建具有现有

OLDER

字符串和编码

NEWER

BigInteger

Recently Updated

  • 3分钟将5000w订单的订单号从数据库中加载到Java内存中
  • 探索将20w数据插入1000w+的表中
  • 关于微服务的一些思考
  • IDEA的一些插件
  • 关于微服务

Trending Tags

Spring Vue Linux Java Docker

Contents

©2025 子墨的博客. Some rights reserved.

Using the Halo theme Chirpy