基本数据类型
Java中有哪8种数据类型
基本类型 | 位数 | 字节 | 默认值 |
---|---|---|---|
int | 32 | 4 | 0 |
short | 16 | 2 | 0 |
long | 64 | 8 | 0L |
byte | 8 | 1 | 0 |
char | 16 | 2 | 'u0000' |
float | 32 | 4 | 0f |
double | 64 | 8 | 0d |
boolean | 1 | false |
对于 boolean ,官方未明确定义,它依赖于JVM厂商的具体实现。逻辑上占用1位,实际中考虑计算机高效存储因素。
注意:
- Java中使用long类型的数据一定要在数值后面加L,否则将作为整型解析。
- char a = ‘h’ char:单引号 , String a = “hello” Sting:双引号
包装类型(8中基本类型与之对应)
Integer、Short、Long、Byte、Character、Float、Double、Boolean。
除了int和char改变外其余首字母变大写。
基本类型与包装类型的区别
基本类型有默认值且不是Null,包装类型不赋值是Null。
从JVM层面讲:
基本数据类型直接存放在Java虚拟机栈中的局部变量表中,而包装类型属于对象类型,对象实例都存在与堆中,相比对象类型,基本数据类型占用的空间较小。
局部变量表主要存放了编译期间可知的基本数据类型,对象引用类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象的相关位置。
包装类型的常量池技术
Java基本类型的包装类型的大部分都实现了常量池技术。
Byte、Short、Integer、Long这4种包装类型默认创建了数值 [-128,127] 的相关类型的缓存数据,Character创建了数值在 [0,127] 范围的缓存数据,Boolean直接返回 True 或 False 。
Integer缓存源码:
/**
*此方法将始终缓存-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);
}
public class Main {
public static void main(String[] args) {
Integer a = 100;
Integer b = 100;
Integer c = 128;
Integer d = 128;
System.out.println(a==b); //true
System.out.println(c==d); //false
}
}
/*
说的就是在通过valueOf 方法创建Integer 对象的时候,如果数值在[-128,127]之间,
便返回指向IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的Integer对象。
所以上面代码中a 与b 相等, c 与d 不相等。*/
public class Main {
public static void main(String[] args) {
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);
}
}
/* Integer i1=40这行代码会发生拆箱,等价于Integer i1 = Integer.valueOf(40)。
所以i1使用的是常量池中的对象,而Integer i1 = Integer(40)会直接创建新的对象。
所以答案位 false。*/
Character缓存源码:
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
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);
}
}
Boolean缓存源码:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public class Main {
public static void main(String[] args) {
Boolean a = false;
Boolean b = false;
Boolean c = true;
Boolean d = true;
System.out.println(a==b); //true
System.out.println(c==d); //true
}
}
如果超出对应范围会创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
两种浮点数据类型包装类Float,Double并没有实现常量池技术。
public static Double valueOf(double d) {
return new Double(d);
}
public class Main {
public static void main(String[] args) {
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
}
}
最后看看下面这段程序的输出结果
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Long g = 3L;
int int1 = 12;
int int2 = 12;
Integer integer1 = new Integer(12);
Integer integer2 = new Integer(12);
Integer integer3 = new Integer(1);
System.out.println("c==(a+b) ->"+ (c==(a+b)));
System.out.println("g==(a+b) ->" + (g==(a+b)));
System.out.println( "c.equals(a+b) ->" + (c.equals(a+b)));
System.out.println( "g.equals(a+b) ->" + (g.equals(a+b)));
System.out.println("int1 == int2 -> " + (int1 == int2));
System.out.println("int1 == integer1 -> " + (int1 == integer1));
System.out.println("integer1 == integer2 -> " + (integer1 ==
integer2));
System.out.println("integer3 == a1 -> " + (integer3 == a));
}
}
/*
c==(a+b) ->true
g==(a+b) ->true
c.equals(a+b) ->true
g.equals(a+b) ->false
int1 == int2 -> true
int1 == integer1 -> true
integer1 == integer2 -> false
integer3 == a1 -> false
*/
1.当 "=="运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果
其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。所
以c==a+b , g==a+b 为true 。
2.而对于equals 方法会先触发自动拆箱过程,再触发自动装箱过程。也就是说a+b,会先各自调
用intValue 方法,得到了加法运算后的数值之后,便调用Integer.valueOf 方法,再进行
equals 比较。所以c.equals(a+b) 为true 。而对于g.equals(a+b) , a+b 会先拆箱进行相加
运算,在装箱进行equals 比较,但是装箱后为Integer , g 为Long ,所以g.equals(a+b) 为
false 。
3. int1 == int2 为true 无需解释, int1 == integer1 ,在进行比较时, integer1 会先进行
一个拆箱操作变成int 型在进行比较,所以int1 == integer1 为true 。
4. integer1 == integer2 -> false 。integer1 和integer2 都是通过new 关键字创建的,可以
看成两个对象,所以integer1 == integer2 为false 。integer3 == a1 -> false ,
integer3 是一个对象类型,而a1 是一个常量它们存放内存的位置不一样,所以integer3 ==
a1 为false ,具体原因可学习下java的内存模型。
注意:所有整型包装类对象之间值的比较,全部使用equals方法比较。
对于Integer var = ? 在 -128至127 之间的赋值,Integer对象在IntegerCache.cache产生会复用对象,这个区间可以直接用==判断,但是区间外的所有数据,都会在堆上产生,不会复用已有的对象,所以推荐用equals方法进行判断。
结论 : Integer 、Short 、Byte 、Character 、Long 这几个类的valueOf 方法的实现是类似
的。Double 、Float 的valueOf 方法的实现是类似的。然后是Boolean 的valueOf 方法是单独一组
的。
包装类型的存在理由
Java是面向对象的语言,对象就是其灵魂,除了定义一些常量与局部变量外,在很多比方比如方法参数、对象属性中很少会使用基本类型来定义变量。
若一个对象中的属性使用了基本类型,那这个属性就存在默认值,这个逻辑不正确,因为很多业务下,对象的某些属性没有赋值,我们希望它为null。除此之外泛型参数不能为基本类型,因为基本类型不是 Object 子类,应该用基本类型对应的包装类型代替。
比如:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
}
Map<Integer, Set<String>> map = new HashMap<>();
自动拆箱与装箱
基本类型与包装类型的转换:
Integer i = 100; //装箱
int n = i; //拆箱
装箱就是调用了包装类的 valueOf() 方法,拆箱就是调用了 xxxValue() 方法。
所以
- Integer i = 10等价于 Integer i = Integer.valueOf(10)
- int n = i 等价于 int n = i.intValue();
自动拆箱引发的NPE问题
《阿里巴巴开发手册》有条规定
关于基本数据类型与包装数据类型的使用标准如下:
1)【强制】所有的POJO类属性必须使用包装数据类型。
2)【强制】RPC方法的返回值和参数必须使用包装数据类型。
3)【推荐】所有局部变量使用基本数据类型。
说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显示地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
正例:数据库查询结果可能为Null,因为自动拆箱,用基本数据类型接收有NPE风险。
反例:业务的交易报表上显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的默认值,页面显示为0%,这是不合理的,因该显示成中划线-。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败,异常退出。