java并不是纯面向对象的语言,java语言是一个面向对象的语言,但是java中的基本数据类型却不是面向对象的,但是我们在实际使用中经常将基本数据类型转换成对象,便于操作,比如,集合的操作中,这时,我们就需要将基本类型数据转化成对象!

包装类和基本数据类型的关系

基础数据类型 包装类型
byte Byte
boolean Boolean
short Short
char Character
int Integer
long Long
float Float
double Double

下面是包装类型的继承结构图
image.png

包装类的基本操作

1.png

  1. public class TestWrappedClass {
  2. public static void main(String[] args) {
  3. //基本数据类型转成包装类对象
  4. Integer a = new Integer(3);
  5. Integer b = Integer.valueOf(30);
  6. //把包装类对象转成基本数据类型
  7. int c = b.intValue();
  8. double d = a.doubleValue();
  9. //把字符串转成包装类对象
  10. Integer e = Integer.valueOf("31134");
  11. Integer f = new Integer("3344");
  12. Integer h = Integer.parseInt("34113");
  13. //把包装类型对象转成字符串
  14. String str = f.toString();
  15. //常见的常量
  16. System.out.println("int类型最大值 " +Integer.MAX_VALUE);
  17. System.out.println("int类型最小值 " +Integer.MIN_VALUE);
  18. }
  19. }

包装类与基本类型的转换

从源代码的角度来看,基础类型和包装类型都可以通过赋值语法赋值给对立的变量类型,如下面的代码所示。

Integer a = 1;
int a = new Integer(1);

这种语法是可以通过编译的。但是,Java作为一种强类型的语言,对象直接赋值给引用类型变量,而基础数据只能赋值给基本类型变量,这个是毫无异议的。
那么基本类型和包装类型为什么可以直接相互赋值呢?这其实是Java中的一种“语法糖”。“语法糖”是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会(来自百度百科)。
换句话说,这其实是一种障眼法,那么实际上是怎么样的呢?下面是Integer a = 1;语句编译的字节码。

0: iconst_1
1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1

首先,生成一个常量1,然后调用Integer.valueOf(int)方法返回Integer对象,
最后将对象的地址(引用)赋值给变量a。Integer a = 1其实相当于Integer a = Integer.valueOf(1)

“莫名其妙”的NullPointException

经常可能碰到过不少因为请求参数或者接口定义字段设置为int(或者其他基本类型)而导致NullPointException。
代码大致地运行步骤如下所示,当然不会跟这个完全一样

Integer a = null;
...
int b = a; // 抛出NullPointException

上面的代码可以编译通过,但是会抛出空指针异常(NullPointException)。前面已经说过了,int b = a实际上是int b = a.intValue(),由于a的引用值为null,在空对象上调用方法就会抛出NullPointException。

自动装箱、拆箱

Java 5 增加了自动装箱、拆箱机制,提供基本数据类型和包装类型的相互转换操作。
自动装箱
自动装箱即自动将基本数据类型转换成包装类型,在 Java 5 之前,要将基本数据类型转换成包装类型只能这样做,看下面的代码。

Integer i1 = new Integer(8);
Integer i2 = Integer.valueOf(8);
// 自动装箱
Integer i3 = 8;

以上 3 种都可以进行转换,但在 Java 5 之前第 3 种方法是编译失败的,第 3 种方法也正是现在的自动装箱功能。
另外,第一种构造器方法也不推荐使用了,已经标为废弃了。
其实自动装箱的原理就是调用包装类的 valueOf 方法,如第 2 个方法中的 Integer.valueOf 方法。

自动拆箱**
自动拆箱即自动将包装类型转换成基本数据类型,与自动装箱相反,有装就有拆,很好理解。

// 自动拆箱
int i4 = i3;
int i5 = i3.intValue();

继续上面的例子,把 i3 赋值给 i4 就是实现的自动拆箱功能,自动装箱的原理就是调用包装类的 xxValue 方法,
如 i5 中的 Integer 的 intValue 方法。
自动装箱、拆箱不只是体现在以上的例子,在方法接收参数、对象设置参数时都能自动装箱拆箱。

IntegerCache

在Java中,“==”符号判断的内存地址所对应的值得相等性,具体来说,基本类型判断值是否相等,引用类型判断其指向的地址是否相等。看看下面的代码,两种类似的代码逻辑,但是得到截然不用的结果。

Integer a1 = 1;
Integer a2 = 1;
System.out.println(a1 == a2); // true

Integer b1 = 222;
Integer b2 = 222;
System.out.println(b1 == b2); // false

这个必须从源代码中才能找到答案。Integer类中的valueOf()方法的源代码如下:

public static Integer valueOf(int i) {
            // 判断实参是否在可缓存范围内,默认为[-128, 127]
    if (i >= IntegerCache.low && i <= IntegerCache.high) 
        // 如果在,则取出初始化的Integer对象
        return IntegerCache.cache[i + (-IntegerCache.low)]; 
    // 如果不在,则创建一个新的Integer对象
    return new Integer(i); 
}

由于1属于[-128, 127]集合范围内,所以valueOf()每次都会取出同一个Integer对象,故第一个“”判断结果为true;而222不属于[-128, 127]集合范围内,所以valueOf()每次都会创建一个新的Integer对象,由于两个新创建的对象的地址不一样,故第一个“”判断结果为false。

XCache

是否有Cache 最小值 最大值
Boolean
Byte ByteCache -128 127(固定)
Short ShortCache -128 127(固定)
Character CharacterCache 0 127(固定)
Integer IntegerCache -128 java.lang.Integer.IntegerCache.high
Long LongCache -128 127(固定)
Float
Double

java.lang.Integer.IntegerCache.high

image.png
IntegerCache类获取high的方法sun.misc.VM.getSavedProperty

  1. 这个值如何如何传递到JVM中?
    和系统属性一样在JVM启动时,通过设置-Djava.lang.Integer.IntegerCache.high=xxx传递进来。

  2. 这个方法和System.getProperty有什么区别?
    为了将JVM系统所需要的参数和用户使用的参数区别开,
    java.lang.System.initializeSystemClass在启动时,会将启动参数保存在两个地方:

2.1 sun.misc.VM.savedProps中保存全部JVM接收的系统参数。
JVM会在启动时,调用java.lang.System.initializeSystemClass方法,初始化该属性。
同时也会调用sun.misc.VM.saveAndRemoveProperties方法,从java.lang.System.props中删除以下属性:

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag

以上罗列的属性都是JVM启动需要设置的系统参数,所以为了安全考虑和隔离角度考虑,将其从用户可访问的System.props分开。

2.2 java.lang.System.props中保存除了以下JVM启动需要的参数外的其他参数。

  • sun.nio.MaxDirectMemorySize
  • sun.nio.PageAlignDirectMemory
  • sun.lang.ClassLoader.allowArraySyntax
  • java.lang.Integer.IntegerCache.high
  • sun.zip.disableMemoryMapping
  • sun.java.launcher.diag

Java 9的IntegerCache
幻想一下,如果以上淘气的玩法出现在第三方的依赖包中,绝对有一批程序员会疯掉(请不要尝试这么恶劣的玩法,后果很严重)。
庆幸的是Java 9对此进行了限制。可以在相应的module中编写module-info.java文件,限制了使用反射来访问成员等,按照需要声明后,代码只能访问字段、方法和其他用反射能访问的信息,只有当类在相同的模块中,或者模块打开了包用于反射方式访问。详细内容可参考一下文章:
在 Java 9 里对 IntegerCache 进行修改?