文编|JavaBuild
哈喽,大家好呀!我是JavaBuild,以后可以喊我鸟哥!俺滴座右铭是不在沉默中爆发,就在沉默中灭亡,一起加油学习,珍惜现在来之不易的学习时光吧,等工作之后,你就会发现,想学习真的需要挤时间,厚积薄发啦!
我们知道Java是面向对象的静态型编程语言,在Java的世界里万物皆对象。但我认为是万物皆数据,世界由各种各样数据构建起来,我们通过程序去实现数据的增删改查、转入转出、加减乘除等等,不同语言的实现方式殊途同归。
由此可见,数据对于程序语言的重要性,而在Java中用来规范数据的标准我们将其称之为“数据类型”,这便是我们今天的Topic!
在下图中我们将Java中的数据类型分为三个部分:基本数据类型,包装类型,引用数据类型
在Java中“boolean、char、byte、short、int、long、float 和 double”构建起了数据结构的基础,非常重要,也是很多公司面试的高频考点,所以,为了方便记忆,鸟哥整理了一份表格如下:
类型名称 | 位数 | 字节 | 默认值 | 取值范围 | 包装类 | 缓存区间 |
---|---|---|---|---|---|---|
byte | 8 | 1 | 0 | -128 ~ 127 | Byte | -128~127 |
short | 16 | 2 | 0 | -32768 (-2^15) ~ 32767 (2^15-1) | Short | -128~127 |
int | 32 | 4 | 0 | -2147483648 ~ 2147483647 | Integer | -128~127 |
long | 64 | 8 | 0L | -2^63 ~2^63 - 1 | Long | -128~127 |
char | 16 | 2 | '\u0000' | 0~65535 | Character | 0 ~127 |
float | 32 | 4 | 0f | 1.4e-45 ~ 3.4e+38 | Float | 无 |
double | 64 | 8 | 0d | 4.9e-324~1.798e+308 | Double | 无 |
boolean | 1 | false | true(1),false(0) | Boolean | 无 |
在计算机的物理存储中,一条电路线被称之为1位,二进制识别中为0(低电平)或1(高电平),英文中用bit表示,而8个bit组成一个字节,英文为Byte
对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
基本数据类型之间也存在着转换关系,往往发生在表达式计算的过程中,而这种转换根据不同场景分为:自动类型转换&强制类型转换
自动类型转换:Java编译器无需显示处理,一般由等级低的数据类型向等级高的数据类型转换,如int -> long。很明显,int所能存储的数据必定是long的子集,不存在数据丢失问题。
等级由低到高 byte -> short -> int -> long -> float -> double char -> int -> long -> float -> double
int a = 3; double b = 1.5; // 自动类型转换:a 被转换为 double 类型 double result = a * b; System.out.println("结果: " + result); // 输出:结果: 4.5
强制类型转换:由高等级数据转为低等级数据时往往存在强制类型转换,这时候Java编译器认为存在隐患,需要程序员介入,显示的处理强转,潜在风险是数据丢失或精度丢失。
由左到右需要强转 double -> float -> long -> int -> char -> short -> byte
double c = 10.1; // 强制类型转换:将 double 类型转换为 int 类型,精度丢失 int d = (int) c; System.out.println("整数值: " + d); // 输出:整数值: 10
转换规则如下
= 右边先自动转换成表达式中*的数据类型,再进行运算。整型经过运算会自动转化最低 int 级别,如两个 char 类型的相加,得到的是一个 int 类型的数值。 = 左边数据类型级别 大于 右边数据类型级别,右边会自动升级 = 左边数据类型级别 小于 右边数据类型级别,需要强制转换右边数据类型 char 与 short,char 与 byte 之间需要强转,因为 char 是无符号类型
这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean 。
因为Java中一切皆对象,基本数据类型无法满足这个大口号,比如泛型、序列化、类型转换、高频数据区间的缓存等,故为了弥补,便诞生了8种基本数据类型对应的包装类型。
[注意]: 很多同学都以为基本数据类型存在栈中,包装类型作为对象存储在堆中,这个观点是有失偏颇的,如果基础数据类型的成员变量在没有被static关键字修饰的情况下,是存在的堆中的,只有局部变量被存在栈的局部变量表中!而全部对象都存在堆中,也是个不完整的答案,这里涉及到HotSpot中的逃逸分析,等讲到JVM时再展开说吧。
在Java中不仅仅基本类型之间存在着转换,基本数据类型与包装类型之间同样存在着转换,在JDK1.5之前是不支持自动装箱与拆箱的,所以那时候需要通过显示的方法调用来实现转换,而JDK1.5开始,自动化程度提升了。
装箱:基本类型转变为包装器类型的过程。
拆箱:包装器类型转变为基本类型的过程。
//JDK1.5之前是不支持自动装箱和自动拆箱的,定义Integer对象,必须 Integer i = new Integer(8); //JDK1.5开始,提供了自动装箱的功能,定义Integer对象可以这样 Integer i = 8; int n = i;//自动拆箱
实现原理
装箱是通过调用包装器类的 valueOf 方法实现的 拆箱是通过调用包装器类的 xxxValue 方法实现的,xxx代表对应的基本数据类型。 如int装箱的时候自动调用Integer的valueOf(int)方法;Integer拆箱的时候自动调用Integer的intValue方法。
【需要注意的问题点】:
1、整型的包装类 valueOf 方法返回对象时,在常用的取值范围内,会返回缓存对象。
2、浮点型的包装类 valueOf 方法返回新的对象。
3、布尔型的包装类 valueOf 方法 Boolean类的静态常量 TRUE | FALSE。
Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1 == i2);//true System.out.println(i3 == i4);//false Double d1 = 100.0; Double d2 = 100.0; Double d3 = 200.0; Double d4 = 200.0; System.out.println(d1 == d2);//false System.out.println(d3 == d4);//false Boolean b1 = false; Boolean b2 = false; Boolean b3 = true; Boolean b4 = true; System.out.println(b1 == b2);//true System.out.println(b3 == b4);//true
以上代码中,我们已Integer为例展开解释一下,基本数据类型的包装类除了 Float 和 Double 之外,其他六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。而Integer的缓存区间是-128~127。
我们去看一下Integer类的缓存源码
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static { // high value may be configured by property int h = 127; } }
IntegerCache 这个静态内部类中设置了缓存区间,当我们通过valueOf()方法获取Integer对象时,会先去找该整数是否在缓存池中,有则直接返回,没有则新建并存入缓存池。
这就解释了为什么第一个 == 号结果是true,而第二个为false,因为超出了缓存区间,每次都新建一个对象,而 == 号又是比较对象地址,对于两个不同的对象,地址肯定不一样啦。
Java的数据类型除了8种基本数据类型和对应的包装类型外,还有一个分类为引用数据类型,在文章开头的树形图中已经分好,引用类型分为:数组,类和接口。
那为什么叫他引用数据类型呢?在创建引用数据类型时,会在栈上给其引用句柄,分配一块内存,然后对象的信息存储在堆上,在程序调用的时候,通过栈上的引用句柄指向堆中的对象,从而获取想要的数据。
因数组,类,接口都包含着太多内容,在后续的博客中会陆续详解,所以本文略做介绍,粗略感受一下。
数组:
int [] arrays = {1,2,3}; System.out.println(arrays); // 打印结果:[I@2d209079
打印结果中的一串内容,世界上是arrays的对象首地址,要想看到结果,需要调用java.lang.Object 类的 toString() 方法。
类:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; }
String是一个类,也是字符串的代表,所以它也是引用数据类型
接口:
List<String> list = new ArrayList<>(); System.out.println(list);
List 是一个非常典型的接口,属于Java的一种集合,存储的元素是有序的、可重复的。
【注意】
1、包装类可以实现基本类型和字符串之间的转换,字符串转基本类型:parseXXX(String s);基本类型转字符串:String.valueOf(基本类型)。 2、引用数据类型的默认值为 null,包括数组和接口。 3、char a = 'h'char :单引号,String a = "hello" :双引号。