【Java】基础篇-包装类

上回说到了 字符类型 char, 作为基本类型之一, char 的底层实现对于 string 等有的关键的决定因素. 至于基本类型,难点不多,我们不在叙述了,这次我们讲 另一种类型 --- 封装类型


Java 有 8 种基本类型,每种基本类型都有一个对应的包装类型. 包装类又是啥呢?

包装类指的是 Java 的类,内部有实例变量,保存了与之相对应的基本类型的值,这些类有类方法,类变量和其他的实例方法.

基本类型

包装类型

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

char

Character

boolean

Boolean

因为包装类型基本相同,我们就以 Integer 和 Character 这 2 个有代表性的包装类讲下。


Integer

对于包装类来说,我们经常用到的一个就是自动装箱和拆箱,当然由于 Java 编译器的问题,不需要我们手动来操作,不过这里给大家顺便解释下

基本类型到包装类型的过程,我们一般称之为装箱 ,而将包装类型转换为基本类型的过程,我们称之为拆箱

比如

    /**
     * 自动装箱
     */
    // 示例 1
    Integer i = 100;
    // 示例 2
    Integer autoboxing = 12;
    List<Integer> list = new ArrayList<Integer>();
    for (int i = 0; i < 10; i++) {
        list.add(i);
    }

    /**
     * 自动拆箱
     */
    // 示例 1 
    int a = i;
    // 示例 2
    int sum = 0;
    for (Integer i : list) {
        // Integer 不能直接进行运算符操作
        // 这里属于自动拆箱
        if (i % 2 != 0) {
            sum += i;
        }
    }

在我们写了上述自动拆箱、装箱的代码后,Java 编译器会帮我们把 代码替换为valueOf 相关的代码 比如:

    Integer i = 10;
    // 会被替换为 Integer i = Integer.valueOf(10);

当然,我们的 Integer 也有构造方法

    Integer i = new Integer(10);

当然,强烈建议用 valueOf 方法,毕竟很直观的来看,构造方法需要用到内存来新建对象,而且从 Java9 开始,已经被标记 过时了。

话不多说,我们来看源码。(JDK 版本 1.8,源码只提供了我们作为研究的代码)

public final class Integer extends Number implements Comparable<Integer> {
    /**
     * 最小值 -2^31
     */
    @Native public static final int   MIN_VALUE = 0x80000000;

    /**
     * 
     * 最大值 2^31-1.
     */
    @Native public static final int   MAX_VALUE = 0x7fffffff;

    /**
     * 字符串表示数字的所有字符
     */
    final static char[] digits = {
        '0' , '1' , '2' , '3' , '4' , '5' ,
        '6' , '7' , '8' , '9' , 'a' , 'b' ,
        'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
        'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
        'o' , 'p' , 'q' , 'r' , 's' , 't' ,
        'u' , 'v' , 'w' , 'x' , 'y' , 'z'
    };

    /**
     * 返回一个基于16进制的 字符串
     */
    public static String toHexString(int i) {
        // 这里的数字 4 就是 16 进制的代指
        //上篇我们的char 类型曾经研究过该函数
        return toUnsignedString0(i, 4);
    }

    /**
     * 返回一个基于 8 进制的 字符串
     */
    public static String toOctalString(int i) {
        // 这里的数字 3 就是 8 进制的指代
        //上篇我们的char 类型曾经研究过该函数
        return toUnsignedString0(i, 3);
    }

    /**
     * 返回一个基于 2 进制的 字符串
     */
    public static String toBinaryString(int i) {
        //这里的数字 1 就是 2 进制的指代
        //上篇我们的char 类型曾经研究过该函数
        return toUnsignedString0(i, 1);
    }

    /**
     * 转换数字为指定的进制类型字符串
     */
    private static String toUnsignedString0(int val, int shift) {
        // assert shift > 0 && shift <=5 : "Illegal shift value";
        int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
        int chars = Math.max(((mag + (shift - 1)) / shift), 1);
        char[] buf = new char[chars];

        formatUnsignedInt(val, shift, buf, 0, chars);

        // Use special constructor which takes over "buf".
        return new String(buf, true);
    }

    
    /**
     * 返回一个指定 int 类型的 Object 对象
     *
     * @param   i  要转换的参数
     * @return  10 进制的字符串
     */
    public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        // 获取输入 int 类型的 位数,比如 10 就是2 ,100 就是3
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }

     final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                      99999999, 999999999, Integer.MAX_VALUE };

    // 这里是上面获取 int 类型的位数方法
    // 这是一个比较巧妙的地方,值得学习
    static int stringSize(int x) {
        for (int i=0; ; i++)
            // 每次和 sizeTable 数组进行比较
            if (x <= sizeTable[i])
                return i+1;
    }

    

    /**
     * 将 指定的 int 参数放入 char 缓冲区中
     * 如果 i == Integer.MIN_VALUE,将会失败
     */
    static void getChars(int i, int index, char[] buf) {
        int q, r;
        int charPos = index;
        char sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }

        // 每次迭代生成 2 位 数
        // 这里是超出 65536 才执行的
        while (i >= 65536) {
            q = i / 100;
        // really: r = i - (q * 100);
            r = i - ((q << 6) + (q << 5) + (q << 2));
            i = q;
            buf [--charPos] = DigitOnes[r];
            buf [--charPos] = DigitTens[r];
        }

        // Fall thru to fast mode for smaller numbers
        // assert(i <= 65536, i);
        for (;;) {
            // 无符号右移进行操作
            q = (i * 52429) >>> (16+3);
             // r = i-(q*10) ... 
             // 这里 比如 我们的 i 是 55 ,那么 q 计算得出也是 55.  所以 r = 55 - (5 * 10) = 5  
             // 下面用位移运算是因为在 计算机的计算中,位移运算的效率极快
            r = i - ((q << 3) + (q << 1)); 
            buf [--charPos] = digits [r];
            i = q;
            if (i == 0) break;
        }
        if (sign != 0) {
            buf [--charPos] = sign;
        }
    }

   

    /**
     * 将字符串参数解析为第二个参数指定的基数*中的有符号整数
     *
     * 重点源码,必会
     *
     * <p>Examples: 示例如下
     * <blockquote><pre>
     * parseInt("0", 10) returns 0
     * parseInt("473", 10) returns 473
     * parseInt("+42", 10) returns 42
     * parseInt("-0", 10) returns 0
     * parseInt("-FF", 16) returns -255
     * parseInt("1100110", 2) returns 102
     * parseInt("2147483647", 10) returns 2147483647
     * parseInt("-2147483648", 10) returns -2147483648
     * parseInt("2147483648", 10) throws a NumberFormatException
     * parseInt("99", 8) throws a NumberFormatException
     * parseInt("Kona", 10) throws a NumberFormatException
     * parseInt("Kona", 27) returns 411787
     * </pre></blockquote>
     */
    public static int parseInt(String s, int radix)
                throws NumberFormatException {
        /*
         * 在初始化IntegerCache之前,可以在VM初始化期间*早期调用此方法。必须注意不要使用 valueOf方法
         */
        if (s == null) {
            throw new NumberFormatException("null");
        }

        if (radix < Character.MIN_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " less than Character.MIN_RADIX");
        }

        if (radix > Character.MAX_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " greater than Character.MAX_RADIX");
        }
        // 表示结果,为了保持最终结果的正数负数在下面的一致性,在返回之前一直用负数表示
        int result = 0;
        //判断是否为负数
        boolean negative = false;
        int i = 0, len = s.length();
        //-2147483647 默认取最大整数的取反值
        // 在 result 返回之前一直和该 limit 值进行比较,判断是否会溢出
        int limit = -Integer.MAX_VALUE;
        int multmin;
        int digit;

        if (len > 0) {
            // 比如 我们现在的参数是55,那么 s.charAt(0) 的 char 就是 50.这里是 ASCII
            char firstChar = s.charAt(0);
            // 判断字符串的起始位置是否有符号操作符
            // 如果是 + ,那么 char 是43 ,而 '0'是 48 自然会进入
            if (firstChar < '0') { // Possible leading "+" or "-"
                if (firstChar == '-') {
                    // 如果有符号,那么溢出的值 limit 判断 就变成了 整数的最小负数了 是  -2^31 
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+')
                //如果 起始位置既不是 + 也不是 -  那么直接抛出异常
                    throw NumberFormatException.forInputString(s);
                // 如果起始位置 只有 + 或者-  那也得抛出异常
                if (len == 1) // Cannot have lone "+" or "-"
                    throw NumberFormatException.forInputString(s);
                i++;
            }
            // multmin 初始值为最大负整数 / 进制数
            multmin = limit / radix;
            while (i < len) {
                // 根据 Character 获取当前字符对应的数字
                digit = Character.digit(s.charAt(i++),radix);
                if (digit < 0) {
                    throw NumberFormatException.forInputString(s);
                }
                // 判断是否会溢出, 由于计算结果是 带负号的,统一用小于号表示
                if (result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                result *= radix;
                // 这里 判断结果是不是小于 limit 加上 计算得到的Character 数字
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                // result 进行赋值
                result -= digit;
                /**
                 * 模拟计算逻辑
                 * 如果 输入的字符参数是 55,我们一般 paseInt 都是 10进制.
                 *  第一次 digit = 55 / 10 = 5, result *= radix; ==>   result 为0,   所以  0 *= 10 = 0, result 进行赋值 为 -5
                 * 第二次 digit = 55 / 10 = 5 , result *= radix;==>   result 为-5,   所以  -5 *= 10 = -50, result 进行赋值 为 -55
                 */
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
        // 最后进行判断 是否使用本地方法,如果没有使用 则返回 -result
        return negative ? result : -result;
    }

    /**
     * 我们一般使用的 parseInt 方法
     */
    public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s,10);
    }

    /**
     * 这是一个无符号的转换,使用不是很多 核心还是调用上面的 parseInt
     */
    public static int parseUnsignedInt(String s, int radix)
                throws NumberFormatException {
        if (s == null)  {
            throw new NumberFormatException("null");
        }

        int len = s.length();
        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar == '-') {
                throw new
                    NumberFormatException(String.format("Illegal leading minus sign " +
                                                       "on unsigned string %s.", s));
            } else {
                if (len <= 5 || // Integer.MAX_VALUE in Character.MAX_RADIX is 6 digits
                    (radix == 10 && len <= 9) ) { // Integer.MAX_VALUE in base 10 is 10 digits
                    return parseInt(s, radix);
                } else {
                    long ell = Long.parseLong(s, radix);
                    if ((ell & 0xffff_ffff_0000_0000L) == 0) {
                        return (int) ell;
                    } else {
                        throw new
                            NumberFormatException(String.format("String value %s exceeds " +
                                                                "range of unsigned int.", s));
                    }
                }
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
    }

    /**
     * 无符号调用
     */
    public static int parseUnsignedInt(String s) throws NumberFormatException {
        return parseUnsignedInt(s, 10);
    }

    /**
     * 重点源码,必会
     * 这是我们的 常用 valueOf 方法.
     * 内部的调用还是 我们上面提到的 parseInt
     * 注意 这里的 返回是 Integer 方法,这里涉及到了 Integer 的内部缓存, 就是 Integer.valueOf()
     */
    public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }

    /**
     * 重点源码,必会
     * 内部的调用还是 我们上面提到的 parseInt
     * 注意 这里的 返回是 Integer 方法,这里涉及到了 Integer 的内部缓存, 就是 Integer.valueOf()
     */
    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }

    /**
     * 重点源码,必会
     * <===============================================================================>
     * 根据JLS的要求,缓存以支持自动装箱的对象标识语义,以获取* -128和127(含)之间的值
     *
     * 缓存在首次使用时初始化。缓存*的大小可以由 -XX:AutoBoxCacheMax = <size>选项控制
     */
    private static class IntegerCache {
        //最低位为 -128
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // 最高位可以由属性配置
            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) {
                    // 如果参数不能解析,忽略
                }
            }
            high = h;
            //设置缓存数组 size
            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() {}
    }

    /**
     * valueOf 先在缓存中进行判断,如果有,进行 return
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;

    /**
     * 构造函数
     */
    public Integer(int value) {
        this.value = value;
    }

    /**
     * 字符串类型的构造函数
     */
    public Integer(String s) throws NumberFormatException {
        this.value = parseInt(s, 10);
    }

    /**
     * 返回一个 byte 类型的值
     */
    public byte byteValue() {
        return (byte)value;
    }

    /**
     * 返回一个 short 类型的值
     */
    public short shortValue() {
        return (short)value;
    }

    /**
     * 返回一个拆箱类型的值
     */
    public int intValue() {
        return value;
    }

    /**
     * 返回一个 long 类型的值
     */
    public long longValue() {
        return (long)value;
    }

    /**
     * 返回一个 float 类型的值
     */
    public float floatValue() {
        return (float)value;
    }

    /**
     * 返回一个 double 类型的值
     */
    public double doubleValue() {
        return (double)value;
    }

    /**
     * 返回一个 基于 value 类型的 10 进制的 字符串
     */
    public String toString() {
        return toString(value);
    }

    /**
     * hash Code 
     */
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    /**
     * int 类型的 hash code, 
     */
    public static int hashCode(int value) {
        return value;
    }

    /**
     * Integer 的 equals 方法 比较value ,如果 value 相同,则相同
     */
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

    /**
     * 排序方法
     */
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

    /**
     * 排序方法
     */
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

由于篇幅有限,不能把所有的源码都写上,不过上面写的都是核心方法,只要了解上面的即可.


下面我们继续来点猛料,看看 Integer 的二进制算法

Integer 的二进制算法

integer 有 2 个 二进制方法进行按位翻转

按位翻转就是左边的位与右边的位进行互换,比如 10 换成 01。

        // 16 进制表示
        int i = 0x1234321;
        // 2 进制输出
        System.out.println("2 进制表示 ======>" + Integer.toBinaryString(i));

        // 10 进制输出
        System.out.println("10 进制表示 ======>" + i);
        //--------------------------------------------------------------------
        //按位翻转-以字节
        int rb = Integer.reverseBytes(i);

        // 2 进制输出
        System.out.println("以字节翻转后 2 进制表示 ======>" + Integer.toBinaryString(rb));

        // 10 进制输出
        System.out.println("以字节翻转后 10 进制表示 ======>" + rb);

        //按位翻转-以字节
        int r = Integer.reverse(i);

        // 2 进制输出
        System.out.println("以位翻转后 2 进制表示 ======>" + Integer.toBinaryString(r));

        // 10 进制输出
        System.out.println("以位翻转后 10 进制表示 ======>" + r);

output

2 进制表示 ======>             1001000110100001100100001
10 进制表示 ======>            19088161
以字节翻转后 2 进制表示 ======> 100001010000110010001100000001
以字节翻转后 10 进制表示 ======>558048001
以位翻转后 2 进制表示 ======>   10000100110000101100010010000000
以位翻转后 10 进制表示 ======> -2067610496
    /**
     * 进行按字节翻转
     */
    public static int reverseBytes(int i) {
        // 以 int i = 0x1234321 (10 进制 19088161)为例 
        // i >>> 24 无符号右移,最高字节移动到最低位置
        return ((i >>> 24)           ) |
                // (i >>   8) &   0xFF00 左边第二个字节移动到右边第二个 
               ((i >>   8) &   0xFF00) |
               ((i <<   8) & 0xFF0000) |
               ((i << 24));
    }
    /**
     * 进行按位翻转
     */
    public static int reverse(int i) {
        // HD, Figure 7-1
        // HD 代表一本书 《Hacker's Delight》(中文版本:《算法心得:高效算法的奥秘》)
        // Figure 7-1 代表书中的 7-1 图

        // 交换第一位
        i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
        //交换第 2 位
        i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
        //交换第 4位
        i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
        i = (i << 24) | ((i & 0xff00) << 8) |
            ((i >>> 8) & 0xff00) | (i >>> 24);
        return i;
    }

上面 reverse 代码看的很晕。其实现原理就是

  • 交换相邻的单一位。
  • 以 2 位一组进行交换相邻的位置
  • 以 4 位一组进行交换相邻的位置
  • 。。。。 然后就是 8 位 16位

这里我们用数字进行解释(为避免太深入 二进制转换,不容易理解,我们以 10进制为例)

比如 i = 12345678

  • 单一位相邻交换 12 34 56 78 ======> 交换后结果 21 43 65 87
  • 以 2 位相邻交换 21 43 65 87 ======> 交换后结果 43 21 87 65
  • 以 4 位相邻交换 43 21 87 65 ======> 交换后结果 8765 4321

如上可以看出,10 进制的翻转 效率很一般,当然对于 2 进制来说,效率就杠杠的了,因为 二进制可以在一条指令中交换多个相邻位

reverse 函数为啥写的这么恶心呢?虽然说我们目前使用的不说很多。我们能不能换一种翻转方式?比如第一个和最后一个交换,第二个和倒数第二个交换这种? 因为我们考虑的是10 进制,这样的操作方式是完全 ok 的,但是对于二进制来说,这样是低效的,毕竟没有充分利用 CPU 的性能,reverse 方法就是充分利用 CPU 的性能而做的方法。


循环移位 (按位操作的补充)

Integr 中提供了 2 个循环移位的方法

    // 循环左移
    // @param i 需要移动的参数
    // @param distance 移动的位数
    public static int rotateLeft(int i, int distance) {
        return (i << distance) | (i >>> -distance);
    }

    // 循环右移
    // @param i 需要移动的参数
    // @param distance 移动的位数
    public static int rotateRight(int i, int distance) {
        return (i >>> distance) | (i << -distance);
    }

这里之所以说是 按位操作的补充,是因为我们的普通按位操作,比如 << 向左移动,右边全部以 0 补齐, >> 向右移动,左边 以 0 补齐。

而这里的 循环移动就是补充的位不再是 0 了,比如 左移 1位,右边不再是 0 补齐,而是 原来 向左 移动的位数会到右边。

我们来举个 例子

1010 0001 << 1 就变成了

10100 0010 左边的 1 被舍弃了,右边 补充了0

而 我们的循环移动则是

1010 0001 循环左移 1位 变成了

10100 0011 左边的 1 被放到 右边

在循环移位时,distance 可以是负数 rotateLeft(val, -distance) == rotateRight(val, distance) 这里是等价的

这里我们解释下关于 distance 是负数的问题。如果我们在移位的时候,传入的 distance 是负数。那么就会根据被移位数的长度进行转换,比如 我现在被移位数是 int 类型的值 rotateRight(10, -8).那么就会转换为 rotateRight(10, 32 +(-8)) = (10, 24),然后计算出左移 的值,再计算出右移的值。两者 进行 或 运算即可。

如果有写的不对的地方,请大家留言。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券