Java基础5:基本数据类型

一、前言

  本文内容摘自《深入理解Java核心技术:写给Java工程师的干货笔记(基础篇)》一书,2022年出版,作者 张洪亮(@Hollis),阿里巴巴技术专家,著有《Java工程师成神之路》系列文章,《Java工程师成神之路》电子书已开源,可在阿里云开发者社区免费下载。书籍内容比电子书内容要丰富,内容有修改,有需要的读者可以购买正版书籍。
 
  【如何成神:先搬砖,再砌砖,后造砖!】
 
  本文由 @大白有点菜 原创,请勿盗用,转载请说明出处!如果觉得文章还不错,请点点赞,加关注,谢谢!
 
深入理解Java核心技术:写给Java工程师的干货笔记(基础篇) 

二、第6章 基本数据类型

1、基本类型

  基本类型(或者叫作内置类型)是Java中不同于类(Class)的特殊类型,它们是编程中使用最频繁的类型。
 
  Java是一种强类型语言,第一次声明变量时必须说明数据类型,第一次变量赋值称为变量的初始化。
 
【Java的基本类型共有8种,可以分为三类】:

  • 字符类型
  • 布尔类型
  • 数值类型

【数值类型又可以分为整数类型byte、short、int、long和浮点数类型float、double】:

  • 字符类型char->包装类【Character】
  • 布尔类型boolean->包装类【Boolean】
  • 数值类型byte->包装类【Byte】、short->包装类【Short】、int->包装类【Integer】、long->包装类【Long】、float->包装类【Float】、double->包装类【Double】

 
  特别需要注意的是,虽然String在日常开发中也经常使用,但是String不是基本类型,而是引用类型
 
  Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。
 
  实际上,Java还存在另外一种基本类型void,不过我们无法直接对它们进行操作。
 
(1)使用基本数据类型的好处
 
  在Java中,新创建的对象是存储在堆里的(不考虑JIT优化的情况下),通过栈中的引用来使用这些对象。所以,对象本身来说是比较消耗资源的。
 
  Java提供了基本数据类型,这种数据的变量不需要使用new创建,它们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
 
(2)整型的取值范围
 
  注:1字节=8位(bit)。Java中的整型属于有符号数。
 
【8 bit可以表示的数字】:

  • 最小值:10000000(-128)
  • 最大值:011111111(127)

 
【整型的取值】:

  • byte1 字节,范围为 -128(-27)到 127(27-1) ,在变量初始化时,byte 类型的默认值为 0
  • short2 字节,范围为 -32768(215) 到 32767(215-1),在变量初始化时,short 类型的默认值为 0
  • int4 字节,范围为 -2147483648(-231)到 2147483647(231-1),在变量初始化时,int 类型的默认值为 0
  • long8 字节,范围为 -9223372036854775808(-263)到 9223372036854775807 (263-1),在变量初始化时,long 类型的默认值为 0L 或 0l,也可直接写为 0

【示例代码】:

/**
 * 基本数据类型
 * @author 大白有点菜
 */
public class BasicDataTypeApp {
    public static void main(String[] args) {
        System.out.println("**********分割线1**********");
        System.out.println("byte范围:" + Byte.MIN_VALUE + " 到 " + Byte.MAX_VALUE);
        System.out.println("**********分割线2**********");
        System.out.println("short范围:" + Short.MIN_VALUE + " 到 " + Short.MAX_VALUE);
        System.out.println("**********分割线3**********");
        System.out.println("int范围:" + Integer.MIN_VALUE + " 到 " + Integer.MAX_VALUE);
        System.out.println("**********分割线4**********");
        System.out.println("long范围:" + Long.MIN_VALUE + " 到 " + Long.MAX_VALUE);
    }
}

 
【运行结果】:

**********分割线1**********
byte范围:-128127
**********分割线2**********
short范围:-3276832767
**********分割线3**********
int范围:-21474836482147483647
**********分割线4**********
long范围:-92233720368547758089223372036854775807

 
(3)超出范围怎么办
 
  在整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致结果超出表示范围,即溢出。
 
【示例代码】:

/**
 * 基本数据类型
 * @author 大白有点菜
 */
public class BasicDataTypeApp {
    public static void main(String[] args) {
        int i = Integer.MAX_VALUE;
        int j = Integer.MAX_VALUE;
        int k = i + j;
        System.out.println("i(" + i + ") + j(" + j + ") = k(" + k + ")");
    }
}

 
【运行结果】:

i(2147483647) + j(2147483647) = k(-2)

 
  溢出时并不会抛出异常,也没有任何提示
 

2、浮点数

 
  计算机中的数字都是通过二进制数存储和运算的,对于十进制整数转换为二进制整数,采用“除2取余,逆序排列”法。

  • 用2整除十进制整数,可以得到一个商和余数。
  • 再用2去除商,又会得到一个商和余数,如此循环,直到商小于1为止。
  • 把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。

  例如,把127转换成二进制数,如图所示:
 
127转换成二进制数

  那么,十进制小数转换成二进制小数,又该如何计算呢?
 
  十进制小数转换成二进制小数采用“乘2取整,顺序排列”法。

  • 用2乘十进制小数,可以得到积。
  • 将积的整数部分取出,再用2乘余下的小数部分,又得到一个积。
  • 再将积的整数部分取出,如此循环,直到积中的小数部分为零,此时 0 或 1 为二进制数的最后一位,或者达到所要求的的精度为止。

  例如,把0.625转换成二进制数,如图所示:
 
0.625转换成二进制数

  但是0.625是一个特例,用同样的算法,计算0.1对应的二进制数是多少,如图所示。
 
0.1转换成二进制数
  0.1的二进制数中出现了无限循环的情况,也就是(0.1)10=(0.000110011001100…)2。
 
  在这种情况下,计算机就无法用二进制数精确地表示0.1了。
 
  为了解决部分小数无法使用二进制数精确表示的问题,于是就有了 IEEE 754(IEEE二进制浮点数算术标准) 规范。

  浮点数和小数并不是完全一样的,计算机中小数的表示法有定点和浮点两种。因为在位数相同的情况下,定点数的表示范围要比浮点数小。所以在计算机科学中,使用浮点数表示实数的近似值。

  IEEE 754 规定了4种表示浮点数值的方式:单精度(32位)、双精度(64位)、延伸单精度(43bit以上,很少使用)与延伸双精度(79bit以上,通常以80位实现)。单精度和双精度两种浮点数最常用。

  一个单精度浮点数(float)在计算机存储器中占4字节(32bit),利用“浮点”(浮动小数点)的方法,可以表示一个范围很大的值。
 
  相比单精度浮点数,一个双精度浮点数(double)占用8字节(64bit)。

 
  需要注意的是,IEEE并没有解决小数无法精确表示的问题,只是提出了一种使用近似值表示小数的方式,并且引入了精度的概念。
 
  一个浮点数a由两个数m和e来表示:a=m x be
 
  在任意的系统中,我们选择一个基数b(记数系统的基)和精度p(即使用多少位来存储)。m(即尾数)是形如 ±d.ddd…ddd 的p位数(每一位是一个介于0到b-1之间的整数,包括0和b-1)。
 
  如果m的第一位是非0整数,则称m是规格化的。有一些描述使用一个单独的符号位(s代表+或者-)来表示正负,这样m必须是正的。e是指数。

  规格化又叫规格化数,是一种表示浮点数的规格化的表示方法,经过规格化的浮点数叫做规格化数。

  最后,由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。建议使用 BigDecimal 或者 Long(单位为分)来表示金额
 

3、Java中负数的绝对值并不一定是正数

 
  绝对值是指一个数在数轴上所对应的点到原点的距离。在数学领域,正数的绝对值是这个数本身,负数的绝对值应该是其相反数。
 
  Java中可以使用 java.lang.Math 中的 abs 方法获得一个数字的绝对值,一共有4个重载的 abs 方法。

public static int abs(int a) {
    return (a < 0) ? -a : a;
}

public static long abs(long a) {
    return (a < 0) ? -a : a;
}

public static float abs(float a) {
    return (a <= 0.0F) ? 0.0F - a : a;
}

public static double abs(double a) {
    return (a <= 0.0D) ? 0.0D - a : a;
}

 
  以上4个方法分别返回 int、long、float、double 类型的绝对值,方法中的逻辑也简单,无非就是遇到整数就直接返回,遇到负数就取相反数返回。
 
  我们基于订单号实现分库分表,但订单号是字符串类型,我们需要取得这个字符串的 hashCode,因为 hashCode 可能是负数,所以对 hashCode 取绝对值,再用这个值对分表数据取模:

Math.abs(orderId.hashCode()) % 1024;

 
  但是,上面这个逻辑是有问题的!因为在极特殊情况下,上面的代码会得到一个负数的值。这种极特殊情况就是当 hashCode 是 Integer.MIN_VALUE 时,即整数能表达的最小值时。通过代码来验证。
 
【abs测试绝对值为负数 - 代码】

/**
 * 绝对值
 * @author 大白有点菜
 */
public class AbsApp {
    public static void main(String[] args) {
        System.out.println("Math.abs(Integer.MIN_VALUE)的值是:" + Math.abs(Integer.MIN_VALUE));
    }
}

 
【abs测试绝对值为负数 - 运行结果】

Math.abs(Integer.MIN_VALUE)的值是:-2147483648

 
  很明显,这是一个负数!为什么会这样呢?其实,就是 Integer 的取值范围引起的, int 的取值范围为 -231 至 231 - 1,即 -21474836482147483647 。当我们使用 abs 取绝对值时,取得 -2147483648 的绝对值为 2147483648 。但是,2147483648 大于 2147483647,即超过了int的取值范围。这时就会发生越界。
 
  2147483647 用二进制的补码表示如下:

01111111 11111111 11111111 11111111

 
  这个数 + 1 后得到如下值:

10000000 00000000 00000000 00000000

 
  这个二进制值就是 -2147483648 的补码。这种情况发生的概率很低,只有当要取绝对值的数字是 -2147483648 时,得到的数字才是一个负数。
 
  如何解决这个问题呢?那就是要解决越界的问题,在取绝对值之前,把这个 int 类型的值转成 long 类型,这样就不会越界了。例如,将分表逻辑修改为:

Math.abs((long)orderId.hashCode()) % 1024;

 
【abs测试绝对值不再为负数 - 代码】

/**
 * 绝对值
 * @author 大白有点菜
 */
public class AbsApp {
    public static void main(String[] args) {
        System.out.println("Math.abs(Integer.MIN_VALUE)的值是:" + Math.abs((long)Integer.MIN_VALUE));
    }
}

 
【abs测试绝对值不再为负数 - 运行结果】

Math.abs((long)Integer.MIN_VALUE)的绝对值是(int转换为long):2147483648

 
  一定要记得,对 long 类型的数值取绝对值其实也可能存在这个情况,只不过发生的概率就更低了。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大白有点菜

你的鼓励决定文章的质量

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值