知识点脑图
Java为什么要设计基础数据类型
Java语言是面向对象的,new一个对象是存在堆里的
像int这类使用非常频繁的类型如果每次使用都还要去new一个对象到堆里
这样会十分的笨重
所以有基础类型,它们使用是直接在栈空间存储,更加高效
有哪些基础数据类型
基础数据类型 | 所占位数 |
---|---|
boolen | 1 |
byte | 8 |
short | 16 |
int | 32 |
float | 32 |
double | 64 |
long | 64 |
char | 16 |
void(注意void也是java的基础数据类型) | - |
有了基础数据类型为什么还要有包装类
看到了基础数据类型可以提高效率,那为什么又有包装类的存在呢?
有几点:
- java语言是面向对象的,基础数据类型虽有益处但不符合面向对象的设计思想
- 基本数据类型用法单一,包装类灵活,且功能丰富
- java中集合和泛型等不支持基本数据类型的直接使用
java的包装类
java中所有的基础数据类型都对应一个包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
void | Void |
值得注意的是,所有的包装类都是由final关键字进行修饰的,意味着这些包装类将不能够被继承
自动拆装箱
java语言允许包装类和基础数据类型之间进行转换,这个过程被称为自动拆箱与装箱
Integer a = 1;
int a = new Integer(1);
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱
反之将Integer对象转换成int类型值,这个过程叫做拆箱。
因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。
实质
其实所谓的自动拆装箱是java的语法糖
,这是一个刻意的设计,为了让程序开发与阅读是更加便利
public class Main {
public static void main(String[] args) {
Integer a = 1;
}
}
这样一段代码通过工具查看编译后的字节码实质是这样的
本质就是调用了Integer的valueOf方法进行实现的
再看
public class Main {
public static void main(String[] args) {
int a = new Integer(1);
}
}
它的实质则是:
本质就是调用了intValue
其他的类型同理
发生拆装箱的时机
最常见的是赋值操作时,如下面这段代码,就分别做了装箱与拆箱
public class Main {
public static void main(String[] args) {
Integer a = 1;
int b = a;
}
}
还有就是方法调用时,如下面这个代码
public class Main {
public static void main(String[] args) {
int a = 1;
test(a);
}
private static void test(Integer a) {
System.out.println("this is a test");
}
}
看下它的字节码
很明显这里进行了一次自动装箱
需要注意的地方
浪费资源
自动拆装箱方便了我们的开发,但如果不注意会很多对象创建上的问题
在诸如此类代码中:
public class Main {
public static void main(String[] args) {
Integer a = 1;
for (int i = 0; i < 1000; i++) {
a+=i;
}
}
}
这里就存在比较严重的无用的对象创建的问题,因为每一次的a+=i都要进行一次拆装箱,这样就会创建一个没有用的Integer对象在堆中
重载方法
通过上面的介绍,可以知道,java在方法参数传递上会自动的进行自动拆装箱的操作
但是这里有个特别的例子,就是当出现方法重载时
举个例子:
private void test(int i) {
System.out.println("这是int");
}
private void test(Integer i) {
System.out.println("这是integer");
}
这种情况调用的时候就不会出现自动拆箱或者装箱的情况
比较过程的拆装
有了自动拆装箱后,我们在使用比较时就需要小心
看下面的例子
public class Main {
public static void main(String[] args) {
Integer a = 1;
int b = 1;
System.out.println(a==b);
}
}
此过程输出结果为true
查看字节码分析一下流程
可以看到的是Integer a = 1
先进行了一次装箱,然后在==
进行运算时进行了一次拆箱,所以本质还是两个int的比较
常量池
这个算是的一个常见的面试题吧
先看代码
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 1;
System.out.println(a==b); // 输出 true
Integer c = 128;
Integer d = 128;
System.out.println(c==d); // 输出 false
}
}
出现这种情况的原因就是Integer这个类型在自动装箱时做的一个缓存的处理
通过上面的介绍可以知道Integer a = 1
本质调用的是Integer.valueOf
看下它的源码,很明显可以看到Integer的valueOf对-128至127的数据做了缓存处理,这一点在官方的注释中也可以看到
当超出这个范围,将会调用new Integer()
产生新的示例
这是Java语言为了更好地性能做出的处理
这里要提的一点就是,大家可能都记得Integer有缓存池(面试题养成的应急反应吗?哈哈哈)
但是常会忽略其他的包装类型也有常量池这个点!
各种各样的常量池
除了Integer,Java的其他数据类型也有常量池(但不全有)
有常量池的类型有:Byte、Short、Integer、Long、Character、Boolean
这里有意思的是Byte、Short、Integer、Long缓存的值都是[-128,127]
而Character是在[0,127]
,Boolean则只有true和false
注意:Float和Double这两个浮点类型包装类就是没有常量池
随意查看两个有常量池包装类的源码看看:
Long.valueOf
Character.valueOf
Boolean的实现就是定义两个常量,比较简单,反正就true和false
需要留意
以上各种包装类在进行==比较的时候都要记得结合自动拆箱和装箱以及常量池的概念进行判断
否则可能会在你的程序里面留下意想不到的问题
参考
-
https://blog.csdn.net/xialei199023/article/details/63251295
-
https://droidyue.com/blog/2015/04/07/autoboxing-and-autounboxing-in-java/