JAVA--------Int 和Integer的理解

int

int 是我们常说的整形数字,是 Java 的 8 个原始数据类型(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(和源代码配套的API帮助文档),这个值默认缓存是 -128 到 127 之间。

 Integer 的源码

 

  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() {}
    }
  • IntegerCache是Integer的一个内部类,它包含了int可能值的Integer数组,默认范围是[-128,127],它不会像Byte类将所有可能值缓存起来,因为int类型范围很大,将它们全部缓存起来代价太高,而Byte类型就是从-128到127,一共才256个。所以这里默认只实例化256个Integer对象,当Integer的值范围在[-128,127]时则直接从缓存中获取对应的Integer对象,不必重新实例化。这些缓存值都是静态且final的,避免重复的实例化和回收。另外我们可以改变这些值缓存的范围,再启动JVM时通过-Djava.lang.Integer.IntegerCache.high=xxx就可以改变缓存值的最大值,比如-Djava.lang.Integer.IntegerCache.high=1000则会缓存[-128,1000]。
  • 因为字符串的设计实现时,提到过字符串是不可变的,保证了基本的信息安全和并发编程中的线程安全.在0包装类里存储数值的成员变量“value”,你会发现,不管是 Integer 还 Boolean 等,都被声明为“private final”,所以,它们同样是不可变类型! 这种设计是可以理解的,或者说是必须的选择.想象一下这个应用场景,比如 Integer 提供了 getInteger() 方法,用于方便地读取系统属性,我们可以用属性来设置服务器某个服务的端口, 如果我可以轻易地把获取到的 Integer 对象改变为其他数值,这会带来产品可靠性方面的严重问题

  • Integer 等包装类,定义了类似 SIZE 或者 BYTES 这样的常量,这反映了什么样的设计考虑呢?如果你使用过其他语言,比如 C、C++,类似整数的位数,其实是不确定的,可能在不同的平台,比如 32 位或者 64 位平台,存在非常大的不同。那么,在 32 位 JDK 或者 64 位JDK 里,数据位数会有不同吗?或者说,这个问题可以扩展为,我使用 32 位 JDK 开发编译的程序,运行在 64 位 JDK 上,需要做什么特别的移植工作吗?其实,这种移植对于 Java 来说相对要简单些,因为原始数据类型是不存在差异的,这些明确定义在Java 语言规范里面,不管是 32 位还是 64 位环境,开发者无需担心数据的位数差异。对于应用移植,虽然存在一些底层实现的差异,比如 64 位 HotSpot JVM 里的对象要比 32 位HotSpot JVM 大(具体区别取决于不同 JVM 实现的选择),但是总体来说,并没有行为差异,应用移植还是可以做到宣称的“一次书写,到处执行”,应用开发者更多需要考虑的是容量、能力等方面的差异。

 

原始类型线程安全


对于线程安全设计,原始数据类型操作是不是线程安全的呢?
这里可能存在着不同层面的问题:

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

.Java 原始数据类型和引用类型局

  • 原始数据类型和 Java 泛型并不能配合使用

这是因为 Java 的泛型某种程度上可以算作伪泛型,它完全是一种编译期的技巧,Java 编译期会自动将类型转换为对应的特定类型,这就决定了使用泛型,必须保证相应类型可以转换为Object

  • 无法高效地表达数据,也不便于表达复杂的数据结构,比如 vector 和 tuple

我们知道 Java 的对象都是引用类型,如果是一个原始数据类型数组,它在内存里是一段连续的内存,而对象数组则不然,数据存储的是引用,对象往往是分散地存储在堆的不同位置.这种设计虽然带来了极大灵活性,但是也导致了数据操作的低效,尤其是无法充分利用现代 CPU 缓存机制.ava 为对象内建了各种多态,线程安全等方面的支持,但这不是所有场合的需求,尤其是数据处理重要性日益提高,更加高密度的值类型是非常现实的

 

装箱与拆箱

装箱:将基本数据类型变为包装类对象,利用每个包装类提供的构造方法实现装箱处理。

拆箱:将包装类中包装的基本数据类型的值取出。

JDK1.5之后提供了自动拆装箱机制,使用包装类对象就和使用基本数据类型一模一样

对于Integer var = ?(自动装箱) ?在-128-127之间的赋值,Integer对象在Integer常量池产生,会复用已有对象。在这个区间外的所有数据在堆上产生,不会复用已有对象。

自动装箱实际上算是一种语法糖。语法糖可以简单理解为 Java 平台为我们自动进行了一些转换,保证不同的写法在运行时等价,它们发生在编译阶段,也就是生成的字节码是一致的。

问题

1.判断两个包装类对象是否相等,

  • 使用equals方法比较

2.自动装箱 / 自动拆箱 是发生在什么阶段?

  • ,它们发生在编译阶段,也就是生成的字节码是一致 的. 像前面提到的整数,javac 替我们自动把装箱转换为 Integer.valueOf(),把拆箱替换为 Integer.intValue(),这似乎这也顺道回答了另一个问题,既然调用的是 Integer.valueOf,自然 能够得到缓存的好处啊

对下面代码进行编译(javac  类名),然后再反编译(Javap  -v  类名)

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

反编译结果:

intintValue()

Integer的值作为 int

static IntegervalueOf(int i)

返回一个 Integer指定的 int值的 Integer实例。

static IntegervalueOf(String s)

返回一个 Integer对象,保存指定的值为 String

static IntegervalueOf(String s, int radix)

返回一个 Integer对象,保存从指定的String中 String的值,当用第二个参数给出的基数进行解析时。

包装类

包装类就是将基本数据类型封装到类中。

Java中的包装类
数值型包装类(Number类的子类):NumberFormatException
Byte、Double、Short、Long、Float、Integer(int)

对象型包装类(Object类的直接子类)
Boolean、Character(char)

这种缓存机制并不是只有 Integer 才有,同样存在于其他的一些包装类,比如:

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

自动拆箱和装箱的注意问题:

  • 原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,如创建 10 万个 Java 对象和 10 万个整数的开销可不是一个数量级的,不管是内存使用还是处理速度,光是对象头的空间占用就已经是数量级的差距了。
  • 如果使用原始数据类型、数组甚至本地代码实现等,在性能极度敏感的场景往往具有比较大的优势,用其替换掉包装类、动态数组(如 ArrayList)等可以作为性能优化的备选项。一些追求极致性能的产品或者类库,会极力避免创建过多对象。

补充:

对象头

对象在内存中存储的布局可以分为3块区域:对象头(Header),实 例数据(Instance Data)和对齐填充(Padding‘’

HotSpot虚拟机的对象头包括两部分信息,

  • 第一部分用于存储对象自身的运行时数据,如哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为"Mark Word
  • 对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来 确定这个对象是哪个类的实例.并不是所有的虚拟机实现都必须在对象数据上保留类型指 针,换句话说,查找对象的元数据信息并不一定要经过对象本身,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为 虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中却 无法确定数组的大小.

JVM一般是这样使用锁和Mark Word的:

1,当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。

2,当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。

3,当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。

4,当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。

5,偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。

6,轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。

7,自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。

 

第二部分实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的 字段内容.无论是从父类继承下来的,还是在子类中定义的,都需要记录起来.

第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用.由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍

 

Integer和int的注意事项
[1] 基本类型均具有取值范围,在大数*大数的时候,有可能会出现越界的情况。

[2] 基本类型转换时,使用声明的方式。例:long result= 1234567890 * 24 * 365;结果值一定不会是你所期望的那个值,因为1234567890 * 24已经超过了int的范围,如果修改为:long result= 1234567890L * 24 * 365;就正常了。

[3] 慎用基本类型处理货币存储。如采用double常会带来差距,常采用BigDecimal、整型(如果要精确表示分,可将值扩大100倍转化为整型)解决该问题

[4] 优先使用基本类型。原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,

[5] 如果有线程安全的计算需要,建议考虑使用类型AtomicInteger、AtomicLong 这样的线程安全类。部分比较宽的基本数据类型,比如 float、double,甚至不能保证更新操作的原子性,可能出现程序读取到只更新了一半数据位的

 

到底选择包装类还是基本类型?

1.强制要求:所有POJO类(自己定义的Java类)的属性均使用包装类
2.推荐:所有局部变量使用基本类型

字符串与包装类型数据类型的转换

  • String -> 基本类型

使用包装类提供的parseXXX方法
eg: Integer.parseInt("123");

  • NumberFormatException产生原因:

存在非数字的字符串转为数值类型
eg:Integer.parseInt("123a");

以后在进行字符串与数值转换时,首先判断字符串是否由纯数字组成。

基本类型->String

  • I.""+基本类型
  • II.调用String.valueOf(各种数据类型)
     

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值