int
int 是我们常说的整形数字,是 Java 的 8 个原始数据类型(Primitive Types,boolean、byte 、short、char、int、float、double、long)之一。Java 语言虽然号称一切都是对象,但原始数据类型是例外。
Integer
Integer 是 int 对应的包装类,它有一个 int 类型的字段存储数据,并且提供了基本操作,比如数学运算、int 和字符串之间转换等。在 Java 5 中,引入了自动装箱和自动拆箱功能(boxing/unboxing),Java 可以根据上下文,自动进行转换,极大地简化了相关编程。
关于 Integer 的值缓存,这涉及 Java 5 中另一个改进。构建 Integer 对象的传统方式是直接调用构造器,直接 new 一个对象。但是根据实践,我们发现大部分数据操作都是集中在有限的、较小的数值范围,因而,在 Java 5 中新增了静态工厂方法 valueOf,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照 Javadoc,这个值默认缓存是 -128 到 127 之间。
int和Integer有什么区别?
它们之间的主要区别在于以下几点:
基本数据类型 vs 类型包装器:
- int是Java的基本数据类型,是原始数据类型之一。它直接存储整数值,而不是对象。
- Integer是 java.lang 包中的一个类,是 int 的包装器类。它允许将基本数据类型 int 转换为对象,并提供了一些额外的功能。
空值表示:
- int 是基本数据类型,因此不能为 null。如果不赋初值,int默认初始化为0。
- Integer 是一个对象,因此可以为 null。如果 Integer 对象没有被初始化,它的默认值是 null。
性能:
- 由于 int 是原始数据类型,它在内存中占用的空间比 Integer 小,且操作更为高效。
- 使用 Integer 对象会占用更多的内存,并且可能涉及到自动装箱(autoboxing)和自动拆箱(autounboxing)的过程,可能会导致性能损失。
方法和功能:
- int 不是对象,因此没有与之相关的方法或功能。
- Integer 是一个类,它提供了一系列方法,比如 parseInt()、valueOf() 等,以及一些其他的实用方法。
int和Integer的使用场景
使用 int 的场景:
- 简单的整数值:当进行基本的数学运算时,通常使用 int。因为它是原始数据类型,操作更为高效。
- 数组索引:通常使用 int 类型,因为它能够直接映射到数组的位置。
- 在对性能敏感的代码中,避免使用对象,而是使用 int 可以提高性能。
- 默认值为0的情况:当你知道变量的默认值应该是0时,可以使用 int,因为它在未初始化时默认值为0。
使用 Integer 的场景:
- 集合类和泛型:在需要对象而不是原始类型的集合类中,使用 Integer。例如,List<Integer> 或 Map<String, Integer>。
- 方法参数和返回值:当需要将整数包装为对象传递给方法时,或者方法需要返回一个整数对象时,可以使用 Integer。
- 空值表示:如果需要在某些情况下表示空值,可以使用 Integer,因为它可以为 null,而 int 不能。
- Java中的一些API:在与一些Java API 交互时,有些方法要求使用对象而不是原始类型。例如,某些集合类或方法可能要求传递 Object,这时可以传递 Integer。
总结:
在实际编码中,通常可以直接使用 int,但在需要对象的上下文中(例如集合,泛型类,方法参数等),可以使用 Integer。此外,自Java 5以来,引入了自动装箱和自动拆箱的特性,可以方便地在基本类型和其对应的包装类型之间进行转换。例如,你可以将 int 自动装箱为 Integer,反之亦然。
自动装箱、拆箱
自动装箱(Autoboxing)和自动拆箱(Unboxing)是Java中的两个特性,它们允许在基本数据类型(如 int, double, char 等)和对应的包装类型(如 Integer, Double, Character 等)之间进行自动转换。
1. 自动装箱(Autoboxing)
自动装箱是指将基本数据类型自动转换为其对应的包装类型。这是通过Java编译器在需要的上下文中自动完成的,保证不同的写法在运行时等价,它们发生在编译阶段,也就是生成的字节码是一致的。例如,将 int 转换为 Integer:相当于使用Integer.valueOf()方法
代码语言:java复制int a = 42;
Integer b = a; // 自动装箱
// 等价于 Integer b = Integer.valueOf(a); // 显式装箱
在这里,a 是一个基本的整数类型,而将它赋给 b 时,会自动转换为 Integer 对象。这样的转换在需要使用对象而实际上只有基本数据类型可用的情况下很方便。
2. 自动拆箱(Unboxing)
自动拆箱是指将包装类型自动转换为其对应的基本数据类型。同样,这也是由Java编译器在需要的上下文中自动完成的。例如,将 Integer 转换为 int:
代码语言:java复制Integer b = 42;
int a = b; // 自动拆箱
// 等价于 int a = b.intValue(); // 显式拆箱
在这里,a 是一个 Integer 对象,而将它赋给 b 时,会自动转换为基本的整数类型。
自动装箱和自动拆箱的引入简化了代码,并提高了代码的可读性。它们在Java 5及以后的版本中被引入,旨在使基本数据类型和其对应的包装类型之间的转换更加方便。但在一些性能敏感的场景,需要注意自动装箱和拆箱可能引起的性能开销。
自动装箱 / 自动拆箱似乎很酷,在编程实践中,有什么需要注意的吗?原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,创建 10 万个 Java 对象和 10 万个整数的开销可不是一个数量级的,不管是内存使用还是处理速度,光是对象头的空间占用就已经是数量级的差距了。
我们其实可以把这个观点扩展开,使用原始数据类型、数组甚至本地代码实现等,在性能极度敏感的场景往往具有比较大的优势,用其替换掉包装类、动态数组(如 ArrayList)等可以作为性能优化的备选项。一些追求极致性能的产品或者类库,会极力避免创建过多对象。当然,在大多数产品代码里,并没有必要这么做,还是以开发效率优先。
知识扩展
坦白说,理解基本原理和用法已经足够日常工作需求了,但是要落实到具体场景,还是有很多问题需要仔细思考才能确定。
在面试中,面试官可以结合其他方面,来考察面试者的掌握程度和思考逻辑,比如:
- Java 使用的不同阶段:编译阶段、运行时,自动装箱 / 自动拆箱是发生在什么阶段?
- 前面提到使用静态工厂方法 valueOf 会使用到缓存机制,那么自动装箱的时候,缓存机制起作用吗?
- 为什么我们需要原始数据类型,Java 的对象似乎也很高效,应用中具体会产生哪些差异?
- 阅读过 Integer 源码吗?分析下类或某些方法的设计要点。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!