JAVA的装箱类型初探

PS:三天后朱健迎来他的第一场面试,希望他能得到他想要的结果。好了不说他了,我们干我们的。

什么是装箱类?我们为什么要装箱?

        首先我们知道,JAVA存在8中基本类型,4中引用类型。

byte short int long

float double

char boolean


StrongReference SoftReference 

WeakReference    PhantomReference

        对象(也就是引用数据类型)相对于基本数据类型更加高级,它不但可以存储数据还可以存储方法还可以继承还可以封装………那强调万物皆对象的JAVA,为什么还需要int,float这些基本类型呢。下面这段话是在论坛看到的,说得真的很好,插一下。

按理说C#被设计成一种完全面向对象的语言。因此,包括数字、字符、日期、布尔值等等在内的一切,都是对象。似乎只需要一种方式来对待这些对象就可以了。

但是C#不是只停留在学院中和理想中,它必须为性能而妥协,我们知道,对于CPU来说,处理一个完整的对象,需要很多的指令,对于内存来说,又需要很多的内存。如果连整数都是对象,那么性能自然很低。C#于是使用了一种机制,使得这些基本类型在一般的编程中被当作非对象的简单类型处理,在另一些场合,又允许它们被视作是一个对象。这种机制就是装箱和拆箱。

装箱后的对象看上去和一个对象一样,拥有方法,可以当作object处理,拆箱后的变量,看上去又如同C语言中的那些变量、结构体一样,可以直接参与运算和处理。

        JAVA也是一样的,一句话概括装箱拆箱

自动根据数值类型创建对应的对象,此为装箱(boxing)

自动根据装箱(类型)转变为基本类型,此为拆箱(unBoxing)

基本类型    ---    装箱类型

  int   ---  Integer

  short  ---  Short

  long   ---  Long

  double ---  Double

  float  ---  Float

  boolean ---  Boolean

  byte   ---  Byte

  char   ---  Character

如何编写装箱类型,它怎么用的。

Integer i = 1; //自动装箱

int j = i;     //自动拆箱


public void argAutoBoxing(Integer i) {

}

argAutoBoxing(1);  //自动装箱

public void argAutoUnBoxing(int i) {

}

argAutoUnBoxing(new Integer(1));    //自动拆箱

//我们将只有存储作用的基本类型变为装箱类型的对象后,可以使用背后的`方法`相互转化
//这个用法非常常见,也是主要功能
String year = "2018";
int i = Integer.parseInt(year);
System.out.println(i/2);

注意!Integer i = new Integer(xx)Integer i = xx是不一样的,这个后面再提及

什么时候使用int,什么时候用Integer?

我们知道的是,在堆内存创建对象的消耗比起使用栈内存是要大的 ,如果是在数据量较大或是循环次数较多的情况下,对性能的影响是非常大的。若非是特殊需求,一般使用基本类型而不是装箱类型。

1
2
3
4
5
6
7
8
9
10
11

Integer sum = 0;

for(int i =0;i<=2000;i++){
sum += i;
}

这里的 sum += i 等价为 sum = sum + i;

JVM检测到右边为`表达式`,则自动触发拆箱 sum = sum.intValue() + i;
JVM检测到左边为`装箱类`,则自动触发装箱 Integer sum = new Integer(result);

sum为Integer类型,在上面的循环中会创建2000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题

拆装箱的内部原理是什么?

我们上面提到的自动拆装箱为例

1
2
3
4
5
6

Integer i = 1; //自动装箱 JVM调用Integer类的valueOf()方法

int j = i; //自动拆箱 JVM调用Integer类的intValue()方法

//其他类型类推

让我们看看valueOf()intValue()两个方法的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

通过valueOf()方法创建Integer对象的时候,如果数值在符合条件,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

1
2
3
4
5
6
7
8

/**
* Returns the value of this {@code Integer} as an
* {@code int}.
*/
public int intValue() {
return value;
}

通过intValue()方法将参数转化为基本类型并返回。

练习&拓展

下面是搜集的一些关于装箱类的一些基础面试题,让我们通过这些试题来练习一下也拓展之前并没有提及的知识点。

友情提示

  • 对于两边都是包装类型的比较==比较的是引用,equals比较的是值。
  • 对于两边有一边是表达式(包含算数运算)则==比较的是数值(自动触发拆箱过程),对于包装类型equals方法不会进行类型转换。

1.

1
2
3
4
5
6
7
8
9
10
11
12
13

public class Main {
public static void main(String[] args) {

Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;

System.out.println(a==b);
System.out.println(c==d);
}
}

这里得到的输出应是 true 和 false ,
通过观察上面提到的装箱发放valueOf()方法,并再仔细观看里面嵌入的另一个关键方法IntegerCache()我们发现,可以进一步将进一步解读ValueOf()为

valueOf方法,参数如果是-128~127之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象

2.

1
2
3
4
5
6
7
8
9
10
11
12
13

public class Main {
public static void main(String[] args) {

Double a = 100.0;
Double b = 100.0;
Double c = 200.0;
Double d = 200.0;

System.out.println(a==b);
System.out.println(c==d);
}
}

我们会发现得到的输出都是false,是因为不同的数据类型有不同的valueOf()实现方式。
通过查阅文档和编写实验,可以得出一个结论:

对于Integer、Short、Byte、Character、Long类型的valueOf方法,参数如果是-128~127之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象;

而Double、Float类型与Integer类型类似,一样会调用Double、Float的valueOf方法,但是不管传入的参数值是多少都会new一个对象来表达该数值,因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。

3.

1
2
3
Boolean i = null;

boolean j = i;

这里,会得到一个NullPointerException异常。虽然自动拆装箱非常方便,但千万不要同等对待了。 对象Boolean里有 true false null ,基本类型boolean里可没有null

4.

1
2
3
4
5
6
public static void main(String[] args) {
Integer i =new Integer(12);
Integer j = 12;

System.out.println(i == j);
}

照理来说,通过练习1我们应该推测这里得到的输出应该是true,12并没有超出缓存范围。但通过实践得到的输出确是false。这是为什么呢?

因为,new Integer(xxx)这种创建对象的方法不是自动装箱,没有用到cache。两个对象自然指向的不是一个区域的值。

5.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class Main {
public static void main(String[] args) {

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;

System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}

这个题我就不讲解,结合前面的知识点来练习就可以得到相应的正确输出,也可以真正了解到==equals的区别。

  1. 装箱类的应用十分广泛,基础的知识掌握是必须的。
  2. 总结,是一件快乐的事。
  3. 下周循环!