文编|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种基本数据类型对应的包装类型。

包装类型与基本数据类型差异

  • 使用场景: 在Java中除了一些常量和局部变量的定义会用到基础数据类型外,绝大部分情况下均采用包装类型,如方法参数,对象属性等,且基本数据类型不能用于泛型,包装类型可以!
  • 默认值: 包装类型比基本类型多了一个非功能值:null,在不做任何赋值的情况下,包装类型的默认就是null,而基本数据类型都有相应的默认值,见上面表格。
  • 所占内存 因为包装类型是对象,会有一些对象头等信息,所以占用空间上要大于基本数据类型。
  • 比较方式 基本类型采用 == 号比较,比较的是值,而对于包装类型来说,==比较的其实是对象的内存地址,对象值的比较需要通过equals()方法完成。

[注意]: 很多同学都以为基本数据类型存在栈中,包装类型作为对象存储在堆中,这个观点是有失偏颇的,如果基础数据类型的成员变量在没有被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" :双引号。
posted on 2024-01-13 15:40  JavaBuild  阅读(275)  评论(0编辑  收藏  举报