典型问答

Q:int 和 Integer 有什么区别?谈谈 Integer 的值缓存范围。
A:
int 是Java 的8个原始数据类型(Primitive Types,boolean、byte、short、int、long、float、double)之一。Java 语言号称一切都是对象,但原始数据类型是例外。
Integer 是 int 对应的包装类,它有一个 int 类型的字段存储数据,并且提供了基本操作,比如数学运算、包装类和int 或字符串之间转换等。在 Java 5 中,引入了自动装箱和自动拆箱功能(boxing/unboxing),Java 可以根据上下文,自动进行转换,极大地简化了相关编程。
关于Integer 的值缓存, Java 5 中新增了静态工厂方法 valueOf,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照 Javadoc,这个值默认缓存是 -128 到 127 之间。
**

考点分析

自动装箱、自动拆箱机制,进而考察封装类的一些设计和实践

  1. 自动装箱(Integer.valueOf())和自动拆箱(instance.intValue())发生Java 的那个阶段,编译期还是运行时
  2. 静态工厂方法valueOf 会用到缓存机制,那么自动装箱时,缓存机制起作用吗
  3. 为什么需要使用原始数据类型,Java 的对象似乎也很高效,应用中具体会产生哪些差异
  4. 阅读过Integer源码吗?分析下类或某些方法的设计要点

知识拓展

  • 理解自动装箱、拆箱

自动装箱是一种语法糖,发生在编译阶段,保证不同写法再运行时等价,也就是生成的字节码一致。
原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感场合(创建10万个Java对象和10万个正式的开销 不是一个数量级)

  • 源码分析

1.Integer包括

  1. 基础常量,如最大值、最小值、位数等;
  2. 静态工厂方法(装箱方法)valueOf();
    1. static Integer valueOf(int i)
  3. 实例方法(拆箱方法)
    1. int intValue()
  4. 获取环境变量数值的方法;
  5. 各种转换方法,如转换为不同进制的字符串,或者反过来解析的方法等。

2.缓存机制

  • Integer的缓存范围默认是-128~127,但是缓存的上限值可以根据需要调整,JVM提供了设置参数
    -XX:AutoBoxCacheMax=N<br />具体实现在Integer源码的IntegerCache的静态初始块中实现

3.包装类中的

  • 原始类型线程安全

原始数据类型的变量需要通过并发手段才能保证线程安全,如果有线程安全的计算需要,建议考虑IntegerAtomic,LongAtomic这样的线程安全类。

  • Java 原始数据类型和引用类型局限性
    • 原始数据类型和Java泛型并不能配合使用
      Java的泛型其实可以算作伪泛型,只是编译期技巧,Java编译期自动将类型转换为特定类型,这决定了使用泛型必须保证相应类型可以转换成Object
    • 无法高效表达数据,也不便于表达复杂的数据结构

      一课一练

      从空间角度,Java 对象比原始数据类型开销大很多。你知道对象的内存结构吗?比如,对象头的结构。如何计算或者获取某个Java 对象的大小?

      节选自《深入理解JAVA虚拟机》: 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为”Mark Word”。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身,这点将在2.3.3节讨论。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却无法确定数组的大小。 接下来的实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。 第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。

精选留言

理解Java原始数据类型和包装类的一条线索:原始数据类型(Primitive Types)和 Java 泛型(Generic)并不能配合时用,于是JAVA就设计了这个auto-boxing/unboxing机制,实际上就是primitive value 与 object之间的隐式转换机制,要是没有这个机制,开发者就必须每次手动显示转换。
但是primitive value 与 object各自有各自的优势,primitive value在内存中存的是值,所以找到primitive value的内存位置,就可以获得值;object存的是reference,找到object的内存位置,还要根据reference找下一个内存空间,要产生更多的IO,所以计算性能比primitive value差,但是object具备generic的能力,更抽象,解决业务问题编程效率高。于是JAVA设计者的初衷估计是这样的:如果开发者要做计算,就应该使用primitive value如果开发者要处理业务问题,就应该使用object,采用Generic机制;反正JAVA有auto-boxing/unboxing机制,对开发者来讲也不需要注意什么。然后为了弥补object计算能力的不足,还设计了static valueOf()方法提供缓存机制,算是一个弥补。