【Java常用类】1-2 基本类型包装类

§1-2 基本类型包装类

1-2.1 什么是包装类?

在基础课程中,我们学习了 Java 中的八大基本数据类型:byte, short, int, long, float, double, char, boolean

在程序运行的过程中,这些基本数据类型变量存储在栈中,不具备任何的属性或方法。对这些基本数据类型,只能通过 Java 所提供的运算符来操作这些基本数据类型。

为了向这些基本数据类型提供更为强大的功能,JDK 中提供了由这八大基本数据类型所对应的引用数据类型,称为基本数据类型的包装类。此时,这些 “基本数据类型” 就有了配套的属性和方法。

其中,Object 类可统一所有数据,包装类的默认值为 null(即引用类型的默认值)。

下表列出了基本数据类型所对应的包装类,所有类型都在 java.lang 下:

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

上述的包装类都继承自一个抽象类 Number,该类还有以下已知子类:AtomicIntegerAtomicLongBigDecimalBigInteger

1-2.2 类型转换与装箱、拆箱

基本类型变量存储在栈中,而对象存储在堆中。此处的类型转换指的是基本数据类型和其对应包装类的类型转换。

装箱:将栈中的基本数据类型以对象的形式存储到堆中,称为装箱。

装箱可使用包装类的构造器(不推荐,已被弃用),或使用 valueOf(); 方法返回包装类实例。

拆箱:将堆中的对象以基本数据类型的形式存储到栈中,称为拆箱。

拆箱可通过 Number 类中的 byteValue();shortValue();intValue();longValue();floatValue();doubleValue(); 将引用类型转成基本类型。(通过子类的重写方法)

示例:JDK 1.5 前,装拆箱需要手动进行

public class Test {
    public static void main(String[] args) {
        //JDK 1.5 前,手动装箱、拆箱
        //不建议使用构造器,现已被弃用
        //手动装箱
        int num1 = 10;
        Integer int1 = Integer.valueOf(num1);
        System.out.println("手动装箱:");
        System.out.println("num1 = " + num1);
        System.out.println("int1 = " + int1);
        System.out.println();

        //手动拆箱
        Integer int2 = Integer.valueOf(100);
        int num2 = int2.intValue();
        System.out.println("手动拆箱:");
        System.out.println("int2 = " + int2);
        System.out.println("num2 = " + num2);
    }
}

运行得到

手动装箱:
num1 = 10
int1 = 10

手动拆箱:
int2 = 100
num2 = 100

JDK 1.5 后,支持自动装拆箱:

public class Test {
    public static void main(String[] args) {
        //不建议使用构造器,现已被弃用
        //JDK 1.5 后,自动装拆箱
        //自动装箱
        int num3 = 20;
        Integer int3 = num3;
        System.out.println("自动装箱:");
        System.out.println("num3 = " + num3);
        System.out.println("int3 = " + int3);
        System.out.println();

        //自动拆箱
        Integer int4 = 200;
        int num4 = int4;
        System.out.println("自动拆箱:");
        System.out.println("int4 = " + int4);
        System.out.println("num4 = " + num4);
    }
}

运行得到:

自动装箱:
num3 = 20
int3 = 20

自动拆箱:
int4 = 200
num4 = 200

实际上,自动装拆箱的原理是编译器在编译时为我们自动做了装拆箱工作。将自动装拆箱的程序字节码文件反编译,就可以发现,自动装拆箱的实现,实际上就是编译器将赋值语句转换成了方法调用语句。

1-2.3 基本类型与字符串的互相转换

基本类型转换成字符串:可以通过使用包装类中的 toString(); 方法实现。以整型为例:

//与字符串的互相转换
//基本类型转成字符串:toString();
int1 = 255;
String str = int1.toString();      //int1 + ""也可以,但更推荐使用方法
System.out.println(str);
str = Integer.toString(int1,16);    //使用重载(静态方法),以特定进制输出
System.out.println(str);
str = Integer.toHexString(int1);    //使用这个方法也可以实现十六进制转换
System.out.println(str);

得到结果:

255
ff
ff

字符串转换为基本类型:使用静态方法 XXX.parseXXX();

String str = "17e";		//含有非法字符
int num = 0;
boolean accepted = true;
try {
    num = Integer.parseInt(str);
} catch (NumberFormatException e) {
    accepted = false;
    System.out.println("输入中含有非法字符。");
}
if (accepted) {
    System.out.println(num);
}

运行得到

输入中含有非法字符。

注意

  • 若字符串中含有非法字符,则会抛出 NumberFormatException 异常;
  • 字符串转布尔类型:"true" 则返回 true,非 "true" 则返回 false

1-2.4 Integer 缓冲区

我们发现,自动装箱的过程中,编译器实际上是在调用 valueOf(); 方法。

且已知,使用 == 运算符,对于引用变量而言,比较的实际上是二者的地址是否相同。

测试如下程序

public class IntegerCacheTest {
    public static void main(String[] args) {
        //Integer 缓冲区
        //不使用构造器
        Integer int1 = 100;
        Integer int2 = 100;
        System.out.println(int1 == int2);   //使用 == 比较引用变量地址,使用 equals() 比较内容

        Integer int3 = 200;
        Integer int4 = 200;
        System.out.println(int3 == int4);
    }
}

运行得到

true
false

不同的对象实际上是通过构造器构造产生(不允许外部使用,已被弃用),地址不同,比较结果应当都为 false。而 int1 == int2 结果为 true 意味着,二者在内存当中,共同指向同一片内存地址。

结合子自动装箱原理,查看 valueOf() 方法的源码,不难发现:

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

对于方法中的一些变量,有(节选):

//java.lang.Integer.java

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer[] cache;		//一个 Integer 缓冲数组
    static Integer[] archivedCache;
    
    //静态代码块
    static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;	//即 high = h = 127

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;		//缓冲数组长度(256)

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;		//指向同一个数组
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
}

实际上,Java 预先创建了 256 个常用的整型包装类对象,存放于数组之中,范围为 -128~127。调用 valueOf() 方法,若传入参数范围落入其中,则直接返回预先创建好的对象的地址,否则创建新对象。

在实际应用中,这鞋整数使用比较频繁。这样子可以对一创建的对象进行复用,减少内存消耗。

posted @ 2023-07-17 17:51  Zebt  阅读(40)  评论(0)    收藏  举报