一、基本数据类型转换
在学习基本数据类型转换之前,首先要了解为什么要进行基本数据类型转换。
1.1 为什么要进行转换
在上一节课时中,提到了 Java 数据类型的层级,如下图所示。
Java 作为一个强类型的编程语言,在不同数据类型之间进行转换时,需要进行手动 / 自动转换。
byte
的数据范围是 -128 到 127,如果将数值 128 直接赋值给 byte
类型的数据,IntelliJ IDEA 会编译报错,如下图所示。
所以需要进行数据转换,请看以下代码:
public class Main { public static void main(String[] args) { int a = 128; byte b = (byte) a; System.out.println("答案 = " + b); } }
最终输出结果为:
答案 = -128
因为 128 超过了 byte
的数据范围,则重新计数赋值,第一个值为 -128
。
通过以上一个简单的例子,同学们只需简单了解下为什么要进行数据类型转换即可。
即只要参与运算的基本数据类型不一致时,就会发生数据类型的转换。
Java 中基本数据类型的转换主要分为两类
- 自动转换类型(隐式转换)
- 强制转换类型(显示转换)
接下来逐个讲解。
1.2 自动类型转换(隐式转换)
自动类型转换,顾名思义就是自动进行数据类型转换。
即由 Java 编译器进行自动处理,Java 代码不需要经过任何处理。
小类型到大类型是自动提升的,那什么是小类型和大类型呢?如下图所示,箭头非被指结点为相对小类型,反之箭头被指结点为相对大类型,如 short 相对于 int 来说就是小类型。
1.2.1 自动类型转换1——直接赋值
小类型变量赋值给大类型时,会触发自动类型转换,比如:
public class Main { public static void main(String[] args) { long a = 1; } }
数值 1
是 int
类型,而承载的变量为 long
类型,则数值 1
会被自动转换为 1L
。
1.2.2 自动类型转换2——运算时转换
小类型变量和大类型变量进行运算时,会将小类型提升为大类型,再进行数学运算,如下所示。
public class Main { public static void main(String[] args) { int a = 1; long b = 5L + a; } }
比如变量 a
为 int 类型,在计算 5L
+ a
时,会首先将 a
转换为 1L
,再计算 5L + 1L = 6L
,最终得出 6L
结果。
简单来说 long
+ int
会自动转换为 long
+ long
再进行计算。
同理 int
+ double
也会自动转换为 double
+ double
再进行计算,如下代码所示。
public class Main { public static void main(String[] args) { int a = 1; double b = 3.14 + a; } }
1.3 强制类型转换(显示转换)
大类型转为小类型时,需要强制类型转换,可能会导致数据丢失。
比如 int
类型的取值范围是 -2^31到2^31-1
,byte
类型的取值范围是 -2^7到2^7-1
。
当 int
类型转换为 byte
类型时,会出现数据溢出的情况,如下代码所示。
public class Main { public static void main(String[] args) { int a = 128; byte b = (byte) a; System.out.println("b = " + b); } }
最终输出的结果为 b = -128
,即发生了数据溢出情况,也可以理解为数据丢失。
同理,在 double
类型强制转换为 int
时,也会出现数据精度丢失(数据丢失),如下代码所示。
public class Main { public static void main(String[] args) { double a = 3.1415926; int b = (int) a; System.out.println("b = " + b); } }
输出结果为 b = 3
,即发生了数据精度丢失情况,也可以理解为数据丢失。
1.3 类型转换小结
关于 Java 的数据类型转换,同学们需要理解以下三点:
- 大转小:强制类型转换,如下:
int a = 6; byte b = (byte) a;
提示:对于 byte 和 int 类型之间的转换中需要注意,当把一个 int 数值赋值给 byte 变量时,不管是否超过范围,都需要强制转换。
- 小转大:自动类型转换,如下:
int a = 6; long c = a;
- 默认定义类型
整数的默认类型为 int
。
整数带有后缀 L
时类型为 long
。
浮点数的默认类型为 double
。
浮点数带有后缀 f
时类型为 float
。
提示:关于 String 的类型转换,将在后续的课时中讲解,因为 String 不属于 Java 的基本数据类型。
二、自动装箱和自动拆箱
在学习自动装箱和自动拆箱之前,首先了解什么是包装类。
2.1 包装类是什么?
在 jdk1.4 中,新增了 8 个基本数据类型的对应包装类,如下表所示。
基本类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本数据类型的对应包装类的方式基本相同,但在 JVM 上的分配有所不同,具体将在后续的课时中讲解,同学们只需知道在时间或空间上,基本数据类型优于对应包装类即可。
2.2 为什么要用包装类?
从 jdk1.5 开始,定义集合需要使用包装类。定义普通数组,使用基本类型和包装类都可,如下代码所示。
public class Main { public static void main(String[] args) { /** * 普通数组的定义方法 */ int[] a = new int[10]; /** * 集合的定义方法 */ List<Integer> arr = new ArrayList<>(); /** * 对基本类型的集合定义需要转为包装类 */ // List<int> arr = new ArrayList<>(); // 编译错误 } }
关于集合的用法,会在下面的课时中讲解,同学们只需要了解包装类的使用场景即可。
2.3 自动装箱
自动装箱是什么?基本类型自动转换为包装类型就是自动装箱。
请看以下代码:
public class Main { public static void main(String[] args) { /** * 直接赋值 */ Integer a = 666; /** * 包装类的静态工厂方法 */ Integer b = Integer.valueOf(666); /** * 构造器赋值,在现有 JDK 中已被废弃 */ Integer c = new Integer(666); } }
在对 Integer
类型的变量赋值时,666
这是一个基本数据类型,而变量 a
、b
、c
为包装类。
自动装箱,就是将一个基本类型直接赋值给包装类型的过程。
如果没有自动装箱,以上代码无法通过 JVM 编译。
2.4 自动拆箱
自动拆箱是什么?包装类型自动转换为基本类型就是自动拆箱。
请看以下代码:
public class Main { public static void main(String[] args) { Integer a = 666; int b = 666; System.out.println("判断 1 = " + (a == b)); System.out.println("判断 2 = " + Objects.equals(a,b)); } }
代码中分别定义基本类型和包装类,赋同样的值,最后判断是否相等,输出为:
判断 1 = true 判断 2 = true
其中变量 a
在比较之前,自动拆箱为基本类型,然后在于 b
进行比较,最后得出 true
的结果。
如果不进行自动拆箱,两个不同类型的变量无法进行比较。
就好比拿 苹果
和 电视
比较性能,这是不可行的。
Java 只能将包装类 Integer
拆箱为 int
才能和 int
值进行比较。
这就是 自动拆箱。
2.5 性能比较
同学们学习自动装箱、自动拆箱之后,还需要了解它们的性能差距。
因为,这将影响你在实战开发中,决定常用哪个,最终影响你的程序性能。
2.5.1 测试基本类型
首先对基本数据类型进行测试,代码如下所示。
public class Main { private static final long MAX_NUMBER = 1000000000; public static void main(String[] args) { long start = System.currentTimeMillis(); long sum = 0; for(long i = 0; i < MAX_NUMBER; i ++) { sum += i; } long end = System.currentTimeMillis(); System.out.printf("耗时 = " + (end - start) + ",计算结果 = " + sum); } }
最后输出结果:
耗时 = 304,计算结果 = 499999999500000000
2.5.2 测试包装类
接着对包装类进行测试,代码如下所示。
public class Main { private static final Long MAX_NUMBER = 1000000000L; public static void main(String[] args) { long start = System.currentTimeMillis(); Long sum = 0L; for(Long i = 0L; i < MAX_NUMBER; i ++) { sum += i; } Long end = System.currentTimeMillis(); System.out.printf("耗时 = " + (end - start) + ",计算结果 = " + sum); } }
最后输出结果:
耗时 = 6374,计算结果 = 499999999500000000
2.5.3 测试小结
由此可见,使用包装类虽然简便,但频繁自动装拆箱会带来性能低下的问题。
所以在实战开发中,建议使用基本数据类型。
如果一定要使用包装类的场景下,再去使用包装类。
在 2.1 小节 也提到过,基本类型无论是在时间还是空间上都是优于引用类型(如包装类)的。
三、课时小结
在本节课时中,讲解了低转高、高转低的 Java 基本类型转换,接着学习了包装类的概念,再引申出自动装箱、自动拆箱的概念,最后分别进行了性能测试。在下节课时中,将学习 Java 变量、常量及其作用域的知识。