1 Java基本数据类型

在Java有8种基本数据类型,分别是:

基本类型 位数 字节 默认值 范围 包装类
byte 8 1 0 -128-127 Byte
short 16 2 0 -32768-32767 Short
int 32 4 0 -2147483648 - 2147483647 Integer
long 64 8 0L Long
float 32 4 0f Float
double 64 8 0d Double
char 16 2 ‘u0000’ Character
boolean 1 false 比较特殊,当作为单变量是与int相等;当作为数组时占用1字节 Boolean

注意:

  1. Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析。
  2. char a = 'h':单引号,String a = "hello" :双引号。

    2 为什么char类型是双字节

    主要是为了向前兼容。
    JVM中内码采用UTF16。早期,UTF16采用固定长度2字节的方式编码,两个字节可以表示65536种符号(其实真正能表示要比这个少),足以表示当时unicode中所有字符。但是随着unicode中字符的增加,2个字节无法表示所有的字符,UTF16采用了2字节或4字节的方式来完成编码。Java为应对这种情况,考虑到向前兼容的要求,Java用2个char来表示那些需要4字节的字符。所以,java中的char是占用两个字节,只不过有些字符需要两个char来表示。

char在内存中采用UTF-16编码,一些字符需要使用两个char表示,故1个字符可能需要2-4个字节表示。在UTF-8中,英文字符占用1个字节,大部分汉字占用3个字节,少量占用4字节;而在UTF-16中,英文字符占用2个字节,大多数汉字占用2字节,个别汉字占用4个字节

3 基本数据类型的操作

首先看下面几行代码:

  1. byte b1 = 10;
  2. byte b2 = 11;
  3. byte b3 = b1 + b2;// 不合法,右操作数类型默认是int型,不进行强转会损失精度
  4. byte b3 = (byte)(b1 + b2);// 正确
  5. short s1 = 1;
  6. s1 = s1 + 1;//不合法,右操作数类型默认是int型,从int转换到short可能会有损失

上述几行代码中,最难理解的应该是第 3 行。明明是 2 个byte类型的数相加,结果也应该是byte类型,为什么会出错呢?
答:
如果要深究的话,要从 JVM 的指令集说起,每种指令代表了一种操作。在Java虚拟机中,对于大部分与数据类型相关的字节码指令,他们的操作码助记符中都有特殊的字符来表示专门为哪种数据类型服务。但是Java虚拟机的操作码长度只有一个字节,这就带来一个问题,如果每种与数据类型相关的指令都支持Java虚拟机运行时数据类型的话,显然一个字节就不够用了。 面对这种情况,对于特定的操作,JVM 选择其中部分数据类型提供指令,这种特性被称为“Not Orthogonal”。
大部分的指令都没有支持byte、char、short,甚至没有任何指令支持boolean类型。编译器在编译期或者运行期将byte和short类型的数据带符号扩展为相应的int类型数据将boolean和char类型数据零位扩展为相应的int类型数据。与之类似的,在处理byte、char、short和boolean类型的数组时,也会转换成对应的int类型的字节码指令来处理。
因此,大多数对于byte、short、char和boolean类型数据的操作,实际上都是使用相应的int类型作为运算类型的。关于这部分的解释,可以查看:《深入理解JVM虚拟机(第三版)》的第 6.4.1 节。
所有在上述第 3 行中,两个 byte 类中的数相加,结果其实是一个 int 类型的数。如果把 int 类型的数赋值给 byte 类型,不进行强制类型转换是会报错的。

再看看第6行,左边是 short 类型 + int 类型,结果是数值范围较大的类型,也就是 int 类型。把 int 赋值给 short 类型,不进行强制类型转换是会报错的。

总结一下:
变量相加,如果变量的类型是byte、short,则先提升类型至 int ,然后再计算。

4 Java中包装类及装箱、拆箱

虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性、没有方法可调用。沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单、有效地进行常规数据处理。为解决此类问题,Java为每种基本数据类型分别设计了对应的类,称之为包装类。每个包装类的对象可以封装一个相应的基本类型的数据,并提供了其它一些有用的方法。包装类对象一经创建,其内容(所封装的基本类型数据值)不可改变。
包装类型不赋值就是 Null ,而基本类型有默认值且不是 Null

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;

下面代码描述了自动装箱的过程:

  1. Integer i3 = 1;// 当把一个int型赋给一个Integer型时,引发编译器自动装箱,转化为Integer i3 = new Integer(1);

下面代码描述了自动拆箱的过程:

  1. i4 = new Integer(1);// 由于基本类型无法指定对象,所以编译器会自动拆箱,该条语句会转换为:int i4 = new Integer(1).intvalue();

5 Java中包装类的常见误区

  1. public void testEquals() {
  2. int int1 = 12;
  3. int int2 = 12;
  4. Integer integer1 = new Integer(12);
  5. Integer integer2 = new Integer(12);
  6. Integer integer3 = new Integer(127);
  7. Integer a1 = 127;
  8. Integer a2 = 127;
  9. Integer a = 128;
  10. Integer b = 128;
  11. System.out.println("int1 == int2 -> " + (int1 == int2)); // true
  12. System.out.println("int1 == integer1 -> " + (int1 == integer1)); // true
  13. System.out.println("integer1 == integer2 -> " + (integer1 == integer2)); // false
  14. System.out.println("integer3 == a1 -> " + (integer3 == a1)); // false
  15. System.out.println("a1 == a2 -> " + (a1 == a2)); // true
  16. System.out.println("a == b -> " + (a == b)); // false
  17. }
  • 1.略
  • 2.Integer是int的封装类,当Integer与int进行==比较时,Integer就会拆箱成一个int类型,所以还是相当于两个int类型进行比较。
  • 3.两个都是对象类型,就不会进行拆箱比较,new出来的不同对象不想等。
  • 4.integer3是新建的一个对象类型,而a1是自动装箱的,从缓冲池中获取的一个对象,它们存放内存的位置不一样,所以也不等。
  • 5.127在缓冲池的范围内,无论调用多少次,都返回同一个对象的引用,所以相等。
  • 6.128不在缓存范围内,所以会new出两个不同的对象,所以不等。

    6 隐式类型转换和显示类型转换

  • 当将占位数少的类型赋值给占位数多的类型时,java自动使用隐式类型转换(如int型转为long型)

  • 当把在级别高的变量的值赋给级别低变量时,必须使用显式类型转换运算(如double型转为float型)

    7 包装类中的缓存

    包装类存在缓存池设计,在 JDK8 中,Integer 缓存池可缓存的数值范围为-128 到 127。Integer对象是在IntegerCache.cache中产生,这个区间的Integer值会尝试从缓存池中取对象(如果存在的话),多次调用会取得同一个对象的引用,可以直接使用 == 进行判断。而在这个区间外的数据,都会在堆内存中产生,需要使用equals方法进行判断。
    当使用范围在-128 到 127之间的数创建包装类时,会尝试从缓存池中取对象(如果存在的话),多次调用会取得同一个对象的引用;而new Integer(123)则会新建一个对象,下面是常见的比较:

    1. Integer i1 = new Integer(123);
    2. Integer i2 = new Integer(123);
    3. System.out.println(i1 == i2); // false
    4. Integer i3 = Integer.valueOf(123);
    5. Integer i4 = Integer.valueOf(123);
    6. System.out.println(i3 == i4); // true
    7. Integer i5 = 123;
    8. Integer i6 = 123;
    9. System.out.println(i5 == i6); // true

    8 Integer中valueOf方法的实现

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

    valueOf方法首先判断值是否在缓存池中,如果在的话就直接返回缓存池中的内容。在 JDK8 中,Integer 缓存池可缓存的数值范围为 -128 到 127。所以,在申请一个 [-128,127] 内的数时,其实是从 IntegerCache.cache 中直接取出来用的,如果不在这个范围则是 new 了一个 Integer 对象。
    编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。值得注意的是这个上界是可以通过 JVM 参数修改。
    从下面的例子中可以看出:

  • 超出 Integer 缓存池则 new 一个新的 Integer 对象;多次使用会创建不同的新对象。

  • Integer内部重写了 equals 方法,比较的是里面的值。
  • 如果使用 == 比较对象是否是同一个对象。在缓存池范围内的,是同一个对象;超过缓存池而 new 出来的,不是同一个对象。 ```java Integer i1 = Integer.valueOf(1); Integer i2 = Integer.valueOf(1); Integer i3 = Integer.valueOf(1280); Integer i4 = Integer.valueOf(1280);

System.out.println(i1 == i2); // true System.out.println(i1.equals(i2)); // true System.out.println(i3 == i4); // false System.out.println(i3.equals(i4)); // true ```

9 字符型常量和字符串常量的区别

  • 从形式上来看,字符常量是单引号引起的一个字符;字符串常量是双引号引起的若干个字符。
  • 从含义上来看,字符常量相当于一个整型值(即ASCII值),可以参加表达式运算;字符串常量代表一个地址值,即该字符串在内存中存放位置。
  • 从占用空间上看,字符常量只占2字节;字符串常量占若干个字节。

参考