Java 中int和Integer有什么区别

面对上面的问题,你该怎么回答?java

典型回答

int 是咱们常说的整形数字,是 Java 的 8 个原始数据类型(Primitive Types,boolean、byte、short、char、int、float、double、long)之一。Java 语言虽然号称一切都是对象,但原始数据类型是例外。程序员


Integer 是 int 对应的包装类,它有一个 int 类型的字段存储数据,而且提供了基本操做,好比数学运算、int 和字符串之间转换等。在 Java 5 中,引入了自动装箱和自动拆箱功能(boxing/unboxing),Java 能够根据上下文,自动进行转换,极大地简化了相关编程。面试


关于 Integer 的值缓存,这涉及 Java 5 中另外一个改进。构建 Integer 对象的传统方式是直接调用构造器,直接 new 一个对象。可是根据实践,咱们发现大部分数据操做都是集中在有限的、较小的数值范围,于是,在 Java 5 中新增了静态工厂方法 valueOf,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照 Javadoc,这个值默认缓存是 -128 到 127 之间。编程

这个问题涵盖了 Java 里的两个基础要素:原始数据类型、包装类。谈到这里,就能够很是天然地扩展到自动装箱、自动拆箱机制,进而考察封装类的一些设计和实践。坦白说,理解基本原理和用法已经足够平常工做需求了,可是要落实到具体场景,仍是有不少问题须要仔细思考才能肯定。数组

咱们能够结合其余方面,来考察面试者的知识掌握程度和思考逻辑,好比:缓存

  • 自动装箱 / 自动拆箱是发生在什么阶段?编译阶段、运行时?
  • 使用静态工厂方法 valueOf 会使用到缓存机制,那么自动装箱的时候,缓存机制起做用吗?
  • 为何咱们须要原始数据类型,Java 的对象彷佛也很高效,应用中具体会产生哪些差别?
  • 阅读过 Integer 源码吗?分析下类或某些方法的设计要点。

彷佛有太多内容能够探讨,咱们一块儿来分析一下。安全

知识扩展

1. 理解自动装箱、拆箱自动装箱实际上算是一种语法糖。

什么是语法糖?能够简单理解为 Java 平台为咱们自动进行了一些转换,保证不一样的写法在运行时等价,它们发生在编译阶段,也就是生成的字节码是一致的。
像前面提到的整数,javac 替咱们自动把装箱转换为 Integer.valueOf(),把拆箱替换为Integer.intValue(),这彷佛这也顺道回答了另外一个问题,既然调用的是 Integer.valueOf,天然可以获得缓存的好处啊。性能优化

如何程序化的验证上面的结论呢?并发

你能够写一段简单的程序包含下面两句代码,而后反编译一下。固然,这是一种从表现倒推的方法,大多数状况下,咱们仍是直接参考规范文档会更加可靠,毕竟软件承诺的是遵循规范,而不是保持当前行为。jvm

Integer integer = 1;
int unboxing = integer ++;

反编译输出:

1: invokestatic #2 // Method
java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
8: invokevirtual #3 // Method
java/lang/Integer.intValue:()I


这种缓存机制并非只有 Integer 才有,一样存在于其余的一些包装类,好比:

  • Boolean,缓存了 true/false 对应实例,确切说,只会返回两个常量实例Boolean.TRUE/FALSE。
  • Short,一样是缓存了 -128 到 127 之间的数值。
  • Byte,数值有限,因此所有都被缓存(-128 到 127 )。
  • Character,缓存范围 '\u0000' 到 '\u007F'。
  • Long只缓存了 -128 到 127 之间的数值。

自动装箱 / 自动拆箱彷佛很酷,在编程实践中,有什么须要注意的吗? 

原则上,建议避免无心中的装箱、拆箱行为,尤为是在性能敏感的场合,建立 10 万个 Java 对象和 10 万个整数的开销可不是一个数量级的,不论是内存使用仍是处理速度,光是对象头的空间占用就已是数量级的差距了。

咱们其实能够把这个观点扩展开,使用原始数据类型、数组甚至本地代码实现等,在性能极度敏感的场景每每具备比较大的优点,用其替换掉包装类、动态数组(如 ArrayList)等能够做为性能优化的备选项。一些追求极致性能的产品或者类库,会极力避免建立过多对象。固然,在大多数产品代码里,并无必要这么作,仍是以开发效率优先。以咱们常常会使用到的计数器实现为例,下面是一个常见的线程安全计数器实现。

class Counter {
	private final AtomicLong counter = new AtomicLong();
		
	public void increase() {
		counter.incrementAndGet();
	}
}

若是利用原始数据类型,能够将其修改成

class CompactCounter {
	private volatile long counter;
	
	private static final AtomicLongFieldUpdater<CompactCounter> updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class,"counter");
	
	public void increase() {
		updater.incrementAndGet(this);
	}
}

2. 源码分析

考察是否阅读过、是否理解 JDK 源代码多是部分面试官的关注点,这并不彻底是一种苛刻要求,阅读并实践高质量代码也是程序员成长的必经之路,下面我来分析下 Integer 的源码。总体看一下 Integer 的职责,它主要包括各类基础的常量,好比最大值、最小值、位数等;前面提到的各类静态工厂方法 valueOf();获取环境变量数值的方法;各类转换方法,好比转换为不一样进制的字符串,如 8 进制,或者反过来的解析方法等。咱们进一步来看一些有意思的地方。首先,继续深挖缓存,Integer 的缓存范围虽然默认是 -128 到 127,可是在特别的应用场景,好比咱们明确知道应用会频繁使用更大的数值,这时候应该怎么办呢?

缓存上限值实际是能够根据须要调整的,JVM 提供了参数设置:

- XX:AutoBoxCacheMax=N

这些实现,都体如今java.lang.Integer源码之中,并实如今 IntegerCache 的静态初始化块里。

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
    ....
}

第二,咱们在分析字符串的设计实现时,提到过字符串是不可变的,保证了基本的信息安全和并发编程中的线程安全。若是你去看包装类里存储数值的成员变量“value”,你会发现,不论是Integer 仍是Boolean 等其余基本类型封装类,其value都被声明为“private final”,因此,它们一样是不可变类型! 

3. 原始类型线程安全

前面提到了线程安全设计,你有没有想过,原始数据类型操做是否是线程安全的呢?

这里可能存在着不一样层面的问题:

  • 原始数据类型的变量,显然要使用并发相关手段,才能保证线程安全,这些我会在专栏后面的并发主题详细介绍。若是有线程安全的计算须要,建议考虑使用相似 AtomicInteger、AtomicLong 这样的线程安全类。
  • 特别的是,部分64位的数据类型,long、double,甚至不能保证写操做的原子性,可能出现程序读取到只更新了一半数据位的数值! 

“深刻java虚拟机”中提到,int等不大于32位的基本类型的操做都是原子操做,可是某些jvm对long和double类型的操做并非原子操做,这样就会形成错误数据的出现。

错误数据出现的缘由是: 
在32位的虚拟机中,对于long和double变量,把它们做为2个原子性的32位值来对待,而不是一个原子性的64位值, 
这样将一个long型的值保存到内存的时候,多是2次32位的写操做, 
2个竞争线程想写不一样的值到内存的时候,可能致使内存中的值是不正确的结果。

一、写入高位32位值(线程2) 
二、写入高位32位值(线程1) 
三、写入低位32位值(线程1) 
四、写入低位32位值(线程2)


这样内存中的值变成线程1的高32位值和线程2的低32位值的组合,是个错误的值。 
书中还提到,上面出现问题的long和double变量是没有声明为volatile的变量。
volatile自己不保证获取和设置操做的原子性,仅仅保持修改的可见性。

可是java内存模型保证声明为volatile的long和double变量的get和set操做是原子的。

jvm spec引用中的最后一段说到,jvm能够很轻易的将变量操做变成原子性的,可是却受到了当前硬件的约束,由于流行的微处理器仍是32bit居多,所以64bit的变量须要拆分红两次,但若是是64bit处理器就能知足64bit变量的原子性操做了。

针对这个问题,找到了网上的一个总结:

  • 对于64位的long和double,若是没有被volatile修饰,那么对其操做能够不是原子的。在操做的时候,能够分红两步,每次对32位操做;
  • 若是使用volatile修饰long和double,那么其读写都是原子操做;
  • 在实现JVM时,能够自由选择是否把读写long和double做为原子操做;
  • java中对于long和double类型的写操做不是原子操做,而是分红了两个32位的写操做。读操做是否也分红了两个32位的读呢?在JSR-133以前的规范中,读也是分红了两个32位的读,可是从JSR-133规范开始,即JDK5开始,读操做也都具备原子性;
  • java中对于其余类型的读写操做都是原子操做(除long和double类型之外);
  • 对于引用类型的读写操做都是原子操做,不管引用类型的实际类型是32位的值仍是64位的值;
  • 对于long类型的不恰当操做可能读取到从未出现过的值。而对于int的不恰当操做则可能读取到旧的值;
相关文章
相关标签/搜索