包装类

873 阅读6分钟

    Java是一种面向对象语言,Java中的类把方法与数据连接在一起,构成了自包含式的处理单元。但是Java中不能定义基本类型对象,在对基本类型数据进行操作时便会出现大量重复的自定义方法。为了能将基本类型视为对象进行处理,并连接相关的方法,Java为所有的基本类型都提供一个与之对应的包装类(wrapper)。Java中所有的包装类都存在于java.lang包中。

对于包装类,有以下两点需要注意:

  • 包装类为final类,不允许扩展,因此不能定义它们的子类。
  • 包装类中包装的基本类型的数据为常量,一旦构造了包装类,就不允许更改包装在其中的值。
基本类型-对应包装器
基本类型 byte short int long float double char boolean
对应的包装类 Byte Short Integer Long Float Double Character Boolean

    其中,六种数值类型(四个整型,两个浮点型)对应的包装类都派生于公共的超类java.lang.NumberNumber为抽象类,没有具体的方法实现。

    另外,除了以上基本类型对应的包装类。Java还提供BigIntegerBigDecimal这两个包装类,它们没有对相对应的基本类型,主要应用于高精度的运算,BigInteger支持任意精度的整数,BigDecimal支持任意精度带小数点的运算。

装箱和拆箱

基本类型数据转换成对应包装类对象称为装箱;

包装类对象转换成对应基本类型数据称为拆箱。

下面通过int类型和对应包装类Integer演示装箱、拆箱操作:

  1. 装箱

    • 自动装箱
    int a=3;
    Integer t=a;
    
    • 手动装箱
    int a=3;
    Integer t=new Integer(a);
    
  2. 拆箱

    • 自动拆箱
    Integer b=new Integer(4);
    int t=b;
    
    • 手动拆箱
    Integer b=new Integer(4);
    int t=b.intValue();
    

    对自动装箱和自动拆箱操作进行反编译,得出下图结果:

    {% asset_img 反编译结果.png %}

        由此可知,自动拆箱操作其实是隐式地执行int t=b.intValue();自动装箱操作其实是隐式地执行Integer t=Integer.valueOf(a)

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

注意:为了避免频繁的创建和销毁对象而影响系统性能,自动装箱规范要求booleanbytechar<=127,介于[-128,127]之间的shortint被包装到固定的对象中,存储在一个缓存空间(称为对象常量池)中,从而实现了对象的共享。故当参数介于[-128,127]之间时会直接引用对象常量池中的对象。

Integer three=100;
System.out.println("three==100的结果:"+(three==100));

Integer four=100;
System.out.println("three==four的结果:"+(three=four));

运行结果如下:

{% asset_img 运行结果.png %}

    第一个结果true是因为包装类对象three进行了自动拆箱,从而变成了两个基本类型数据100和100之间的比较。

    第二个true是因为Java对象常量池的实现,从而使three和four引用了对象常量池中的同一个对象。

注意:八种基本数据类型中,只有floatdouble对应的包装类没有应用对象常量池这个概念。Java的这种实现提高了效率,但是可能会让==得出我们不希望的结果,所以在对于两个包装类对象的比较时应调用equals方法。如果两个或其中一个包装类对象都是通过手动装箱(显式使用new关键字)创建的,那它们的存储地址肯定不一样,==比较的结果也为false;因为引用类型,==比较的是两个变量存储的对象引用是否相等,则实例对象的存储地址是否一致。

String中的==和equals的区别

public class StringDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String a = "abc";
		String b = "abc";
		String c = new String("abc");
		System.out.println("a和b的内容是否相同:" + a.equals(b));
		System.out.println("a和c的内容是否相同:" + b.equals(c));

		System.out.println("a和b的地址是否相同:" + (a == b));
		System.out.println("a和c的地址是否相同:" + (b == c));
	}

}

这令我不能不想到包装类的对象常量池概念,但是字符串那么多组合方式感觉又不大可能。

    第一种方式是在常量池中拿对象,如果找不到就在常量池中创建一个字符串;第二种方式是直接在堆内存空间创建一个新的对象。目前获得的信息暂时是这样子,进一步了解之后再来更新。

基本数据类型和字符串之间的装换

  1. 基本数据类型转换成字符串。采用静态方法toString()

    int t1=2;
    String t2=Integer.toString(2);
    
  2. 字符串转换为基本数据类型。

    • 采用静态方法parseInt()

      int t3=Integer.parseInt(t2);
      
    • 采用静态方法valueOf()

      int t4=Integer.valueOf(t2);
      

包装类与基本数据类型的区别

主要可以从以下几个方面来说明:

显而易见的区别:

Java中只有八种基本类型不是对象,其余都是对象(包括包装类)。

声明方式的不同:

  1. 基本类型无需通过new关键字来创建。
  2. 包装类对象需要通过new关键字来创建;即便是自动装箱操作,其底层原理也是通过new关键字创建的对象。

作为成员变量时的默认初始值:

  1. 基本数据类型

    • 数值类型

      • 整型:包括byteintshort初始值为0;long初始值为0L。
      • 浮点型:float初始值为0.0f;double初始值为0.0d。
    • 字符类型:char初始值为'\u0000'

    • 布尔类型:boolean初始值为false

  2. 包装类

    • 与其他的引用类型一样,默认初始值为null

使用上的不同:

集合等泛型机制中的类型变量只能是包装类或其他引用类型,而不能是基本数据类型。

存储方式及位置的不同:

  1. 基本类型变量是直接存储变量的值保存在栈中,能高效地进行存取。
  2. 包装类变量需要通过引用指向实例,具体的实例保存在堆中。