基本数据类型踩过的坑,一一剖析, 提纲挈领,言简意骇~
相信初入Java,都会有手写基本数据类型的情形,
所以这一次,好好输出一下
其实,基本数据类型
在Java层面 byte short int long
float double
char
boolean
------------- 这八中基本数据类型
在JVM层面 其实是九种数据类型
What?九种?平时只听说以及用到的是八种。
是的,在JVM源码中,在JVM中,
多了一个returnAddress类型
只不过呢,returnAddress类型会被JVM中的无条件转移指令jsr、ret和jsr_w指令所使用。returnAddress类型的值指向一条虚拟机指令的操作码
意思就是:对于 JVM 来说,程序就是存储在方法区的字节码指令,returnAddress 类型的值就是指向特定指令内存地址的指针
returnAddress 数据只存在于字节码层面,与编程语言无关,也就是说,
在 Java 语言中是不会直接与 returnAddress 类型的数据打交道的。
所以,在这里,好好的谈谈Java中 八种基本数据类型
装箱 拆箱 概念以及理论
Java为每种基本数据类型都提供了对应的包装器类型
基本类型 包装类型 字节 位数 默认值(基本类型) 取值范围
byte Byte 1 8 0 -2^7 - 2^7-1
short Short 2 16 0 -2^15 - 2^15-1
int Integer 4 32 0 -2^31 - 2^31-1
long Long 8 64 0 -2^63 - 2^63-1
float Float 4 32 0.0 -2^31 - 2^31-1
double Double 8 64 0.0 -2^63 - 2^63-1
char Character 2 16 0 - 2^16-1
boolean Boolean false true\false
所以:一个字节 就是 八位
装箱 拆箱 顾明思义,意思就类似于 寄快递 与 拆快递的过程
装箱: 基本类型 to 包装类型
自动将基本数据类型转换为包装器类型
拆箱: 包装类型 to 基本类型
自动将包装器类型转换为基本数据类型
Integer i = 70; //装箱 (自动将基本数据类型转换为包装器类型)
int n = i; //拆箱 (自动将包装器类型转换为基本数据类型)
装箱 拆箱 字节码剖析
以 int Integer 为例 字节码 剖析 装箱 拆箱
Integer i = 70;
int j = i;
stack=2, locals=4, args_size=1
0: bipush 70
2: invokestatic #2
// 装箱
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: invokevirtual #3
// 拆箱
// Method java/lang/Integer.intValue:()I
从字节码内容可以看出,
装箱:自动调用的是Integer的valueOf(int)方法。
拆箱:自动调用的是Integer的intValue方法
装箱 拆箱 总结
总结:
装箱:调用包装器的valueOf方法实现的
拆箱:调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)
基本概念的区分:
1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型
2、Integer 变量必须实例化后才能使用,而int变量不需要
3、Integer 实际是对象的引用,当new一个 Integer时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值
4、Integer的默认值是null,int的默认值是0
基本数据类型
包装类型 使用场景剖析
在代码中 会见到这样的 写法
int i = 70;
Integer j = 70;
Integer k = new Integer(70);
以上,字节码会是如何,使用场景又是如何?
0: bipush 70 // int i = 70
2: istore_1
3: bipush 70 // Integer j = 70
5: invokestatic #2
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
8: astore_2
9: new #3
// Integer k = new Integer(70)
// class java/lang/Integer
12: dup
13: bipush 70
15: invokespecial #4
// Method java/lang/Integer."<init>":(I)V
18: astore_3
LocalVariableTable:
Start Length Slot Name Signature
0 20 0 args [Ljava/lang/String;
3 17 1 i I
9 11 2 j Ljava/lang/Integer;
19 1 3 k Ljava/lang/Integer;
从字节码可以看到:
1、int i = 70 变量 i 是直接存储到 局部变量表中
2、Integer j = 70 自动调用Integer.valueOf() 装箱
实际上是 Integer j = Integer.valueOf(70)
3、Integer k = new Integer(70)
通过 new 生成了对象
那么,在实际运用中,到底用哪一种呢?
变量 尽量不要定义为包装类,尽量使用基本类型。
int和Integer的区别
1.存储原理不一样:
int:属于基本类型,不存在“引用”这个概念;其数据是存储在栈空间中;
Integer:属于包装类型,引用存储在栈中,对象数据存储在堆中;
在进行参数传递的时候,int是值传递,其在栈中的数据不可变;
而Integer类型是引用传递,引用指向的内存地址中的数据是可以变化的,但是栈中的引用是不变的;
2.缺省值不一样
int的初始化值是0 ,Integer初始化的值是null。
3.泛型支持不一样
泛型支持Integer,不支持int
4.方法不一样
int i =70;
Integer j= new Integer(70);
在方法调用中:
i是基本类型,并没有什么方法;因为方法是类的特性。
j是包装类型,有很多方法,因为方法是对象中定义的
int Integer 剖析
int i = 70;
Integer j = 70;
Integer k = new Integer(70);
//实际上 看看字节码 在以下这三行的时候 发生了什么?
System.out.println(i==j);
System.out.println(i==k);
System.out.println(j==k);
字节码剖析:
1 当进行到这里的时候
System.out.println(i==j);
24: invokevirtual #6
// Method java/lang/Integer.intValue:()I
看到了 Integer j 自动掉用了 Integer.intValue()
即为 拆箱,自动的 转为了 基本数据类型
2 当进行到这里的时候
System.out.println(i==k);
43: invokevirtual #6
// Method java/lang/Integer.intValue:()I
看到了 Integer k 自动掉用了 Integer.intValue()
即为 拆箱,自动的 转为了 基本数据类型
if_icmpne 指令 : 比较两个栈顶的引用,当结果不相等的时候跳转
由以上 字节码指令 可以 总结:
int Integer 字节码总结
1 Integer j = 70
Integer j = 70
在编译时,会装箱成为 Integer j = Integer.valueOf(70)
Integer.valueOf() -------- 源码分析
/**
返回一个表示指定值的{@code Integer}实例
* {@code int}值。如果没有新的{@code Integer}实例
需要时,一般应优先使用此方法
*构造函数{@link #Integer(int)},因为这个方法是可能的
*以产生更好的空间和时间性能
缓存频繁请求的值。
*此方法将始终缓存-128到127的值,
*包括,并可能缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
所以:Integer.valueOf()
java对于-128到127之间的数,会进行缓存。
所以Integer j = 70
时,会将70进行缓存,下次再写Integer j = 70时,就会直接从缓存中取,就不会new了。
意思就是说:
int 取值范围为 : -2^31 - 2^31-1
但是呢?对 -128 到 127 之间的值进行了缓存
如果 传入的值 在-128到127之间,直接返回 缓存中的值,不需要new 对象
否则 要进行new 对象
2 当 int 与 Integer 比较的时候
System.out.println(i==j);
System.out.println(i==k);
包装类Integer 和 基本数据类型int 比较时,
java会自动拆包装为int ,然后进行比较,
实际上就变为两个int变量的比较
int Integer 题目练手
根据以上的结论(由字节码总结结论)
如果可以一眼说出答案,那么 对于 int Integer 可以ok~
那么 对于 整型数据类型 byte short int long
这里以 int 作为例子演示,其他三个 依次类推
浮点类型 double Double
根据上文的结论 这里 猜想为 true true false ?
答案正确吗?
字节码:
0: ldc2_w #2 // double 70.0d
3: dstore_1
4: ldc2_w #2 // double 70.0d
7: invokestatic #4
// Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
10: astore_3
11: ldc2_w #2 // double 70.0d
14: invokestatic #4
// Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
17: astore 4
19: ldc2_w #5 // double 700.0d
22: invokestatic #4
// Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
25: astore 5
27: ldc2_w #5 // double 700.0d
30: invokestatic #4
// Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
由字节码可知,这里的 double Double
与上文的 int Integer
是如此的相似。都会涉及到 装箱,拆箱
But 这里的 Double.valueOf 源码剖析?
Double.valueOf 源码剖析
/**
返回一个代表指定值的{@code Double}实例
* {@code double}值。
*如果不需要新的{@code Double}实例,则使用此方法
*通常应该优先使用构造函数
* {@link #Double(Double)},因为这个方法可能会产生
*缓存显著提高了空间和时间性能
频繁请求的值
*/
public static Double valueOf(double d) {
return new Double(d);
}
由源码可以知道了
Double.valueOf() 并没有范围缓存
而是 new 对象
那么 对于 浮点数据类型 float double
这里以 double作为例子演示,另外一个 依次类推
char Character
字节码:
4: sipush 20013
7: invokestatic #2
// Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
10: astore_2
11: sipush 20013
14: invokestatic #2
// Method java/lang/Character.valueOf:(C)Ljava/lang/Character;
17: astore_3
18: getstatic #3
// Field java/lang/System.out:Ljava/io/PrintStream;
21: iload_1
22: aload_2
23: invokevirtual #4
// Method java/lang/Character.charValue:()C
Character.valueOf()源码
/**
*返回一个<tt>字符</tt>实例表示指定的
* < tt >字符< / tt >价值。
*如果不需要新的<tt>字符</tt>实例,则使用此方法
*通常应该优先使用构造函数
* {@link #Character(char)},因为这个方法可能会产生
*缓存显著提高了空间和时间性能
频繁请求的值。
*
*此方法将始终缓存{@code范围内的值
*/
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
boolean Boolean
字节码:
3: invokestatic #2
// Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
6: astore_2
7: iconst_0
8: invokestatic #2
// Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
11: astore_3
12: iconst_1
13: invokestatic #2
// Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
16: astore 4
18: iconst_1
19: invokestatic #2
// Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
22: astore 5
24: getstatic #3
// Field java/lang/System.out:Ljava/io/PrintStream;
27: iload_1
28: aload_2
29: invokevirtual #4
// Method java/lang/Boolean.booleanValue:()Z
同样的 都会涉及到 装箱,拆箱
but Boolean.valueOf 源码
/**
*返回一个代表指定值的{@code布尔}实例
* {@code boolean}值。如果指定的{@code boolean}值
*是{@code true},这个方法返回{@code Boolean.TRUE};
*如果是{@code false},则此方法返回{@code Boolean.FALSE}。
*如果不需要新的{@code Boolean}实例,则使用此方法
*通常应该优先使用构造函数
* {@link #Boolean(Boolean)},因为这个方法可能会产生
*显著提高空间和时间性能。
*/
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
赶快来分享关注吖