深入理解Java包装类与自动拆装箱

在Java中,针对八种基本数据类型定义了相应的引用类型—包装类(封装类)

有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。

八种基本数据类型对应的包装类如下图所示:
在这里插入图片描述
基本数据类型与包装类之间的转换,如下图所示:
在这里插入图片描述

一、基本数据类型转换为包装类

在这里插入图片描述
基本数据类型—>包装类:调用包装类的构造器

//基本数据类型——>包装类:调用包装类的构造器
int i=10;
Integer integer=new Integer(i);
System.out.println(integer.toString()); //10  toString可加可不加
Integer integer1=new Integer("123");
System.out.println(integer1); //123
Integer integer2=new Integer("123abc"); // ×

double d1=12.3;
Double d2=new Double(d1);
System.out.println(d2); //12.3

float f1=123.45F;
Float f2=new Float(f1);
System.out.println(f2); //123.45

char c1='A';
Character c2=new Character(c1);
System.out.println(c2); //A

boolean b1=true;
Boolean b2=new Boolean(b1);
System.out.println(b2); //true
Boolean b3=new Boolean("TruE"); //可以忽略大小写
System.out.println(b3); //true
//注意点:包装类是类,默认初始化值为null
Class Order{
    int i1;
    Integer i2;
    double d1;
    Double d2;
    boolean b1;
    Boolean b2;
}

public class Test {
    public static void main(String[] args) {
        Order order=new Order();
        System.out.println(order.i1); //0
        System.out.println(order.i2); //null 包装类是类,默认初始化值为null
        System.out.println(order.d1); //0.0
        System.out.println(order.d2); //null 
        System.out.println(order.b1); //false
        System.out.println(order.b2); //null
    }
}

总结:包装类是类,默认初始化值为null

二、包装类转换为基本数据类型

在这里插入图片描述
包装类—>基本数据类型:调用包装类的xxxValue()方法

//包装类——>基本数据类型:调用包装类的xxxValue()方法
Integer integer=new Integer(12);
int i=integer.intValue();
System.out.println(i); //12

byte b=integer.byteValue();
System.out.println(b); //12

short s=integer.shortValue();
System.out.println(s); //12

long l=integer.longValue();
System.out.println(l); //12

double d=integer.doubleValue();
System.out.println(d); //12.0

float f=integer.floatValue();
System.out.println(f); //12.0

Double d1=new Double("12.3");
double d2=d1.doubleValue();
System.out.println(d2); //12.3

Float f1=new Float("12.6");
float f2=f1.floatValue();
System.out.println(f2); //12.6

//总结:byte、short、int、long、float、double,它们的包装类都可以装换成这六种基本数据类型

Boolean b1=new Boolean("true");
boolean b2=b1.booleanValue();
System.out.println(b2); //true

Character c1=new Character('A');
char c2=c1.charValue();
System.out.println(c2); //A

总结:包装类——>基本数据类型:调用包装类的xxxValue()方法;
byte、short、int、long、float、double,它们的包装类都可以装换成这六种基本数据类型。

三、自动拆装箱

自动装箱和拆箱是从Java 1.5开始引入的,目的是将原始类型值转自动地转换成对应的包装类(对象)。自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。

3.1 自动装箱

如果基本数据类型的数据处于需要对象的环境中时,会自动转为“对象”。
自动装箱过程是通过调用包装类的valueOf()方法实现的。

Integer i= 100; //自动装箱
//相当于编译器自动作以下的语法编译:
Integer i = Integer.valueOf(100); //调用的是valueOf(100),不是new Integer(100)

3.2 自动拆箱

如果包装类对象处于需要基本数据类型的环境中时,包装类对象会自动转成基本数据类型,没必要再去显式调用intValue()、doubleValue()等转型方法。

Integer i= 100;
int k = i;//自动拆箱
//相当于编译器自动作以下的语法编译:
int k = i.intValue();

3.3 包装类的缓存问题

整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。
缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new的方式调用包装类的构造方法来创建对象。
Integer类相关源码:

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

由上面的源码我们可以看出:

  1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
  2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127,IntegerCache.cache为内部类的一个静态属性。
  3. 静态代码块的目的就是初始化数组cache的,这个过程会在类加载时完成。

3.4 自动拆装箱注意问题

3.4.1 空指针异常问题

自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这个功能很方便,但在程序运行阶段必须了解Java的语义。
例如下面的程序是可以通过编译的:

public class Test {
    public static void main(String[] args) {
        Integer i = null; //因为Integer是类,所以值可以为null
        int j = i;
    }
}

运行结果:
在这里插入图片描述
运行结果之所以会出现空指针异常,是因为上述的代码相当于:

public class Test {
    public static void main(String[] args) {
        Integer i = null; 
        int j = i.intValue();   
    }
}
3.4.2 使用==进行比较时(重点)

这是一个比较容易出错的地方,== 既可以用于数值进行比较,也可以用于对象进行比较。当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中不会发生自动装箱。如果想要进行对象值比较,不应该使用==,而应该使用对象对应的equals方法。

下面我们使用例子来进行说明:

//建议:先自己写答案,再看运行结果,看运行结果与你的答案是否一致
public class Test {
    public static void main(String[] args) {
        int int1 = 10;
        int int2 = 10;

        Integer integer1 = new Integer(10);
        Integer integer2 = new Integer(10);
        Integer integer3 = new Integer(127);

        Integer a1 = 127; //或者写成Integer a1 = Integer.valueOf(127);
        Integer a2 = 127;//或者写成Integer a2 = Integer.valueOf(127);

        Integer a = 128;
        Integer b = 128;

        System.out.println("int1 == int2 -> " + (int1 == int2));
        System.out.println("int1 == integer1 -> " + (int1 == integer1));
        System.out.println("integer1 == integer2 -> " + (integer1 == integer2));
        System.out.println("integer3 == a1 -> " + (integer3 == a1));
        System.out.println("a1 == a2 -> " + (a1 == a2));
        System.out.println("a == b -> " + (a == b));
    }
}

运行结果:
在这里插入图片描述
下面我们就来详细解释一下,为什么是上面的结果:

  1. 因为int1与int2比较的是数值,所以两者相等,这个很容易理解;
  2. 因为Integer是int的包装类,当Integer与int进行 == 比较时,Integer就会拆箱成一个int类型,所以还是相当于两个int类型进行比较,所以两者就是相等的;
  3. integer1和integer2这两个都是对象类型,此时使用==比较的不再是数值,而是两者是否为同一对象。因为两者的地址值不一样,所以两者不相等;
  4. integer3是一个对象类型,而a1是一个常量,它们在内存中存放的位置不一样,所以两者也不相等;
  5. 因为127在进行装箱时,会对-128到127之间的数进行缓存,当再创建a2时,发现缓存中已经存在127这个数了,就直接取出来赋值给a2,所以a1 == a2;
  6. 因为128超过了缓存范围,此时就是使用new Integer()来new一个对象了,所以a、b都是new Integer(128)出来的变量,所以两者不相等。

3.5、自动装箱的弊端

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。

Integer sum= 0;
 for(int i=1000; i<10000; i++){
   sum+=i; //因为Integer是包装类,当包装类与基本数据类型进行运算时,就会先拆箱,再装箱
}

上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,因为sum首先会进行自动拆箱操作,然后进行数值相加操作,最后进行自动装箱操作转换成Integer对象。其内部变化如下:

int result = sum.intValue() + i;
Integer sum = new Integer(result);

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

四、基本数据类型包装类与String类的相互转换

4.1、基本数据类型包装类—>String类:调用String类的valueOf()方法

int i=12;
String s1=String.valueOf(i);
System.out.println(s1); //12

//参数为包装类
Integer i1=new Integer(13);
String s2=String.valueOf(i1);
System.out.println(s2); // 13

double d=12.3;
String s3=String.valueOf(d);
System.out.println(s3); // 12.3

//总结:基本数据类型包装类——>String类:调用String类的valueOf()方法

注意点:String类的valueOf()方法中的参数可以为基本数据类型,也可以为包装类。

4.2、String类—>基本数据类型包装类:调用包装类的parseXxx()方法

String s1="123";
int i1=Integer.parseInt(s1);
System.out.println(i1); // 123

double d=Double.parseDouble(s1);
System.out.println(d); // 123.0

float f=Float.parseFloat(s1);
System.out.println(f); // 123.0

//总结:String类——>基本数据类型包装类:调用包装类的parseXxx()方法

Java包装类与自动拆装箱也是Java中一个非常重要的知识点,无论是在日常开发还是面试和笔试中都经常用到。希望本篇文章能够对你有所帮助。

相关阅读:

抖音赚钱小项目

抖音怎么合拍

抖音黄v

猜你喜欢

转载自blog.csdn.net/zzqq12345/article/details/106914089