Java工具类—包装类

Java工具类——包装类

咱们都知道,JDK 其实给咱们提供了不少不少 Java 开发者已经写好的现成的类,他们其实均可以理解成工具类,好比咱们常见的集合类,日期相关的类,数学相关的类等等,有了这些工具类,你会发现它能很大程度的帮你节省时间,能很方便的实现你的需求。固然,没有这些包,你也能实现你的需求,可是你须要时间,今天咱们主要是来学习一下包装类。java

1、包装类介绍

一、为何须要包装类?

咱们知道 Java 语言是一个面向对象的编程语言,可是 Java 中的基本数据类型却不是面向对象的,可是咱们在实际使用中常常须要将基本数据类型转换成对象,便于操做,好比,集合的操做中,这时,咱们就须要将基本类型数据转化成对象,因此就出现了包装类。编程

二、包装类是什么呢?

包装类,顾名思义就是将什么通过包装的类,那么是将什么包装起来的呢,显然这里是将基本类型包装起来的类。包装类的做用就是将基本类型转成对象,将基本类型做为对象来处理。api

Java 中咱们知道,基本数据类型有8个,因此对应的包装类也是8个,包装类就是基本类型名称首字母大写。但Integer 和 Character 例外,它们显示全称,以下面表格所示:数组

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

2、包装类的继承关系

经过阅读 Java8 的 API 官方文档或者看源代码咱们能够得知8个包装类的继承关系以下:缓存

经过以上的继承关系图,咱们其实能够这样记忆,包装类里面有6个与数字相关的都是继承自 Number 类,而其他两个不是与数字相关的都是默认继承 Object 类。经过看 API 官方文档,咱们还能够得知这8个包装类都实现了Serializable , Comparable 接口。好比下图的 Integer 类oracle

public final class Integer extends Number implements Comparable<Integer> {}

3、包装类的使用方法(基本操做)

接下来关于包装类的讲解我就讲Integer包装类,其余的都依此类推,用法和操做都是差很少的,只是名字不同而已。编程语言

一、包装类的构造方法

8个包装类都有带本身对应类型参数的构造方法,其中8个包装类中除了Character还有构造方法重载,参数是String类型的。工具

Integer one = new Integer(666);
Integer two = new Integer("666");

二、包装类的自动拆装箱

在了解自动拆装箱以前,咱们得先知道什么是拆箱和装箱。其实拆装箱主要应对基本类型与包装类型的相互转换问题。学习

  • 装箱:将基本类型转换成包装类型的过程叫作装箱。code

  • 拆箱:将包装类型转换成基本类型的过程叫作拆箱。

其实,在 JDK1.5 版本以前,是没有自动拆装箱的,开发人员要手动进行装拆箱:

//手动装箱,也就是将基本类型10转换为引用类型
Integer integer = new Integer(10);
//或者
Integer integer1 = Integer.valueOf(10);

//手动拆箱,也就是将引用类型转换为基本类型
int num = integer.intValue();

而在在 JDK1.5 版本以后,为了减小开发人员的工做,提供了自动装箱与自动拆箱的功能。实现了自动拆箱和自动装箱,以下方代码所示:

//自动装箱
Integer one = 1;
//自动拆箱
int two = one + 10;

其实以上两种方式本质上是同样得,只不过一个是自动实现了,一个是手动实现了。至于自动拆装箱具体怎么实现的我这里不作深刻研究。

4、包装类的缓存机制

咱们首先来看看如下代码,例1:

public static void main(String[] args) {
  Integer i1 = 100;
  Integer i2 = 100;
  Integer i3 = new Integer(100);
  Integer i4 = new Integer(100);
  System.out.println(i1 == i2);//true
  System.out.println(i1 == i3);//false
  System.out.println(i3 == i4);//false
  System.out.println(i1.equals(i2));//true
  System.out.println(i1.equals(i3));//true
  System.out.println(i3.equals(i4));//true
}

当咱们修改了值为200的时候,例2:

public static void main(String[] args) {
  Integer i1 = 200;
  Integer i2 = 200;
  Integer i3 = new Integer(200);
  Integer i4 = new Integer(200);
  System.out.println(i1 == i2);//false
  System.out.println(i1 == i3);//false
  System.out.println(i3 == i4);//false
  System.out.println(i1.equals(i2));//true
  System.out.println(i1.equals(i3));//true
  System.out.println(i3.equals(i4));//true
}

经过上面两端代码,咱们发现修改了值,第5行代码的执行结果居然发生了改变,为何呢?首先,咱们须要明确第1行和第2行代码其实是实现了自动装箱的过程,也就是自动实现了 Integer.valueOf 方法,其次,比较的是地址,而 equals 比较的是值(这里的 eauals 重写了,因此比较的是具体的值),因此显然最后五行代码的执行结果没有什么疑惑的。既然比较的是地址,例1的第5行代码为何会是true呢,这就须要咱们去了解包装类的缓存机制。

其实看Integer类的源码咱们能够发如今第780行有一个私有的静态内部类,以下:

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() {}
}

咱们知道,静态的内部类是在整个 Integer 加载的时候就已经加载完成了,以上代码初始化了一个 Integer 类型的叫 cache 的数组,取值范围是[-128, 127]。缓存机制的做用就是提早实例化相应范围数值的包装类对象,只要建立处于缓存范围的对象,就使用已实例好的对象。从而避免重复建立多个相同的包装类对象,提升了使用效率。若是咱们用的对象范围在[-128, 127]以内,就直接去静态区找对应的对象,若是用的对象的范围超过了这个范围,会帮咱们建立一个新的 Integer 对象,其实下面的源代码就是这个意思:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

因此 例1 代码里,i1 和i2 是100,值的范围在[-128, 127],因此直接区静态区找,因此i1和i2指向的地址是同一个,因此 i1==i2;而在例2的代码里,i1 和i2 是200,值的范围不在在[-128, 127],因此分别建立了一个新的对象,放在了堆内存里,各自指向了不一样的地址,因此地址都不一样了,天然 i1 不等于 i2。

经过分析源码咱们能够发现,只有 double 和 float 的自动装箱代码没有使用缓存,每次都是 new 新的对象,其它的6种基本类型都使用了缓存策略。
使用缓存策略是由于,缓存的这些对象都是常用到的(如字符、-128至127之间的数字),防止每次自动装箱都建立一次对象的实例。

5、包装类和基本数据类型的区别

  • 默认值不一样

包装类的默认值是null,而基本数据类型是对应的默认值(好比整型默认值是0,浮点型默认值是0.0)

  • 存储区域不一样

基本数据类型是把值保存在栈内存里,包装类是把对象放在堆中,而后经过对象的引用来调用他们

  • 传递方式不一样

基本数据类型变量空间里面存储的是值,传递的也是值,一个改变,另一个不变,而包装类属于引用数据类型,变量空间存储的是地址(引用),传递的也是引用,一个变,另一个跟着变。

5、小结

​ 以上就是我对于Java包装类的我的理解,其实学习这些工具类还有一个更好的学习方式,就是去看官方文档(API官方文档地址:https://docs.oracle.com/javase/8/docs/api/)


公众号:良许Linux

有收获?但愿老铁们来个三连击,给更多的人看到这篇文章