01、基本数据类型 的 包装器类

一、包装器 / 包装器类 / 包装类


1、提供 包装器类 的原因

  • Java 是面向对象语言,但 Java 基本数据类型不是面向对象的。
    • 在实际使用中,经常需要将基本数据类型转化成对象,便于操作。
    • 如:集合中不能存放基本数据类型,只能存放对象
      • 所以,当使用 集合时,就要使用到包装类对象

2、包装器类

  • 为此,Java 语言为所有的 基本数据类型 都提供了一个与之对应的类。
    * 如:Integer 类对应基本类型 int。
    • 通常,这些类称为 包装器(wrapper),这些包装器类有显而易见的名字:
      • Integer、Long、Float、Double、Short、Byte、Character、Boolean。
  • 包装器类不可变的
    • 因此,一旦创建了包装器对象,就不允许更改包装在其中的(内部 value 用 final 修饰)。
  • 包装器类也是 final 修饰的。
    • 因此,不能派生它们的子类。
  • 包装器类默认值 null
  • 包装类提供了更多的功能(如:数据进制转换字符串转成数字、查看最大值、最小值等等)。
    • parseInt(“1100110”, 2) 结果:102 二进制转十进制。
    • parseInt(“+42”, 10) 结果:42
    • parseInt(“-0”, 10) 结果:0
    • parseInt(“-FF”, 16) 结果:-255

二、自动装箱、自动拆箱


1、自动装箱 和 自动拆箱 – 通过 指的是?语法糖实现的。


  • 指的是 基本数据类型 与 对应的 包装类型 之间进行的 自动 相互转换
    • 装箱:Integer i = 2; // 编译器自动装箱 时,改成 Integer.valueOf(2);
    • 拆箱:Integer i = 2; int b = i; // 编译器自动拆箱 时,改成 i.intValue();
Integer n = 3;
n++;
// 编译器将自动地插入指令,对对象拆箱。然后,将结果值增 1 。最后,再将其装箱。
原始类型包装类型装箱方法拆箱方法
byte 1 字节ByteByte.valueOf()Byte.byteValue()
char 2 字节CharracterCharacter.valueOf()Character.charValue()
short 2 字节ShortShort.valueOf()Short.shortValue
int 4 字节IntegerInteger.valueOf()Integer.intValue()
long 8 字节LongLong.valueOf()Long.longValue()
double 8 字节DoubleDouble.valueOf()Double.doubleValue()
booleanBooleanBoolean.valueOf()Boolean.booleanValue()

2、在字节码层面,验证 自动装箱 和 自动拆箱

/**
 * 语法糖 -- 自动装箱,自动拆箱
 *
 * @author zhangxw
 * @since 1.0.0
 */
public class PackingAndUnboxing {
    public static void main(String[] args) {
        Integer integerTemp = 10000;

        int intTemp = integerTemp;
    }
}
  • 查看编译后的字节码:
    • javap -verbose -c PackingAndUnboxing
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: sipush        10000
    // 调用了 Integer.valueOf 方法
         3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         6: astore_1
         7: aload_1
    // 调用了 Integer.intValue 方法
         8: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        11: istore_2
        12: return
      LineNumberTable:
        line 11: 0
        line 13: 7
        line 14: 12
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  args   [Ljava/lang/String;
            7       6     1 integerTemp   Ljava/lang/Integer;
           12       1     2 intTemp   I

三、包装类 的 对象缓存


包装类型缓存范围是否可调整实现方式
Integer-128 到 127(默认)是(通过 JVM 参数)IntegerCache
Long-128 到 127LongCache
Character0 到 127CharacterCache
Byte-128 到 127ByteCache(全部缓存)
Short-128 到 127ShortCache
Booleantrue 和 false静态实例(全部缓存)
Float--
Double--
  • 经典考点
package org.rainlotus.materials.javabase.a04_oop.aboutintegercache;

/**
 * IntegerCache 带来的影响:
 *      由于 IntegerCache 中默认缓存的数据为 -127 ~ 127 。
 *          所以,i1、i2、i3、i4 编译后,调用了 Integer.valueOf 自动装箱方法。
 *      当数值在  -127 ~ 127  之间时,直接从 IntegerCache 中,取出一个缓存对象,返回。
 *          因此,i1、i2 在内存中,指向的是同一个对象。两者的 == 判断,返回 true 。
 *      当数值不在-127 ~ 127  之间时,直接调用 new Integer(int i) ,来创建对象 。
 *          因此,i3、i4 在内存中,指向的是不同的对象。两者的 == 判断,返回 false 。
 *          即:等同于 i5、i6 之间的 == 判断,返回 false 的 原理一致。
 *
 * @author zhangxw
 */
public class IntegerCacheTest {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;

        // 输出 true ;
        System.out.println(i1 == i2);

        Integer i3 = 128;
        Integer i4 = 128;

        // 输出 false ;
        System.out.println(i3 == i4);


        Integer i5 = new Integer(127);
        Integer i6 = new Integer(127);

        // 输出 false ;
        System.out.println(i5 == i6);
    }
}

1、IntegerCache


  • 默认 缓存 -128 到 127(包含)之间的值。
  • 缓存会在首次使用时初始化。
  • 自动装箱 时,会用到。
  • 缓存的大小 的控制:
    • 1、可以通过 -XX:AutoBoxCacheMax= 选项进行控制。
    • 2、在 VM 初始化期间,java.lang.Integer.IntegerCache.high 属性可能会被设置并保存在 sun.misc.VM 类的私有系统属性中。
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() {}
}

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

2、LongCache


  • 默认 缓存 -128 到 127(包含)之间的值。
  • 缓存会在首次使用时初始化。
  • 自动装箱 时,会用到。
private static class LongCache {
    private LongCache(){}

    static final Long cache[] = new Long[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}


// 自动装箱
public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

3、CharacterCache


  • 默认 缓存 0 到 127(包含)之间的值。
  • 缓存会在首次使用时初始化。
  • 自动装箱 时,会用到。
private static class CharacterCache {
    private CharacterCache(){}

    static final Character cache[] = new Character[127 + 1];

    static {
        for (int i = 0; i < cache.length; i++)
            cache[i] = new Character((char)i);
    }
}



// 自动装箱
public static Character valueOf(char c) {
    if (c <= 127) { // must cache
        return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

4、ByteCache


  • 默认 缓存 -128 到 127(包含)之间的值。
  • 缓存会在首次使用时初始化。
  • 自动装箱 时,会用到。
private static class ByteCache {
    private ByteCache(){}

    static final Byte cache[] = new Byte[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Byte((byte)(i - 128));
    }
}



// 自动装箱
public static Byte valueOf(byte b) {
    final int offset = 128;
    return ByteCache.cache[(int)b + offset];
}

5、ShortCache


  • 默认 缓存 -128 到 127(包含)之间的值。
  • 缓存会在首次使用时初始化。
  • 自动装箱 时,会用到。
private static class ShortCache {
    private ShortCache(){}

    static final Short cache[] = new Short[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Short((short)(i - 128));
    }
}



// 自动装箱
public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

6、Boolean


  • 通过静态常量,实现缓存 new Boolean(true) 和 new Boolean(false) 。
  • 缓存会在首次使用时初始化。
  • 自动装箱 时,会用到。
public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean> {
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);



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

    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

}

四、常见题目


1、将字符串“123”转化成基本类型数字的方式有哪些?


public class IntegerTest {
    public static void main(String[] args) {
        Integer i1 = Integer.parseInt("127");
        Integer i2 = Integer.parseInt("127");
        // Integer.parseInt("127") 返回的是 int 值。
            // 当赋值给 Integer 时,有自动调用了 Integer.valueof() 被自动装箱,并存储 IntegerCache 中。
        // 所以,返回 true 。
        System.out.println(i1 == i2);

        Integer i3 = new Integer("123");
        // new Integer("123") 先创建了 Integer 对象 i3。然后,调用 parseInt("127") 并赋值给 i3.value。
        // 所以,返回 false
        System.out.println(i1 == i3);

        Integer i4 = Integer.valueOf("127");
        // Integer.valueOf("127") 内部调用的是 Integer.valueOf(parseInt("127", 10)); 。
            // 等价与 Integer i1 = Integer.parseInt("127"); 。
        // 所以,返回 true 。
        System.out.println(i1 == i4);
    }
}

2、new Integer(123) 与 Integer.valueOf(123) 的区别是啥?


Integer x = new Integer(123);
Integer y = new Integer(123);
// false
System.out.println(x == y);

Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
// true
System.out.println(z == k);
  • new Integer(123):每次都会新建⼀个对象;
  • Integer.valueOf(123):会使⽤ 缓存池 中的对象,多次调⽤会取得同⼀个对象的引⽤。
    • valueOf() ⽅法的实现⽐较简单,先判断值是否在缓存池中,如果在就 直接返回缓存池的内容
    • 缓存池的实现:Integer 数组
public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

3、判断两个数字是否相等


  • 基本数据类型使用 ==
  • 包装类型使用 equals

4、Java 金额计算怎么避免 精度 丢失?


  • 创建 BigDecimal 对象时,一定要用 String 对象 作为构造器参数。
    • 不能直接使用 double 数字。否则同样会发生 精度丢失 的问题。
  • 如果必须使用 double 浮点数作为 BigDecimal 构造器的参数时。
    • 不要直接将该 double 浮点数作为构造器参数创建 BigDecimal 对象,
    • 而是应该通过 BigDecimal.valueOf(double value) 静态方法 来创建 BigDecimal 对象。
      • 在内部,将 value 从 double 转成 String 。

5、对于 null 的理解


Object obj1 = null;
Object obj2 = null;
// true
System.out.println(obj1 == obj2);

// true
System.out.println(Objects.equals(obj1, obj2));
  • 任何含有 null 值的包装类在 Java 拆箱生成基本数据类型时候都会抛出一个空指针异常。
package org.rainlotus.materials.javabase.oop.aboutnull;

/**
 * 关于 null 的理解  普通方法
 *
 * @author zhangxw
 * @since 1.0.0
 */
public class AboutNull {

    static int staticFiled = 123;
    int ordinaryFiled = 123;

    private static void staticMethod(){ System.out.println("静态方法:staticMethod"); }

    private void ordinaryMethod(){ System.out.println("普通方法:ordinaryMethod"); }

    public static void main(String[] args) {
        AboutNull aboutNull = null;
        // false
        System.out.println(aboutNull instanceof AboutNull);

        // 通过上边 实例变量 来调用静态属性和静态方法,没有抛出 NullPointerException 异常。
            // 验证:当使用实例变量来调用静态元素时,底层实际上是通过委托类来访问的。
        // 输出: 静态方法:staticMethod
        aboutNull.staticMethod();
        // 输出: 123
        System.out.println(aboutNull.staticFiled);

        // true
        System.out.println(null == null);

        Integer intA = null;
        // 运行时会抛出空引用异常
        int intB = intA;

        // 抛出异常:NullPointerException
        System.out.println(aboutNull.ordinaryFiled);
        // 抛出异常:NullPointerException
        aboutNull.ordinaryMethod();

        aboutNull = new AboutNull();
        // true
        System.out.println(aboutNull instanceof AboutNull);
    }
}
  • 字节码分析:
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
        // 从常量池中,将 null 压入栈顶
         0: aconst_null
        // 将栈顶的值(即 null)弹出并赋值给本地变量表第 1 个位置。
         1: astore_1
        // 获取静态属性 System.out
         2: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        // 将局部变量表 1 位置的值(即 null)压入栈顶
         5: aload_1
        // 检验对象是否是指定的类的实例。如果是将 1 压入栈顶,否则将 0 压入栈顶
         6: instanceof    #7                  // class org/rainlotus/materials/javabase/oop/aboutnull/AboutNull
        // 调用实例 PrintStream 的 println 方法。并将栈顶的 0 弹出并输出。
         9: invokevirtual #8                  // Method java/io/PrintStream.println:(Z)V
        // 将局部变量表 1 位置的值(即 null)压入栈顶
        12: aload_1
        // 弹出栈顶的值(即 null)
        13: pop
        // 调用静态方法 staticMethod 。此处体现了源代码中使用实例调用(即:aboutNull.staticMethod())静态方法,底层本质上使用 类来调用静态方法。
        14: invokestatic  #9                  // Method staticMethod:()V
        17: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: aload_1
        21: pop
        // 调用静态属性 staticFiled 。此处体现了源代码中使用实例调用(即:aboutNull.staticFiled)静态属性,底层本质上使用 类来调用静态属性。
        22: getstatic     #10                 // Field staticFiled:I
        25: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V

6、void 和 Void 有什么区别?


  • void 表示“空”,该方法 没有返回值
  • java.lang.Void 是一种类型。
    • Void 变量唯一能持有的是 null。
    • 通过 Void 类的源代码可以看到,Void 类型不可以继承与实例化。
    • 泛型出现前:Void 一般用于 反射之中,判断 方法的返回值
    • 泛型出现后:Void 类本身只是一个 占位符类,不能被实例化,多用于泛型中作占位符使用。
// 在 Java 源代码中看到 Void 类的使用

private static Void checkParentAccess(ThreadGroup parent) {
    parent.checkAccess();
    return null;
}

public ThreadGroup(ThreadGroup parent, String name) {
    this(checkParentAccess(parent), parent, name);
}

private ThreadGroup(Void unused, ThreadGroup parent, String name) {
    this.name = name;
    this.maxPriority = parent.maxPriority;
    this.daemon = parent.daemon;
    this.vmAllowSuspension = parent.vmAllowSuspension;
    this.parent = parent;
    parent.add(this);
}
public class AboutVoid {
    public void noReturnValueMethod(){
        // 返回 void,return 可写可不写
        return;
    }

    public Void returnVoidMethod(){
        // 此处必须返回 null ,返回其它值会报错
        return null;
    }

    public static void main(String[] args) {
        // class java.lang.Void		void
        System.out.println(Void.class + "\t\t" + void.class);

        // class java.lang.Integer		int
        System.out.println(Integer.class + "\t\t" + int.class);

        for (Method method : AboutVoid.class.getMethods()){
            if(method.getReturnType().equals(Void.TYPE)){
                /*  没有 returnVoidMethod 方法
                    输出结果:
                        noReturnValueMethod、main、
                        wait、wait、wait、notify、notifyAll
                */
                System.out.println(method.getName());
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值