Java包装类

2.1 包装类的作用

(1)基本数据类型的存在意义

  我们都知道在Java语言中,new一个对象存储在堆里,我们通过栈中的引用来使用这些对象。但是对于经常用到的一系列类型如int、boolean…如果我们用new将其存储在堆里就不是很高效——特别是简单的小的变量。所以,同C++一样Java也采用了相似的做法,决定基本数据类型不是用new关键字来创建,而是直接将变量的值存储在栈中,方法执行时创建,结束时销毁,因此更加高效。所以基本数据类型的存在意义是为了运行效率

(2)为什么要引入包装类

  Java是一门面向对象的编程语言,但是Java中的基本数据类型却不是面向对象的,并不具有对象的性质,这在实际生活中存在很多的不便。为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得具有了面向对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作,方便涉及到对象的操作。所以,引入包装类是为了方便对基本类型进行操作

  在使用集合类型时,就一定要使用包装类型,因为容器都是装object的,基本数据类型显然不适用。

2.2 包装类的原理

2.2.1 包装类

  Java设计当初就提供类8种基本数据类型及对应的8 种包装类(封装类);

基本数据类型包装类
byteByte
booleanBoolean
shortShort
charCharacter
intInteger
longLong
floatFloat
doubleDouble

2.2.2 装箱和拆箱

 下面均以int,Integer为例进行讲解;

  • 装箱:基本数据类型转换为包装类;装箱有两种方法:
//装箱两种方法
Integer i1 = new Integer(8);
Integer i2 = Integer.valueOf(8);

// 自动装箱,会隐式调用valueOf方法
Integer i3 = 8;
fun(8);

void fun( Integer a){
    //...
}
  • 拆箱:包装类转换为基本数据类型;拆箱方法:
//拆箱方法
int i5 = i3.intValue();

//自动拆箱,会隐式调用intValue方法
int i4 = i3;
test(i3);
void test(int a){
    //...
}

2.2.3 缓存池

 上一节说到,装箱有两种方法:new Integer(8)Integer.valueOf(8);他们的区别在于:

  • new Integer(8)每次调用创建一个新对象;
  • Integer.valueOf(8)使用缓存池中的对象,多次调用会取得同一个对象的引用(可复用)。

 下面看一下Integer的源码,分析一下valueOf和缓存池;

 valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];//1
    return new Integer(i);//2
}
Integer i6 = 127;//自动装箱,值在缓存池范围内,调用valueOf,执行1,i6是缓存池单元的引用(地址);
Integer i7 = 128;//自动装箱,值在缓存池范围外,调用valueOf,执行2,最终调用newInteger,i7是堆单元的引用;

 在 Java 8 中,Integer 缓存池的大小默认为 -128~127,和byte范围相同;

    private static class IntegerCache {
        static final int low = -128;//Integer缓存池最小值
        static final int high;//Integer缓存池最大值
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;//Integer缓存池最大值
            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;//Integer缓存池最大值
            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() {}
    }

2.3.4 封装类对象的比较

封装类对象的比较强制使用equals(源自阿里编码规范);

public class Test {  
    public static void main(String[] args) {  
        Integer i1 = new Integer(127);
        Integer i2 = Integer.valueOf(127);
        Integer i3 = 127;  
        Integer i4 = 127;  
        Integer i5 = 128;  
        Integer i6 = 128;  
        int i7 = 127;
        System.Out.println(i1 == i2); //1false;
        System.Out.println(i2 == i3); //2true
        System.Out.println(i3 == i4); //3true
        System.Out.println(i5 == i6); //4false 
        System.Out.println(i5.equals(i6)); //5true 
        System.Out.println(i1 == i7);//true
        System.Out.println(i3 == i7);//true
    }  
} 

 首先声明==和equals的区别:

  • equals比较对象的内容(所有属性值);
  • ==如果是基本数据类型比较值,如果是对象比较内存地址;

 (1)i1是堆中新开辟一块内存的地址;i2是堆中缓存池某一单元的地址;1输出false;

 (2)自动装箱,值在缓存池范围内,调用valueOf,i2、i3和i4是同一缓存池单元的引用(地址);2,3输出true;

 (3)自动装箱,值在缓存池范围外,valueOf最终调用newInteger,i5,i6都创建了一个新对象,俩对象内容相同,地址不同;4false ,5true ;

 (3) System.Out.println(i1 == i7);编译成 System.Out.println(i1.intValue() == i7);所以输出true;System.Out.println(i3 == i7);同理输出true;

2.2.5 String和Integer相互转换

  • String to Integer: Intrger.parseInt(string);
  • Integer to String: Integer.toString();

2.3 基本类型和包装类的区别

  • 存储位置:基本类型变量值存储在栈中,而包装类型是将对象放存储在堆中,然后通过引用来使用;

  • 默认初值:基本类型的默认初始如int为0,boolean为false,而包装类型的默认初始为null;

  • 作用不同:基本数据类型的存在意义是为了运行效率;而引入包装类是为了方便对基本类型进行操作

2.3 包装类使用场景

  • 泛型(包括容器的泛型参数)只能使用包装类;
  • 某个允许为null的属性只能使用包装类;

 以下三条源自阿里编码规范:

  • 所有POJO类(简单Java类,只有属性、构造器、seter、geter的类)属性;
  • RPC(远程方法调用)方法返回值和参数必须使用包装数据类型;
  • 推荐所有的局部变量使用基本数据类型,避免频繁GC;

  ps:在一个ORM中存在一个Student类和Student类表,有一个JavaBean 类Student类中有一个表示学生考试分数的属性score,相对应Student类表中有一个表示学生考试分数的字段score;如果用Integer而不用int定义这个属性,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样

2.4 包装类存在的问题

  • 效率低;

  由于使用包装类会创建新对象,而且若在循环中使用包装类会频繁GC,效率比较低;所以在循环中尽量使用基本数据类型;所以阿里编码规范推荐所有的局部变量使用基本数据类型;

Integer sum = 0;
 for(int i=0; i<1000; i++){
   sum+=i;
}
  • 拆箱可能会产生空指针异常;
Object obj = null;//1
int i = (Integer)obj;//2

  2处自动拆箱会调用obj.intValue();方法,从而产生空指针异常;

参考:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值