前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Java】基础篇-包装类

【Java】基础篇-包装类

作者头像
haoming1100
发布2019-04-18 16:31:40
5350
发布2019-04-18 16:31:40
举报
文章被收录于专栏:步履前行步履前行

上回说到了 字符类型 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 编译器的问题,不需要我们手动来操作,不过这里给大家顺便解释下

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

比如

代码语言:javascript
复制
    /**
     * 自动装箱
     */
    // 示例 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 相关的代码 比如:

代码语言:javascript
复制
    Integer i = 10;
    // 会被替换为 Integer i = Integer.valueOf(10);

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

代码语言:javascript
复制
    Integer i = new Integer(10);

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

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

代码语言:javascript
复制
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。

代码语言:javascript
复制
        // 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

代码语言:javascript
复制
2 进制表示 ======>             1001000110100001100100001
10 进制表示 ======>            19088161
以字节翻转后 2 进制表示 ======> 100001010000110010001100000001
以字节翻转后 10 进制表示 ======>558048001
以位翻转后 2 进制表示 ======>   10000100110000101100010010000000
以位翻转后 10 进制表示 ======> -2067610496
代码语言:javascript
复制
    /**
     * 进行按字节翻转
     */
    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));
    }
代码语言:javascript
复制
    /**
     * 进行按位翻转
     */
    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 个循环移位的方法

代码语言:javascript
复制
    // 循环左移
    // @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),然后计算出左移 的值,再计算出右移的值。两者 进行 或 运算即可。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-04-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Integer
  • Integer 的二进制算法
    • 循环移位 (按位操作的补充)
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档