③JAVA 数据类型
③JAVA 数据类型
学习核心
基本数据类型
- Java基本类型?各占多少位?
包装类
- 基本类型和包装类的区别?
- 自动装箱和拆箱机制?原理是什么?
Integer
- int和Integer的区别?
- Integer的缓存机制?
BigDecimal
- 如何避免浮点数精度丢失问题?
- 如何比较两个BigDecimal是否相等?
数据类型分类
Java 中的数据类型有两类:
- 值类型(又叫内置数据类型,基本数据类型)
- 引用类型(除值类型以外,都是引用类型,包括
String
、数组)
基本类型
Java基本数据类型:byte、short、int、long、char、float、double、boolean
Java 语言提供了 8 种基本类型,大致分为 4 类
基本数据类型 | 分类 | 比特数 | 默认值 | 取值范围 | 说明 |
---|---|---|---|---|---|
boolean | 布尔型 | 8 位 | false | ||
char | 字符型 | 16 位 | '\u0000' | [0, $2^{16} - 1$] | 存储 Unicode 码,用单引号赋值 |
byte | 整数型 | 8 位 | 0 | [-$2^7$, $2^7 - 1$] | |
short | 整数型 | 16 位 | 0 | [-$2^{15}$, $2^{15} - 1$] | |
int | 整数型 | 32 位 | 0 | [-$2^{31}$, $2^{31} - 1$] | |
long | 整数型 | 64 位 | 0L | [-$2^{63}$, $2^{63} - 1$] | 赋值时一般在数字后加上 l 或 L |
float | 浮点型 | 32 位 | +0.0F | [$2^{-149}$, $2^{128} - 1$] | 赋值时必须在数字后加上 f 或 F |
double | 浮点型 | 64 位 | +0.0D | [$2^{-1074}$, $2^{1024} - 1$] | 赋值时一般在数字后加 d 或 D |
尽管各种数据类型的默认值看起来不一样,但在内存中都是 0。在这些基本类型中,boolean
和 char
是唯二的无符号类型
基本类型和引用类型的区别
- 从概念方面来说
- 基本类型:变量名指向具体的数值
- 引用类型:变量名指向存数据对象的内存地址
- 从内存方面来说
- 基本类型:变量在声明之后,Java 就会立刻分配给他内存空间
- 引用类型:以特殊的方式(类似 C 指针)向对象实体(具体的值),这类变量声明时不会分配内存,只是存储了一个内存地址
- 从使用方面来说
- 基本类型:使用时需要赋具体值,判断时使用
==
号 - 引用类型:使用时可以赋 null,判断时使用
equals
方法
- 基本类型:使用时需要赋具体值,判断时使用
数据类型转换
Java 中,数据类型转换有以下方式:
- 自动转换
- 强制转换
- 表达式类型的自动提升
1.自动转换
满足自动类型转换的条件:目标类型能和源类型兼容(如double类型兼容int型,但char类型不能兼容int型),在进行赋值或者运算的时候,精度小的类型自动转化为精度大的数据类型
从小到大精度排序:(结合八种基本数据类型进行记忆)
- char=>int=>long=>float=>double
- byte=>short=>int=>long=>float=>double
其中(byte、short)和char之间不会自动转化,他们可以转化为int类型参与计算
2.强制转换
在不符合自动转换条件时或者根据用户的需要,可以对数据类型做强制的转换
**强制转换使用括号 ()
**
引用类型也可以使用强制转换
3.表达式类型的自动提升
当一个算术表达式中包含多个基本类型值的时候,整个表达式的数据类型将自动提升。Java中定义了如下自动提升的规则:
所有的byte类型 short类型 和char类型 自动提升到int类型
整个算术表达式数据类型提升到与表达式中最高的操作数相同
装箱和拆箱
Java是面向对象的编程语言包含了八种基本数据类型,但是这八种基本数据类型不支持面向对象的编程机制。基本数据类型不具备对象的特征,基本数据类型没有成员变量也没有方法,所以不方便使用。为了解决这些问题,Java 中为每一种基本数据类型提供了相应的包装类,如下:
Byte <-> byte
Short <-> short
Integer <-> int
Long <-> long
Float <-> float
Double <-> double
Character <-> char
Boolean <-> boolean
在jdk1.5之后提供了自动拆箱和自动装箱功能。
所谓的自动装箱(boxing),就是把一个基本数据类型直接赋值给对应的包装类
所谓的自动拆箱(unboxing),是指把包装类直接赋值给基本数据类型
自动装箱与拆箱的机制可以在 Java 的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。 因为自动装箱会隐式地创建对象,如果在一个循环体中,会创建无用的中间对象,这样会增加 GC 压力,拉低程序的性能。所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作。(Java 对于自动装箱和拆箱的设计,依赖于一种叫做享元模式的设计模式)
# JDK 5 之前的形式:
Integer i1 = new Integer(10); // 非自动装箱
# JDK 5 之后:
Integer i2 = 10; // 自动装箱
装箱、拆箱注意点
装箱操作会创建对象,频繁的装箱操作会造成不必要的内存消耗,影响性能。所以应该尽量避免装箱。
基础数据类型的比较操作使用
==
,包装类的比较操作使用equals
方法
判等问题
Java 中,通常使用 equals
或 ==
进行判等操作。equals
是方法而 ==
是操作符。此外,二者使用也是有区别的:
- 对基本类型,比如
int
、long
,进行判等,只能使用==
,比较的是字面值。因为基本类型的值就是其数值。 - 对引用类型,比如
Integer
、Long
和String
,进行判等,需要使用equals
进行内容判等。因为引用类型的直接值是指针,使用==
的话,比较的是指针,也就是两个对象在内存中的地址,即比较它们是不是同一个对象,而不是比较对象的内容
数值计算
学习资料
1.浮点数计算问题
计算机是把数值保存在了变量中,不同类型的数值变量能保存的数值范围不同,当数值超过类型能表达的数值上限则会发生溢出问题
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(1.0 - 0.8); // 0.19999999999999996
System.out.println(4.015 * 100); // 401.49999999999994
System.out.println(123.3 / 100); // 1.2329999999999999
double amount1 = 2.15;
double amount2 = 1.10;
System.out.println(amount1 - amount2); // 1.0499999999999998
上面的几个示例,输出结果和预期的很不一样。为什么会是这样呢?
出现这种问题的主要原因是,计算机是以二进制存储数值的,浮点数也不例外。Java 采用了 IEEE 754 标准实现浮点数的表达和运算,你可以通过这里查看数值转化为二进制的结果。
比如,0.1 的二进制表示为 0.0 0011 0011 0011… (0011 无限循环),再转换为十进制就是 0.1000000000000000055511151231257827021181583404541015625。对于计算机而言,0.1 无法精确表达,这是浮点数计算造成精度损失的根源。
浮点数无法精确表达和运算的场景,一定要使用 BigDecimal 类型,且在使用BigDecimal 表示和计算浮点数,务必使用字符串的构造方法来初始化 BigDecimal
2.浮点数精度和格式化
浮点数的字符串格式化也要通过 BigDecimal 进行
private static void wrong1() {
double num1 = 3.35;
float num2 = 3.35f;
System.out.println(String.format("%.1f", num1)); // 3.4
System.out.println(String.format("%.1f", num2)); // 3.3
}
private static void wrong2() {
double num1 = 3.35;
float num2 = 3.35f;
DecimalFormat format = new DecimalFormat("#.##");
format.setRoundingMode(RoundingMode.DOWN);
System.out.println(format.format(num1)); // 3.35
format.setRoundingMode(RoundingMode.DOWN);
System.out.println(format.format(num2)); // 3.34
}
private static void right() {
BigDecimal num1 = new BigDecimal("3.35");
BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN);
System.out.println(num2); // 3.3
BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP);
System.out.println(num3); // 3.4
}
3.BigDecimal判等问题
private static void wrong() {
System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1")));
}
private static void right() {
System.out.println(new BigDecimal("1.0").compareTo(new BigDecimal("1")) == 0);
}
BigDecimal 的 equals 方法的注释中说明了原因,equals 比较的是 BigDecimal 的 value 和 scale,1.0 的 scale 是 1,1 的 scale 是 0,所以结果一定是 false。
如果希望只比较 BigDecimal 的 value,可以使用 compareTo 方法。
4.数值溢出
数值计算还有一个要小心的点是溢出,不管是 int 还是 long,所有的基本数值类型都有超出表达范围的可能性
long l = Long.MAX_VALUE;
System.out.println(l + 1); // -9223372036854775808
System.out.println(l + 1 == Long.MIN_VALUE); // true
显然这是发生了溢出,而且是默默的溢出,并没有任何异常。这类问题非常容易被忽略,改进方式有下面 2 种。
方法1:考虑使用 Math 类的 addExact、subtractExact 等 xxExact 方法进行数值运算,这些方法可以在数值溢出时主动抛出异常。
try {
long l = Long.MAX_VALUE;
System.out.println(Math.addExact(l, 1));
} catch (Exception ex) {
ex.printStackTrace();
}
方法2:使用大数类 BigInteger。BigDecimal 是处理浮点数的专家,而 BigInteger 则是对大数进行科学计算的专家。
BigInteger i = new BigInteger(String.valueOf(Long.MAX_VALUE));
System.out.println(i.add(BigInteger.ONE).toString());
try {
long l = i.add(BigInteger.ONE).longValueExact();
} catch (Exception ex) {
ex.printStackTrace();
}