Java基础-包装类

Java集合如Map、Set、List等所有集合只能存放引用类型数据,它们都是存放引用类型数据的容器,不能存放如int、long、float、double等基础类型的数据。这时,我们就需要将基本类型数据转化成对象!
 
 

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

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

2、继承关系

 

3、包装类的创建

Integer i = new Integer(1);

 

 

4、包装类的自动装箱和拆箱机制

实际应用中,我们需要在基本数据类型和包装类对象之间互相转换,例如:
Integer i = new Integer(1); 
int j = i.intValue();

 

为了方便使用,Java提供了自动装箱和拆箱的机制,
例如,对于如下代码:
public class Test {
    public static void main(String[] args) {
        Integer i = 1;        //自动装箱
        int j = i;        //自动拆箱
        System.out.println(i + "," + j);
    }
}

 

通过对生成的.class文件进行反编译,可以得到如下代码:
public class Test {
  public static void main(String[] paramArrayOfString) {
    Integer integer = Integer.valueOf(1);
    int i = integer.intValue();
    System.out.println(integer + "," + i);
  }
}

可以看到,JAVA的编译器自动帮我们完成了装箱和拆箱的操作

 

5、包装类中的缓存机制

创建包装类对象有两种方式,new关键字、valueOf()方法
有如下代码:
public class Test {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = new Integer(127);
        Integer i4 = new Integer(127);
        Integer i5 = 200;
        Integer i6 = 200;

        System.out.println(i1.equals(i2));  //1、true
        System.out.println(i1 == i2);       //2、true
        System.out.println(i3.equals(i4));  //3、true
        System.out.println(i3 == i4);       //4、false
        System.out.println(i5.equals(i6));  //5、true
        System.out.println(i5 == i6);       //6、false
    }
}

 

是不是看着很惊讶,我们看看反编译后的代码:
public class Test {
  public static void main(String[] paramArrayOfString) {
    Integer integer1 = Integer.valueOf(100);
    Integer integer2 = Integer.valueOf(100);
    Integer integer3 = new Integer(127);
    Integer integer4 = new Integer(127);
    Integer integer5 = Integer.valueOf(200);
    Integer integer6 = Integer.valueOf(200);
    System.out.println(integer1.equals(integer2));
    System.out.println((integer1 == integer2));
    System.out.println(integer3.equals(integer4));
    System.out.println((integer3 == integer4));
    System.out.println(integer5.equals(integer6));
    System.out.println((integer5 == integer6));
  }
}

 

我们来看一下Integer的valueOf()方法:
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

 

看一下其中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() {}
}

 

我们可以看到,使用默认参数i时,存在以下两种情况:
  • 当valueOf()的参数值i在[-128,128)之间时,返回的是IntegerCache.cache[i+128]
  • 当i >= 128 || i < -128 =====> new Integer(i) 
IntegerCache.cache本来已经创建好,也就是说,也就是说在i >= 128 || i < -128是会创建不同的对象,在i < 128 && i >= -128会根据i的值返回已经创建好的指定的对象。
这就可以解释上述代码中结果2、4、6的结果为什么分别是true、false和false。由于100在[-128,128)之间,200不在这个范围间,i1和i2引用了同一个对象,所以相等,i3和i4,i5和i6分别创建了一个新的对象,所以不相等。
至于为什么equals相等,我们看equals的定义:
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

public int intValue() {
    return value;
}

 

可以看到它们直接比较的是值,因此相等。
 
 
看看另外一个例子:
public static void main(String[] args) {
    Double i1 = 100.0;
    Double i2 = 100.0;
    Double i3 = 200.0;
    Double i4 = 200.0;

    System.out.println(i1==i2);          //false
    System.out.println(i1.equals(i2));   //true
    System.out.println(i3==i4);          //false
    System.out.println(i3.equals(i4));   //true
}

 

上面的执行结果,和Integer不一样,我们看valueOf()的实现:
public static Double valueOf(double d) {
    return new Double(d);
}

 

可以看到,它们的valueOf实现不一样,结果肯定不一样,那为什么它们不统一一下呢? 
这个很好理解,因为对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。
但是对于Double类型来说,我们就不能这样做,因为它在这个范围内个数是无限的。 
总结一句就是:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
归类如下: 
  • Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 
  • Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象。
具体可以去看实现的方法。
 
另一种情况下,
public class Main {
    public static void main(String[] args) {
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
        System.out.println(i1==i2);//true
        System.out.println(i3==i4);//true
    }
}

 

可以看到返回的都是true,也就是它们执行valueOf返回的都是相同的对象。
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

 

可以看到它并没有创建对象,因为在内部已经提前创建好两个对象,因为它只有两种情况,这样也是为了避免重复创建太多的对象。
 

6、包装类的运算

如下代码:
public class Test {
    public static void main(String[] args) {
        Integer num1 = 100;
        int num2 = 100;
        Long num3 = 200L;
        System.out.println(num1 + num2);               //200
        System.out.println(num3 == (num1 + num2));     //true
        System.out.println(num3.equals(num1 + num2));  //false
    }
}

 

反编译结果如下:
public class Test {

   public static void main(String[] var0) {
      Integer var1 = Integer.valueOf(100);
      byte var2 = 100;
      Long var3 = Long.valueOf(200L);
      System.out.println(var1.intValue() + var2);
      System.out.println(var3.longValue() == (long)(var1.intValue() + var2));
      System.out.println(var3.equals(Integer.valueOf(var1.intValue() + var2)));
   }
}

 

我们可以看到, 当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
 
总结:
  • 当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
  • equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱

7、基本数据类型在集合中的应用

代码如下
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add(new Object());
        Iterator it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

 

反编译结果如下:
import java.util.ArrayList;
import java.util.Iterator;

public class Test {

   public static void main(String[] var0) {
      ArrayList var1 = new ArrayList();
      var1.add(Integer.valueOf(1));
      var1.add(new Object());
      Iterator var2 = var1.iterator();

      while(var2.hasNext()) {
         System.out.println(var2.next());
      }

   }
}

 

可以发现,虽然集合元素要求是对象,add()方法的形参也是对象(public boolean add(E e)),但由于自动装箱,基本数据类型也可以直接加入集合中。

8、使用中的空指针异常

如下代码:
public class Test {
    public static void main(String[] args) {
        Integer num = null;
        int int100 = num;
    }
}

 

这段代码在编译的时候是可以通过的,但是在执行的时候,会报空指针异常
其中,num为Integer类型的对象,它指向null。但在下一行时,就会对num进行拆箱,也就是对一个null对象执行intValue()方法,当因此会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。
 

9、为什么需要包装类?有了包装类又为什么要保留基本数据类型?

为什么需要包装类?
 
首先,Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,将每个基本数据类型设计一个对应的类进行代表,这种方式增强了Java面向对象的性质。
 
其次,如果仅仅有基本数据类型,那么在实际使用时将存在很多的不便,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的,因为集合的容器要求元素是Object类型。而包装类型的存在使得向集合中传入数值成为可能,包装类的存在弥补了基本数据类型的不足。
 
此外,包装类还为基本类型添加了属性和方法,丰富了基本类型的操作。如当我们想知道int取值范围的最小值,我们需要通过运算,如下面所示,但是有了包装类,我们可以直接使用Integer.MAX_VALUE即可。
 
为什么要保留基本数据类型?
 
在Java语言中,用new关键字创建的对象是存储在堆里的,我们通过栈中的引用来使用这些对象,所以,对象本身来说是比较消耗资源的。对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个对象的话,就会比较笨重了。所以,Java提供了基本数据类型,这种数据的变量不需要使用new在堆上创建,而是直接在栈内存中存储,因此会更加高效。

posted @ 2021-09-15 17:57  r1-12king  阅读(75)  评论(0编辑  收藏  举报