基本数据类型
Java基本数据按类型可以分为四大类:布尔型,整数型,浮点型,字符型,这四大类包含8中基本数据类型。
- 布尔型:boolean
- 整数型:byte、short、int、long
- 浮点型:float、double
- 字符型:char
8种基本类型取值如下:
数据类型 | 代表含义 | 默认值 | 取值 | 包装类 |
boolean | 布尔型 | false | 0(false)/1(true) | Boolean |
byte | 字节型 | (byte)0 | -128到127 | Byte |
char | 字符型 | 'u0000'(空) | 'u0000'到'uFFFF' | Character |
short | 短整数型 | (short)0 | -2^15到2^15 | Short |
int | 整数型 | 0 | -2^31到2^31-1 | Integer |
long | 长整数型 | 0L | -2^63到2^63-1 | Long |
float | 单浮点型 | 0.0f | 1.4e-45到3.4e+38 | Float |
double | 双浮点型 | 0.0d | 4.9e-324到1.798e+308 | Double |
除char的包装类Character和int的包装类Integer之外,其他基本数据类型的包装类只需要首字母大写即可。包装类的作用和特点,本文下半部分详细讲解。
我们可以在代码中,查看某种类型的取值范围,代码如下:
public static void main(String[] args) { // Byte 取值:-128 ~ 127 System.out.println(String.format("Byte 取值: %d ~ %d", Byte.MIN_VALUE,Byte.MAX_VALUE)); // Integer 取值: -2147483648 ~ 2147483647 System.out.println(String.format("Integer 取值: %s ~ %s", Integer.MIN_VALUE,Integer.MAX_VALUE)); }
包装类型
我们知道8种基本数据类型都有其对应的包装类,因为Java的设计思想是万物既对象,有很多时候我们需要以对象的形式操作器某项功能,比如说获取哈希值(hashcode)或获取类(getClass)等。
那包装类特性有哪些呢?
1.功能丰富
包装类本质上是一个对象,对象就包含属性和方法,比如hashCode、getClass、max、min等。
2.可定义泛型类型参数
包装类可以定义泛型,而基本类型不行。
比如使用Integer定义泛型,代码:
List<Integer> list = new ArrayList<>();
如果使用int定义就会报错,代码:
3.序列化
因为包装类都实现了Serializable接口,所以包装类天然支持序列化和反序列化。比如Integer的类图如下:
4.类型转换
包装类提供了类型转换的方法,可以很方便的实现类型之间的转换,比如Integer类型转换代码:
String age = "18"; int ageInt = Integer.parseInt(age) + 2; //输出结果:20 System.out.println(ageInt);
5.高频区间的数据缓存
此特性为包装类很重要的用途之一,用于高频区间的数据缓存,以Integer为例来说,在数值区间为-128~127时,会直接复用已有对象,在这区间之外的数字才会在堆上产生。
我们使用 == 对 Integer 进行验证,代码如下:
public static void main(String[] args) { // Integer 高频区缓存范围 -128 ~ 127 Integer num1 = 127; Integer num2 = 127; // Integer 取值127 == 结果为 true (值127 num1==num2 =>true) System.out.println("值127 num1==num2 =>" + (num1 == num2)); Integer num3 = 128; Integer num4 = 128; // Integer 取值128 == 结果为 false (值128 num3==num4 =>false) System.out.println("值128 num3==num4 =>" + (num3 == num4)); }
从上面的代码很明显可以看出,Integer为127时复用了已有的对象,当值为128时,重新在堆上生成了新对象。
为什么会产生高频区域数据缓存呢?我们查看源码就能发现“线索”,源码版本 JDK8,源码如下:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
由此可见,高频区域的数值会直接使用已有对象,非高频区域的数值会重新new一个新的对象。
各包装类高频区域的取值范围:
- Boolean: 使用静态final定义,就会返回静态值
- Byte:缓冲区 -128~127
- Short:缓冲区 -128~127
- Character:缓冲区 0~127
- Long:缓冲区 -128~127
- Integer:缓冲区 -128~127
包装类的注意事项
- int默认值是0,而Integer的默认值是null
- 推荐所有包装类对象之间的值比较实用equals()方法,因为包装类的非高频去数据会在堆上产生,而高频区又会复用已有对象,这样会导致同样的代码,因为取值不同,而产生两种截然不同的结果。代码示例:
public static void main(String[] args) { // Integer 高频区缓存范围 -128 ~ 127 Integer num1 = 127; Integer num2 = 127; // Integer 取值127 == 结果为 true (值127 num1==num2 =>true) System.out.println("值127 num1==num2 =>" + (num1 == num2)); Integer num3 = 128; Integer num4 = 128; // Integer 取值128 == 结果为 false (值128 num3==num4 =>false) System.out.println("值128 num3==num4 =>" + (num3 == num4)); // Integer 取值128 equals 结果为 true (值128 num3.equals(num4) =>true) System.out.println("值128 num3.equals(num4) =>" + (num3.equals(num4))); }
- Float 和 Double不会有缓存,其他包装类都有缓存。
- Integer是唯一一个可以修改缓存范围的包装类,在VM options加入参数:
-XX:AutoBoxCacheMax=666 即修改缓存最大值为666。
示例代码如下:
public static void main(String[] args) { Integer num1 = 128; Integer num2 = 128; System.out.println("值为-128 =>" + (num1 == num2)); Integer num3 = 666; Integer num4 = 666; System.out.println("值666 num3==num4 =>" + (num3 == num4)); Integer num5 = 667; Integer num6 = 667; System.out.println("值为667 =>" + (num5 == num6)); }
执行结果如下:
值为-128 =>true 值为666 =>true 值为667 =>false
由此可见将Integer最大缓存修改为666之后,667不会被缓存,而-128~666之间的数都被缓存了。
小测验哦~
1.以下Integer代码输出的结果是?
Integer age = 10; Integer age2 = 10; Integer age3 = 133; Integer age4 = 133; System.out.println((age == age2) + "," + (age3 == age4));
答: true, false
2.以下Double代码输出的结果是?
Double num = 10d; Double num2 = 10d; Double num3 = 133d; Double num4 = 133d; System.out.println((num == num2) + "," + (num3 == num4));
答: false, false
3.以下程序输出结果是?
int i = 100; Integer j = new Integer(100); System.out.println(i == j); System.out.println(j.equals(i));
A: true,true
B: true,false
C: false,true
D: false,false
答: A
题目分析:有人认为这和Integer高速缓存有关系,但你发现把值改为10000结果也是 true,true ,这是因为 Integer 和 int 比较时,会自动拆箱为 int ,相当于两个 int 比较,值一定是 true, true .
4.以下程序执行的结果是?
final int iMax = Integer.MAX_VALUE; System.out.println(iMax + 1);
A: 2147483648
B: -2147483648
C: 程序报错
D: 以上都不是
答: B
题目解析: 这是因为整数在内存中使用的是补码的形式表示,最高位是符号位 0 表示正数, 1 表示负数, 当执行 +1 时, 最高位就变成了 1, 结果就成了 -2147483648.
5.以下程序执行的结果是?
Set<Short> set = new HashSet<>(); for(short i = 0; i < 5; i++){ set.add(i); set.remove(i - 1); } System.out.println(set.size());
A: 1
B: 0
C: 5
D: 以上都不是
答: C
题目解析: Short 类型 -1 之后转换成了Int 类型, remove() 的时候在集合中找不到 Int 类型的数据, 所以就没有删除任何元素, 执行的结果就是 5 .
6. short s = 2; s = s + 1; 会报错吗? short s = 2; s+=1; 会报错吗?
答: s=s+1 会报错, s+=1 不会报错, 因为 s = s + 1 会导致short 类型升级为 int 类型,所以会报错, s+=1 还是原来的short 类型, 所以不会报错.
其实对此我是有疑问的,继续探究.分别从源码和底层原理两个方面来了解一下这个问题.
从源码角度:
(1) s = s+1报错,这句先执行s+1然后把结果赋给s,由于1为int类型,所以s+1的返回值是int,编译器自动进行了隐式类型转换。所以将一个int类型赋给short就会出错
(2) s += 1这句不报错
通过反编译可以看到源码为
当jvm识别+=且原值为整型时,会先忽略原值的具体数据类型,先用int计算后,如果计算结果是int就直接转为原来的数据类型,如果不是int就强转为int然后再转回原数据类型,例如
反编译后为
输出结果为2,损失了精度.
从底层原理:
而i+1 是将heap中数据直接送到寄存器中进行运算,运算结果会直接存放在heap中。
i+=1 运行的底层Heap申请一个区域存放i,在数据区域开辟一个区域存放1,2个内存段的数据被送入到寄存器中进行运算,运算结果被放到heap中,数据区域运算后被自动释放后由GC回收 。
7. float f = 3.4; 会报错吗? 为什么?
答: 会报错,因为值3.4是double类型,float类型级别小于double类型,所以会报错.
8.为什么需要包装类?
答: 需要包装类的原因有两个.
- Java的设计思想是万物既对象,包装类体现了面向对象的设计理念;
- 包装类包含了很多属性和方法,比基本数据类型功能多,比如提供的获取哈希值(hashCode)或获取类(getClass)的方法等.
9.基本类 int 和包装类 Integer ,在 -128 ~ 127 之间都会复用已有的缓存对象,这种说法正确吗?
答: 不正确,只有包装类高频区域数据才有缓存.
10.包装类 Double 和 Integer 一样都有高频区域数据缓存,这种说法正确吗?
答: 不正确,基本数据类型的包装类只有Double和Float没有高频区域的缓存.
11.包装类的值比较要是用什么方法?
答: 包装类因为有高频区域数据缓存,所以推荐使用equals()方法进行值比较.
12.包装类有哪些功能?
答:包装类提供的功能有以下几个.
- 功能丰富 : 包装类包含了有 hashCode, getClass, max, min等方法
- 可以定义泛型类型参数 : 例如List<Integer> list = new ArrayList<>(); ;
- 序列化 : 包装类实现了Serializable接口,所以包装类天然支持序列化和反序列化.
- 类型转换 : 包装类提供了方便的类型转换方法,比如Integer的parseInt()方法.
- 高频区域数据缓存 : 高频区域可使用已有的缓存对象;
详见正文"包装类型"部分内容.
13.泛型可以为基本数据类型吗? 为什么?
答: 泛型不能使用基本数据类型.泛型在JVM(Java 虚拟机)编译的时候会类型擦除,比如代码 List<Integer> list 在JVM编译的时候会转换为List list, 因为泛型是在JDK5时提供的,而JVM的类型擦除是为了兼容以前代码的一个这种方案,类型擦除之后就变成了Object, 而Object不能存储基本数据类型,但可以使用基本数据类型对应的包装类,所以像 List<int> list 这样的代码是不被允许的,编译器阶段会检查报错,而List<Integer> list是被允许的.
14.选择包装类还是基本类的原则有哪些?
答: 我们知道正确的使用包装类,可以提供程序的执行效率,看可以使用已有的缓存,一般情况下选择基本数据类型还是选择包装类原则有以下几个.
- 所有POJO类属性必须使用包装类;
- RPC方法返回值和参数必须使用包装类;
- 所有局部变量推荐使用基本数据类型;
15.基本数据类型在JVM中一定存储在栈中吗? 为什么?
答: 基本数据类型不一定存储在栈中,因为基本类型的存储位置取决于声明的作用域,来看具体的解释.
- 当基本数据类型为局部变量的时候,比如在方法中声明的变量,则存放在方法栈中,当方法结束,系统会释放方法栈,在该方法中的变量也会随着栈的销毁而结束,这也是局部变量只能在方法中使用的原因;
- 当基本数据类型为全局变量的时候,比如类中的声明的变量,则存储在堆上,因为全局变量不会随着某个方法的执行结束而销毁.
16.以下程序执行的结果是?
Integer i1 = new Integer(10); Integer i2 = new Integer(10); Integer i3 = Integer.valueOf(10); Integer i4 = Integer.valueOf(10); System.out.println(i1 == i2); System.out.println(i2 == i3); System.out.println(i3 == i4);
答案: false;false;false
题目解析: new Integer(10) 每次都会创建一个新对象,Integer.valueOf(10)则会使用缓存池中的对象.
17. 3*0.1==0.3 返回值是多少?
答: 返回值为: false.
题目解析: 因为有些浮点数不能完全精确的表示出来,如下代码:
System.out.println(3 * 0.1);
返回的结果是: 0.30000000000000004