初探JAVA基本数据类型

51 阅读15分钟

JAVA基本数据类型详解

第一章:概述与分类:

1.1 数据类型分类

java中有8种基本数据类型

其中包括boolean类型,字符型char,和六种数值类型

Byte->short->int->long->float->double

1.2 类型对比表格

基本数据类型分类字节大小(byte)取值范围默认值说明
boolean布尔型{true, false}false
char字符型2['\u0000', \uffff]'\u0000'用单引号包裹,存储unicode字符
byte整型1[-2^7, 2^7 - 1]0
short整型2[-2^15, 2^15 - 1]0
int整型4[-2^31, 2^31 - 1]0
long整型8[-2^63, 2^63 - 1]0L使用时需要在值后加 l 或 L
float浮点型40.0F使用时需要在值后加 f 或 F
double浮点型80.0可以在值后加 d 或 D(可以省略)

关于boolean类型所占用空间大小,引用The Java Tutorials Java关于原始数据类型的声明

boolean: The boolean data type has only two possible values: true and false. Use this data type for simple flags that track true/false conditions. This data type represents one bit of information, but its "size" isn't something that's precisely defined.

boolean数据类型只有两个可能的值:truefalse。此数据类型用于跟踪真/假条件的简单标志。这种数据类型表示一个比特的信息,但它的“大小”并没有精确定义。

float类型取值范围的计算方式与其他数值类型不同,会在后面详说

第二章:整形数据深度分析:

2.1 存储原理:补码系统

计算机中的数据都是以二进制的方式进行存储的,而二进制还有三种表现形式

  1. 原码

    • 举例:将一个十进制数,直接转换为二进制,得到的就是原码,其中最高位为符号位
    • 十进制:-2
    • 二进制:1 0000000 00000000 00000000 00000010
  2. 反码

    • 原码的符号为不变,其他位取反
    • 反码:1 1111111 11111111 11111111 11111101
  3. 补码

    • 反码加1
    • 补码:1 1111111 11111111 11111111 11111110

这里的使用补码系统指的是整型数据和定点数

正整数的原码、反码、补码都相同

采用补码存储的原因:

  1. 解决“0”的重叠问题:在反码系统中,+00000 0000-01111 1111,这会导致两个零的存在,增加电路判断难度。而补码系统中,0 是唯一的。原码 +0 0000 0000 -0 1 000 0000;
  2. 简化加减运算:使用补码,减法可以直接当作加法来做(例如 A - B 等于 A + (-B的补码)),CPU 只需要加法器即可,不需要减法器,大大节省了硬件成本。

如果出现了在反码+1的情况超出了原有位长度会发生什么

会将溢出位直接丢弃

-0 的原码1000 0000

-0 的反码1111 1111

-0 的补码(反码 + 1) :1 0000 0000 (结果变成了 9 位!)

将1丢弃0000 0000 还是0

2.2 运算规则与类型提升

  1. 隐式类型转换(自动提升)

    • 上面我们提到的小于int的类型会自动转换为int的方式,就是自动提升
    • 当两个或多个数据进行运算时,会自动提升为大的类型
    • byte、short、char =>int
    • int、long => long
    • long、float => float (按精度)
    • long、double =》 double
  2. 显示类型转换 (强制转换)

    • 将大的强制转为小的

    • 如果我们想让两个byte相加的结果仍然为byte应该怎么做

    •   byte b1 = 10;
        byte b2 = 20;
        byte b3 = (byte)(b1 + b2);
        log.info("b3 = " + b3);
        //1月 03, 2026 4:18:59 下午 DataType main
        //信息: b3 = 30
        ​
        0 bipush 10
        2 istore_1
        3 bipush 20
        5 istore_2
        6 iload_1
        7 iload_2
        8 iadd
        9 i2b
        10 istore_3
      
    • 这单代码从字节码上看,同样是先提升为int计算,计算后通过i2b命令对int类型进行截断,强制其变为byte,从结果上看,好像也没什么影响么,但如果是下面这样呢?

      byte b1 = 110;
      byte b2 = 20;
      byte b3 = (byte)(b1 + b2);
      log.info("b3 = " + b3);
      ​
      // 1月 03, 2026 4:21:07 下午 DataType main
      // 信息: b3 = -126
      

      我们发现110+20得出的结果却是-126,为什么会这样呢?byte类型只能存储[-128,127]这个区间内的值,而130是明显大于这个值的,所以我们在强制转换时会产生损失,可能是损失精度,也可能是值的准确性,那为什么最后会出现-126这个值呢,这个值是随机的吗?

      当然不!!!

      在jvm计算时,会将b1, b2提升为int, 所以他们在内存中的二进为:

      b1: 00000000 00000000 00000000 01101110

      b2: 00000000 00000000 00000000 00010100

      b3: 00000000 00000000 00000000 10000010

      当我们执行i2b时,会进行截断只留最后8位

      b3: 10000010 // 这是一个负数,因为符号为是1,所以应该转换为补码

      反码: 1 1111101

      补码: 1 1111110

      转化为10进制byte为-126

2.3 转换策略与风险控制

为了防止将超出数据类型范围的值强制转换为此类型导致精度丢失,可以在计算时进行验证

// 例:
int safeAdd(byte a, byte b) {
    int c = a + b;
    if (c > Byte.MAX_VALUE || c < Byte.MIN_VALUE) {
        throw new ArithmeticException("byte类型溢出");
    }
    return (byte) c;
}

第三章:浮点数系统详解

3.1 IEEE 754标准 “引用文章”

计算公式:V = (-1)^S * M * R^E

直到1985年,IEEE 组织推出了浮点数标准,就是我们经常听到的 IEEE754 浮点数标准,这个标准统一了浮点数的表示形式,并提供了 2 种浮点格式:

  • 单精度浮点数 float:32 位,符号位 S 占 1 bit,指数 E 占 8 bit,尾数 M 占 23 bit
  • 双精度浮点数 float:64 位,符号位 S 占 1 bit,指数 E 占 11 bit,尾数 M 占 52 bit

为了使其表示的数字范围、精度最大化,浮点数标准还对指数和尾数进行了规定:

  1. 尾数 M 的第一位总是 1(因为 1 <= M < 2),因此这个 1 可以省略不写,它是个隐藏位,这样单精度 23 位尾数可以表示了 24 位有效数字,双精度 52 位尾数可以表示 53 位有效数字
  2. 指数 E 是个无符号整数,表示 float 时,一共占 8 bit,所以它的取值范围为 0 ~ 255。但因为指数可以是负的,所以规定在存入 E 时在它原本的值加上一个中间数 127,这样 E 的取值范围为 -127 ~ 128。表示 double 时,一共占 11 bit,存入 E 时加上中间数 1023,这样取值范围为 -1023 ~ 1024。

除了规定尾数和指数位,还做了以下规定:

  • 指数 E 非全 0 且非全 1:规格化数字,按上面的规则正常计算
  • 指数 E 全 0,尾数非 0:非规格化数,尾数隐藏位不再是 1,而是 0(M = 0.xxxxx),这样可以表示 0 和很小的数
  • 指数 E 全 1,尾数全 0:正无穷大/负无穷大(正负取决于 S 符号位)
  • 指数 E 全 1,尾数非 0:NaN(Not a Number)

3

3.2 内存结构与表示范围

截屏2026-01-04 02.40.46.png

根据oracle官方文档中给出的浮点数的公式: s · m · 2^(e - N + 1)参照资料Floating-Point Types, Formats, and Values

Parameterfloatfloat-extended-exponentdoubledouble-extended-exponent
N24245353
K8≥ 1111≥ 15
Emax+127≥ +1023+1023≥ +16383
Emin-126≤ -1022-1022≤ -16382

其中:

  • s: +1 或 -1(符号)
  • m: 整数,满足 1 ≤ m < 2^N(规范化数)
  • e: 整数,Emin ≤ e ≤ Emax
  • N: 有效数字位数(float:24, double:53)

Oracle给出的浮点数公式和IEEE754公式稍有差别

核心差异:尾数是“小数”还是“整数”

Oracle中定义的尾数m是一个正整数

IEEE中定义的尾数是一个二进制小数

举例计算:

一:将浮点数转换为二进制:(以32位举例)

浮点数 10.25
首先拆分为整数部分和小数部分
整数:10
小数:0.25
将整数部分和小数部分分别以二进制表示
整数10 => 1010
小数转换为二进制的方式为 loop(小数*2,当小数*2>1时,当前位数为1,否则为0,若还有小数位,继续计算直到没有小数)
0.25 * 2 = 0.5  => 0
0.5 * 2 = 1.0 => 1
小数:0.25 =》 0.01
这个小数部分还可以这样看: 0.25 = 1/4 = 2^-2 因为刚好整除可以看作 1.0 小数点向左两位 0.01
10.25 => 1010.01 => 1.01001 * 2^3
我们现在计算出 ·规格化·数的 底数(尾数)为 1.01001 指数为3(指数计算应为 X-127 = 3,所以指数位8位实际上的值应该是130)
因为规格化的数总是以1开头,所以转为2进制时,可以不写(隐藏位)
所以最终转换结果 符号位0 正数 底数 01001 指数10000010 不足32位在底数后补0
0 10000010 01001000000000000000000

二:以IEEE754公式将二进制数表示为浮点数

V = (-1)^S * M * R^E
s = 0;
M = 1.01001;
R 是基数,二进制表示则为2
E = 3;
将M转为10进制小数。   小数点坐边数*2^(n-1)  右边*2^(-n)
M十进制 = 1 * 2^0. 0*2^(-1) + 1*2^(-2) + 0 * 2^(-3) + 0 * 2^(-4) + 1 * 2^(-5) = 1.28125
V = 1 * 1.28125 * 8 = 10.25

三:以Oracle公式将二进制数表示为浮点数

V = s * m * 2^(e - N + 1)
s = 1;(只能是+1/-1)
在 Oracle 的视角下,m 是将规格化后的二进制数填满 N 位精度后形成的整数。
规格化数是 1.01001(后面补 0 直到满 24 位)
m 二进制 = 101001000000000000000000  [m >= 2^(n-1) {N| N = 24}]
e = 3
N = 24
m 十进制 = 1.01001[二进制] * 2^23 = 1.28125[十进制] * 2^23 = 10747904
V = 1 * 10747904 * 2^(-20) = 10.25

3.3 运算特性与特殊值

浮点数除有限非0.0的值外,还存在一下几种特殊值

  • NaN 在0.0/0.0时会产生此值,表示为Not a Number
  • Double.NEGATIVE_INFINITY 负无穷,在-1.0/0.0时产生
  • Double.POSITIVE_INFINITY 正无穷,在1.0/0.0时产生
  • 正负零

运算特性

  1. 浮点数之间的比较运算

    // 从小到大依次是 负无穷-》0.0 -》正无穷
            double d1 = -1.0 / 0.0;
            double d2 = -1.0;
            double d3 = 0.0;
            double d4 = 1.0;
            double d5 = 1.0/0.0;
            log.info("d1 is : " + d1);
            log.info("d1 < d2: " + (d1 < d2));
            log.info("d2 < d3: " + (d2 < d3));
            log.info("d3 < d4: " + (d3 < d4));
            log.info("d5 is : " + d5);
            log.info("d4 < d5: " + (d4 < d5));
    ​
    //1月 04, 2026 12:16:08 上午 DataType main
    //信息: d1 is : -Infinity
    //1月 04, 2026 12:16:08 上午 DataType main
    //信息: d1 < d2: true
    //1月 04, 2026 12:16:08 上午 DataType main
    //信息: d2 < d3: true
    //1月 04, 2026 12:16:08 上午 DataType main
    //信息: d3 < d4: true
    //1月 04, 2026 12:16:08 上午 DataType main
    //信息: d5 is : Infinity
    //1月 04, 2026 12:16:08 上午 DataType main
    //信息: d4 < d5: true
    
  2. 有NaN参与到运算

    // 任何数据类型与NaN参与的比较运算结果均为false,数值运算结果为NaN
    double d6 = 0.0/0.0;
    double d5 = 1.0/0.0;
    log.info("d6 is : " + d6);
    log.info("d6 < d2: " + (d6 < d2));
    log.info("d6 + d2: " + (d2 + d6));
    ​
    104, 2026 12:19:18 上午 DataType main
    信息: d6 is : NaN
    104, 2026 12:19:18 上午 DataType main
    信息: d6 < d2: false
    104, 2026 12:19:18 上午 DataType main
    信息: d6 + d2: NaN
    
  3. 若数值运算中,两个数中有一个为浮点数,则将另一个自动提升为浮点数,float和double进行运算时,float也会提升double

  4. 浮点数溢出会产生有符号无穷大

第四章:类型转换与运算综合

4.1 自动类型提升规则

boolean类型由于类型大小不确定,所以无法参与运算,后续内容不在讨论

所有参加运算的数据必须能转换为基本数据类型,否则会抛出异常

在基础整型二元运算中,当一个类型的值范围小于int时,且不包含long类型时,应该自动提升为int类型进行运算,如果有long类型参与到运算,应该提升为long类型进行运算

在二元运算中,如果有一个类型为浮点型时,参与运算的其他类型应改提升为浮点型,当其中一个类型为double时,应该将其他类型自动提升为double

4.2 强制类型转换策略

当我们想将一个数值范围大的类型存入到范围小的类型时,可以通过强制类型转换,让其对当前数据进行截断

如 int a = 128;

byte b = (byte) a;

此时jvm会通过i2b进行截断,只保留低位的8位

强制转换可能会损失精度

第五章:精度问题全面解决方案

5.1 问题根源分析

d1 = 1.1;
d2 = 0.1;
log.info("d1 + d2 = " + (d1 + d2));
        
1月 04, 2026 12:32:54 上午 DataType main
信息: d1 + d2 = 1.2000000000000002

我们发现d1+d2并没有产生我们想要的结果1.2

这是因为用二进制无法每次都精确表示一个浮点数,可能某个浮点数用二进制表示是一个无限循环小数

但是浮点数是有存储位数限制的,多余的位数就需要被舍弃,丢失精度,在不断的运算中,丢失的精度会被不断放大。

5.2 浮点数比较正确姿势

避免直接使用==来比较浮点数,可以通过两浮点数相减的绝对值,小于某个值域来判断是否相等

double de1 = 0.100001;
double de2 = 0.13;
if (Math.abs(de1 - de2) < 0.001) {
     System.out.println("相等");
} else {
     System.out.println("不相等");
}

5.3 高精度计算工具使用

使用BigDecimal类

BigDecimal bigDecimal = new BigDecimal("10.12");
BigDecimal bigDecimal2 = new BigDecimal("1.3");
BigDecimal bigDecimal3 = bigDecimal.add(bigDecimal2).setScale(2, RoundingMode.HALF_UP);
log.info("bigDecimal : " + bigDecimal3);

// 可以通过setScale方法指定保留有效位数,和取舍模式来达到我们想要的精度
//1月 04, 2026 1:14:59 上午 DataType main
//信息: bigDecimal : 11.42

RoudingMode的几种取舍模式

模式 (RoundingMode)原始值处理结果逻辑说明
HALF_UP (四舍五入)1.151.2最常用的模式,若舍弃部分 0.5\ge 0.5 则进位。
HALF_DOWN (五舍六入)1.151.1若舍弃部分 >0.5> 0.5 才进位。如果是 1.151 则会变成 1.2。
UP (远离零方向进位)1.111.2只要舍弃部分非零,就直接进位(无视大小)。
DOWN (向零方向舍弃)1.191.1直接截断,不进位。
CEILING (向正无穷大)-1.19-1.1在数轴上向右取值(对负数来说结果变大)。
FLOOR (向负无穷大)1.191.1在数轴上向左取值(对负数来说结果变小)。
HALF_EVEN (银行家舍入)1.15 / 1.251.2 / 1.2四舍六入五考虑:若舍弃位是 5,看前一位,偶数舍,奇数进。能平衡累计误差。

注意:

这里的BigDecimal应该使用String参数的构造方法,否则还会有精度问题

因为使用Double作为参数时,已经发生了精度损失,所以使用BigDecimal也无济于事

也可以使用BigDecimal.valueOf(Double d),因为其内部代码也是通过String构造

public static BigDecimal valueOf(double val) {
  // Reminder: a zero double returns '0.0', so we cannot fastpath
  // to use the constant ZERO.  This might be important enough to
  // justify a factory approach, a cache, or a few private
  // constants, later.
  return new BigDecimal(Double.toString(val));
}

第六章:实战应用指南

6.1 类型选择原则

byte数据类型对于在大型数组中节省内存非常有用,在大型数组中,内存节省实际上很重要。它们也可以用来代替int,short同理

当你需要使用比int提供的值范围更大的类型请选择选择Long

byteshort的建议一样,如果需要在大型浮点数数组中保存内存,请使用float(而不是double)此数据类型不应用于精确值,例如货币

如果需要使用高精度浮点数,请选择BigDecimal

总结:

内存敏感大型数组中考虑使用 bytefloat

普通整数:首选 int,范围不足选 long

高精需求:涉及钱财或精确计算,强制使用 BigDecimal

6.2 常见错误与调试技巧

  1. 当我们在使用非数值类型的默认类型时(整型的默认类型为int,浮点型double),在做运算时要注意会发生类型提升,如果想维持原类型需要做强制转换,但对类型有精度要求时,需要在转换前对运算结果进行验证,如果超出当原类型范围会发生精度丢失
  2. 当我们使用BigDecimal时,需要注意不要直接使用double类型来构造对象,因为在使用double的时候已经出现精度错误,这样构造出的对象已经是错误对象