Java 数据类型

110 阅读4分钟

Java 主要实践了面向对象的编程范式,在语言设计中有很多继承、多态和封装的考虑。

1.数据类型

基础数据类型都是小写命名的,例如 int,而对应的面向对象往往都有封装,例如 Integer。
当数据处理是一次性的,彼此没有联系时,适合使用基础类型,当数据需要被管理,在不同类中传递迭代有时还要做类型转换时,适合使用封装类型。从设计上看,基础类型比较高效,但传递过程是值传递的,管理中要注意使用方式。

基础类型和封装类型的区别:

  • 基础类型按值传递,封装类型按引用传递;
  • 基础类型一般存储在栈中,封装类型是对象,创建和存储一般在堆上;

方法中声明的变量是局部变量,调用方法时会为其创建一个方法栈,变量存放在方法栈中。如果是基本类型变量,变量名和值都存放在虚拟机栈中;如果是引用变量,声明的变量放在虚拟机栈中,变量所指对象放在堆内存中存储。
类中声明的变量是全局变量,放在堆中。如果是基本类型变量,变量名和值都放在堆内存中;如果是引用变量,声明的变量放在堆内存中,变量所指对象也存储在堆内存中。
举例 int[] array = new int[]{1};,此时创建了一个对象 array,对象中的一个元素是基础类型 1,此时对象和基础类型 1 都存储在堆内存中。

  • 基础类型使用高效,封装类型有统一通用方法,减少开发成本;

基础类型分为三类

  • 数值型
    • 整数:byte, short, int, long
    • 浮点:float, double
  • 字符型:char
  • 布尔型:boolean
基本类型封装类型占用字节数缺省值封装类型缓存池
booleanBoolean1falseJVM 编译期转换为 int,true 转换为 1,false 转换为 0
byteByte80(byte)[-128, 127]
charCharacter16\u0000(空 "")[0, 127](\u0000 to \u007F)
shortShort160(short)[-128, 127]
intInteger320[-128, 127](可以通过 VM 参数 -XX:AutoBoxCacheMax=200 修改缓存上限)
floatFloat320.0f-
longLong640L[-128, 127]
doubleDouble640.0d-

float 和 double 没有封装类型缓存池的原因是在一个范围中浮点型数值是无限的,无法缓存

在大数据场景下,为了提高精度和表达范围,java.math 提供了 BigDecimalBigInteger 的类。

2.封装类型

创建方法:

  • 通过封装类构造方法创建:变量名=new 封装类构造方法(参数);

Integer i = new Integer(2);

  • 通过值转换方法创建:变量名=封装类.valueOf(参数);

Integer i = Integer.valueOf(2);

通用方法:

  • compareTo() 数值上比较两个对象,等于返回 0,小于返回 -1,大于返回 1
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 的 compareTo 源码。

  • equals() 比较两个对象,a.equals(b) 当且仅当 b 非 null,且被比较的对象值相同时返回 true。
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

上面是 Integer 的 equals 源码,传入对象 obj 为 null 或者比较二者 value 不相等时都返回 false

  • toString() 将对象用字符串形式表示。
public static String toString(int i, int radix) {
    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        radix = 10;

    /* Use the faster version */
    if (radix == 10) {
        return toString(i);
    }

    char buf[] = new char[33];
    boolean negative = (i < 0);
    int charPos = 32;

    if (!negative) {
        i = -i;
    }

    while (i <= -radix) {
        buf[charPos--] = digits[-(i % radix)];
        i = i / radix;
    }
    buf[charPos] = digits[-i];

    if (negative) {
        buf[--charPos] = '-';
    }

    return new String(buf, charPos, (33 - charPos));
}  

public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}

static int stringSize(int x) {
    for (int i=0; ; i++)
        if (x <= sizeTable[i])
            return i+1;
}

以上是 Integer 的 toString 源码,Integer 支持了进制表达的转换相对复杂,缺省是十进制转换,支持 二 到 三十六 进制。如果是十进制,使用 DigitOnesDigitTens 构建字符数组,其他进制使用 digits 构建字符数组(0-9,a-z)

  • parseXXX() 解析字符串参数。这里的源码实现比较复杂,不在此解析。

拆装箱: 用于在基础类型和封装类型之间转换,避免开发者关注类型转换细节降低开发速度。

  • 装箱:通过调用包装器 valueOf 方法实现 Integer a = 10; 自动创建了 Integer 封装类型将基础类型 10 封装在对象中
  • 拆箱:通过调用包装器 xxxValue 方法实现 Integer a = 10; int i = a.intValue(); 将封装类型对象 a 的封装值 10 解析出来,赋值给基础类型 i。

拆箱过程要注意 null 值,这种情况找不到对象,无法取值。

3.类型转换

boolean 类型与其他 7 种基础类型数据间不能转换;
其他 7 种基础类型数据可以互相转换,过程中可能损失精度。转换排序 double > float > long > int > short > byte
当较低顺位类型转换为较高顺位类型时,没有精度损失,可以自动转换 隐式,无需操作;
反之出现了精度损失,需要使用转换符操作 显示

byte b;
int i = 128;
double d = 3.14;

b = (byte) i;  // i 是 257,强制转换出现损失,b 变成 -128
i = (int) d;  // d 是 3.14,强制转换出现损失,i 变成 3

4.缓存池

下面是各个基础类型对应封装类型的 Cache 实现源码

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));
    }
}

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);
    }
}

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));
    }
}

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

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);
    }
}

下面举例缓存池对于程序使用的影响

Integer a1 = 12;
Integer a2 = 12;
Integer b1 = 129;
Integer b2 = 129;
System.out.println(a1 == a2);  // true 缓存返回
System.out.println(b1 == b2);  // false 未缓存,创建返回了不同的对象

5.总结

Java 提供 8 种基础数据结构,并对应支持了 8 种封装数据结构,提供通用方法以提高开发效率,实现自动装箱和拆箱,并缓存了一些封装数据结构的常用对象。