Java数据类型——基本类型/包装类型
Java数据类型(基本类型/引用类型)
基本类型
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型;Java是一种面向对象语言,为了让基本类型具备对象特性(比如方法调用),Java为每种基本类型提供了一个包装类。
类型 | 默认值 | 占用储存空间 | 存储范围 | 包装类 |
---|---|---|---|---|
byte | 0 | 1*byte | -128(-2^7)~ 127(2^7-1) | Byte |
short | 0 | 2*byte | -32768(-2^15)~ 32767(2^15 - 1) | Short |
int | 0 | 4*byte | -2,147,483,648(-2^31)~ 2,147,483,647(2^31 - 1) | Integer |
long | 0L | 8*byte | -9,223,372,036,854,775,808(-2^63)~ 9,223,372,036,854,775,807(2^63 -1) | Long |
float | 0.0f | 4*byte | 1.4E-45 ~ 3.4028235E38 | Float |
double | 0.0d | 8*byte | 4.9E-324 ~ 1.7976931348623157E308 | Double |
boolean | false | 1*bit | false、true | Boolean |
char | false | 2*byte | \u0000 ~ \uffff | Character |
因为Java为每种基本类型提供了包装类型,实际工作中,更多的是使用包装类型;那对于包装类型的比较应该是大家都会遇到的一些坑;
- 装箱与拆箱
package com.sjw.box;
public class DateType {
public static void main(String[] args) {
int i = 100;
// 自动装箱(autoboxing) 等价于 Integer a=Integer.valueOf(100);
Integer a = 100;
Integer b = 100;
// 包装类型与基本类型通过==比较,比较值大小
// 拆箱(unboxing) 等价于 a.intValue()==i
System.out.println(a == i);
// 包装类型通过==比较,比较地址
System.out.println(a == b);
// 包装类型通过equals比较,比较值大小
System.out.println(a.equals(b));
}
}
为了增强说服力,下面我们通过JDK自带的javap反解析工具,分析上述Java代码编译生成的Class文件。
// javap -c -l DateType.class
Compiled from "DateType.java"
public class com.sjw.box.DateType {
public com.sjw.box.DateType();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sjw/box/DateType;
public static void main(java.lang.String[]);
Code:
// 将int 100 push压入操作数栈
0: bipush 100
// pop出栈(int 100),赋值给局部变量表索引为1的变量 int i
2: istore_1
// 将int 100 push压入操作数栈
3: bipush 100
// pop出栈(int 100),调用Integer.valueOf,并将方法返回值(引用)push压入操作数栈;装箱(autoboxing)
5: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
// pop出栈(引用地址),赋值给局部变量表索引为2的变量 Integer a
8: astore_2
// 将int 100 push压入操作数栈
9: bipush 100
// pop出栈(int 100),调用Integer.valueOf,并将方法返回值(引用)push压入操作数栈;装箱(autoboxing)
11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
// pop出栈(引用地址),赋值给局部变量表索引为3的变量 Integer b
14: astore_3
// 将静态属性PrintStream System.out push压入操作数栈
15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
// 将局部变量表索引为2的引用类型变量a push压入操作数栈
18: aload_2
// pop出栈a(引用)调用其intValue方法,并将返回值push压入操作数栈;拆箱(unboxing)
19: invokevirtual #4 // Method java/lang/Integer.intValue:()I
// 将局部变量表索引为1的int类型变量i push压入操作数栈
22: iload_1
// pop操作数栈栈顶2个元素,进行int比较,不相等跳转到Code 30,相等继续
23: if_icmpne 30
// 将int常量1压入操作数栈
26: iconst_1
// 跳转Code 31
27: goto 31
// 将int常量0压入操作数栈
30: iconst_0
// pop操作数栈栈顶2个元素(out,1或0),以栈顶元素(1或0)作为参数调用out引用的方法println
31: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
34: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
// 将局部变量表索引为2的引用类型变量a push压入操作数栈
37: aload_2
// 将局部变量表索引为3的引用类型变量b push压入操作数栈
38: aload_3
// pop操作数栈栈顶2个元素,进行引用对象比较(比较地址),不相等跳转到Code 46,相等继续
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
50: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
53: aload_2
54: aload_3
55: invokevirtual #6 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
58: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
// 将局部变量表索引为2的引用类型变量a push压入操作数栈
61: aload_2
// pop出栈a(引用)调用其intValue方法,并将返回值push压入操作数栈;拆箱(unboxing)
62: invokevirtual #4 // Method java/lang/Integer.intValue:()I
// pop出栈(int 100),赋值给局部变量表索引为1的变量 int i
65: istore_1
66: return
// Java代码行与Code汇编指令行偏移量映射表
LineNumberTable:
// Java代码第5行代码对用Code汇编指令0-2
line 5: 0
// Java代码第7行代码对用Code汇编指令3-8
line 7: 3
line 8: 9
line 11: 15
line 13: 34
line 15: 50
line 17: 61
line 18: 66
// 局部变量表
LocalVariableTable:
Start Length Slot Name Signature
// 函数参数args
0 62 0 args [Ljava/lang/String;
// int变量 i
3 59 1 i I
// Integer变量 a
9 53 2 a Ljava/lang/Integer;
// Integer变量 b
15 47 3 b Ljava/lang/Integer;
}
注本文重点不是分析javap反解析class字节码文件的具体信息,具体如何分析、汇编指令等参考官方文档。
经过上面简单分析,了解int包装类型Integer的装箱(Integer.valueOf)、拆箱(Integer.intValue)的过程;对于常见的面试题
public void compare(Integer arg) {
int a = 0;
Integer b = 1;
// 拆箱(unboxing),arg.intValue();如果arg为null,会出现NPE(NullPointerException)
if (arg == a) ;
// 不需要拆箱(unboxing),比较引用对象,即地址的比较
if (arg == b) ;
// 如果arg为null,会出现NPE(NullPointerException)
if (arg.equals(b)) ;
// 如果arg为null,不会出现NPE(NullPointerException),
if (b.equals(arg)) ;
}
// Integer.java源码
public boolean equals(Object obj) {
// 如果obj为null,直接返回false
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
- 包装对象的缓存
正如前面介绍,包装对象的值大小比较需要使用equals,在一定范围内包装类型==比较是可以的,注意不是比较值,依然比较地址
public void compare() {
// 装箱(autoboxinxg) Integer a = Integer.valueOf(100);
Integer a = 100;
Integer b = 100;
Integer c = new Integer(100);
Integer e = 1000;
Integer f = 1000;
// ture
if (a == b) ;
// false
if (a == c) ;
// ?
if (e == f) ;
}
上述代码 e == f 是ture还是false?不要急向下看,Java如何缓存对象的?如何分析,我们知道new出来的对象用==比较,只要没用引用同一个对象就为false;对于通过装箱来的包装对象可能用的是缓存对象,我们知道装箱的过程是通过valueOf()方法的;
// valueOf源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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;
// 可通过jvm参数-XX:AutoBoxCacheMax进行配置
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
// 取127与配置值的最大值
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() {}
}
通过int的包装类型Integer分析了一下装箱(autoboxing)、拆箱(unboxing),以及缓存IntegerCache是如何实现的,并且IntegerCache缓存范围默认是-128~127,max value可以通过jvm参数-XX:AutoBoxCacheMax进行配置;假设jvm参数加上-XX:AutoBoxCacheMax=2000;会缓存value值为-127~2000的封装类型,以供通过装箱生成的对象实例;到这里可以回答e==f是true还是false。
对于其他基本类型long、float、double对应的包装类型Long、Float、Double装箱、拆箱以及是否缓存,感兴趣的可以通过Java源码学习了解一下;本文代码样例采用JDK1.8。
知其然,更要知其所以然。。。