浅析 Java 基本数据类型与封装类

Java 有两种有效的数据类型:

  • 基本 (Primitive) 数据类型(也称为原始数据类型)
  • 引用数据类型:封装类 (Warpper) 的引用

一、8 种基本数据类型

基本类型 名称 封装类 字节数 最大值 最小值 缓存范围
byte 字节型 Byte 1byte 127 -128 -128~127
short 短整型 Short 2byte 2^15 - 1 -2^15 -128~127
int 整型 Integer 4byte 2^31 - 1 -2^31 -128~127
long 长整型 Long 8byte 2^63 - 1 -2^63 -128~127
float 单精度浮点型 Float 4byte 无缓存
double 双精度浮点型 Double 8byte 无缓存
char 字符型 Character 2byte \uffff 65,535 0 0~127
boolean 布尔型 Boolean 不明确 - - false、true

byte

1byte 为 8bits,常用于将 Object 转换为 byte[] 数组,用于字节流 ByteArrayInputStream 的输入

char

2 个字节,可表示 65,536 个字符。最初设计能保存所有的 Unicode 字符,但现在 Unicode 包含的字符越来越多,已经收录超过 13 万个字符了(截止版本 12.0.0)。

\uffff 一个 16 进制可以表示 4 位。4 * 4 可表示 16 位,2 个字节。

Character c = '颜';
System.out.println(c.BYTES); // 2

char c = '颜';
int ascii = c;
System.out.println("ascii code " + (int)'颜'); // ascii code 39068

超过两个字节的中文字符,如:𦡦

𦡦 U+26866, 𦡦

超过 2 个字节的处理方式,用 String

更多:https://www.oracle.com/technical-resources/articles/javase/supplementary.html

boolean

Java 虚拟机规范 没有明确规定 boolean 的字节数,可能为 1 个字节,也可能为 4 个字节。

Java 编程语言中对布尔值进行操作的表达式被编译为使用 Java 虚拟机 int 数据类型的值。
在 Oracle 的 Java 虚拟机实现中,Java 编程语言中的布尔数组被编码为 Java 虚拟机字节数组,每个布尔元素使用 8 位。

float

IEEE 754-1985 将 4 个字节定义为单精度(single),浮点(浮动的小数点)

  • 第 1 位表示正负,中间 8 位表示指数,后 23 位储存有效数位(有效数位是 24 位)。最左边 1 位表示 1

Float example.svg

sign = 0
exponent = (-127) + 124 = -3
fraction = 1 + 2^-2 = 1.25
value = (-1)^0 * 2^-3 * 1.25 = 0.15625

浮点型和二进制之间的相互转换:

float f = 0.15625f;
int intBits = Float.floatToIntBits(f);
String binary = Integer.toBinaryString(intBits);
System.out.println(binary); // 111110001000000000000000000000

int intBits2 = Integer.parseInt("111110001000000000000000000000", 2);
float myFloat = Float.intBitsToFloat(intBits2);
System.out.println(myFloat); // 0.15625

float f = 0.140624f;
int intBits = Float.floatToIntBits(f);
String binary = Integer.toBinaryString(intBits);
System.out.println(binary); // 111110000011111111111110111101

int intBits2 = Integer.parseInt("111110000011111111111110111101", 2);
float myFloat = Float.intBitsToFloat(intBits2);
System.out.println(myFloat); // 0.140624 = 1 * 2^-3 * (1 + 2^-4 + 2^-5 ...)

浮点型转二进制过程:

// 示例:0.15625
0.15625 * 2 = 0.3125 * 2 = 0.3125 = 0.625 * 2 = 1.25 // 2^-3 * 1.25
0.25 - 0 * 0.5(2^-1) = 0.25
0.25 - 1 * 0.25(2^-2) = 0

// 示例:0.140624
0.140624 = 2^-3 * 1.124992
0.124992 - 0 * 0.5(2^-1) = 0.124992
0.124992 - 0 * 0.25(2^-2) = 0.124992
0.124992 - 0 * 0.125(2^-3) = 0.124992
0.124992 - 1 * 0.0625(2^-4) = 0.062492
0.062492 - 1 * 0.03125(2^-5) = 0.031242

0.5(2^-1) + 0.25(2^-2) + 0.125(2^-3) + 0.0625(2^-4) + 0.03125(2^-5) + 0.015625(2^-6) + ... + 0.000000119(2^-23) ≈ 1

我们可以看出,float 采用无限逼近的方式来表示小数。

最多保留 8 位小数?

1.9999999
0 01111111 11111111111111111111111

1.0000001 // 正常应该是 1.000000119
0 01111111 00000000000000000000001

浮点型,通过不断接近的方式来得到目标值

float 类型加:

1、5.01 + 4.21

public static void main(String[] args) {
float i = 5.01f; // (-1)*0 * 2^2 * 1.2525
float j = 4.21f; // (-1)*0 * 2^2 * 1.0525
System.out.println(i + j);
}
9.22
1.2525        1.01000000101000111101100   101000000101000111101100 
+ +
1.0525 1.00001101011100001010010 100001101011100001010010
------------------------- ------------------------
10.01001110000101000111110 1001001110000101000111110
进一位,剔除末尾 0
1.1525 * 2 = 1.00100111000010100011111 * 2
2^2 * 1.2525 + 2^2 * 1.0525 = 2^2 * (1.2525 + 1.0525) = 2^2 * 2 * 1.1525 = 2^3 * 1.1525
0 10000001 01000000101000111101100 = 5.01f 
0 10000001 00001101011100001010010 = 4.21f
0 10000010 00100111000010100011111 = 9.22f

2、4.5 + 58.0

public static void main(String[] args) {
float i = 4.5f; // (-1)*0 * 2^2 * 1.0010
float j = 58.0f; // (-1)*0 * 2^5 * 1.1101
System.out.println(i + j); // (-1)*0 * 2^5 * 1.1111010
}
62.5
2^2 * 1.0010 = 2^5 * 2^-3 * 1.0010 = 2^5 * 0.0010010 // 正数左移变大,小数左移变小
2^2 * 1.0010 + 2^5 * 1.1101 = 2^5 * 1.1111010
  1.1101000     11101000
+ +
0.0010010 00010010
--------- ---------
= 1.1111010 = 11111010

double

General double precision float.png

double 和二进制之间的相互转换:

double d = 0.15625;
Long longBits = Double.doubleToLongBits(d);
String doubleBinary = Long.toBinaryString(longBits);
System.out.println(doubleBinary);

Long longBits2 = Long.parseLong("0011111111000100000000000000000000000000000000000000000000000000", 2);
Double myDouble = Double.longBitsToDouble(longBits2);
System.out.println(myDouble);

double 类型加乘:

public static void main(String[] args) {
double i = 5.01;
double j = 4.21;
System.out.println(i + j);
System.out.println(i * j);
}
9.219999999999999
21.0921

直接使用 double 加乘,有精度损失的问题,此时使用可使用 BigDecimal。

class DoubleMathUtil {
// 默认除法运算精度
private static final int DEF_DIV_SCALE = 10;

// 两个 Double 数相加
public static Double add(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.add(b2).doubleValue();
}

// 两个 Double 数相减
public static Double sub(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.subtract(b2).doubleValue();
}

// 两个 Double 数相乘
public static Double mul(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.multiply(b2).doubleValue();
}

// 两个 Double 数相除
public static Double div(Double v1, Double v2) {
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.divide(b2, DEF_DIV_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue();
}

// 两个 Double 数相除,并保留 scale 位小数
public static Double div(Double v1, Double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1.toString());
BigDecimal b2 = new BigDecimal(v2.toString());
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}

最多保留 17 位小数?0.18749999999999997

0.18749999999999997
0011111111000111111111111111111111111111111111111111111111111111

int

int 转 2 进制。

int i = 560067;
String byteStr = Integer.toBinaryString(i);
System.out.println(byteStr); // 10001000101111000011
Integer i = 150;  
int i2 = 150;
System.out.println(i == i2); // true,拆箱为基本数据类型比较
Integer i3 = Integer.valueOf(150);
System.out.println(i2 == i3); // true,拆箱为基本数据类型比较
Integer i4 = Integer.valueOf(150);
System.out.println(i3 == i4); // false,两个对象
Integer i5 = Integer.valueOf(127);
Integer i6 = Integer.valueOf(127);
System.out.println(i5 == i6); // true,内部类 IntegerCache 包含(缓存)了一个 -128-127 的 Integer 数组
Integer i7 = new Integer(127);
Integer i8 = new Integer(127);
System.out.println(i7 == i8); // false,两个为不同的对象

char 类型数字转 int

int i = '8' - '0' // 8

float 取整

Float a = 5.2f;
System.out.println(a.intValue());
float f = 5.2f;
System.out.println((int)a);
  • Byte 和 Boolean 类型缓存了全部值,缓存值存放在数组中。
  • 封装类被 final 修饰,基本数据类型也算是 final 修饰

二、基本数据类型与封装类的联系

使用集合时,其泛型必须为对象。

JDK5.0 开始提供自动封箱功能,基本数据类型可以自动封装成封装类。比如集合 List,往里添加对象 Object,JDK5.0 以前需要将数字封装成封装类型对象,再存到 List 中。

List list = new ArrayList();
list.add(new Integer(1));

在 JDK5.0 以后可以自动封箱,简写成:

List list = new ArrayList();
list.add(1);

也可以自动拆箱,将封装器类型自动转换为对应的基本数据类型

Integer a = 1;// 装箱,底层实现:Integer a = Integer.valueOf(1);
int b = a; // 拆箱,底层实现:int b = a.intValue();

ASM Bytecode Outline 反编译结果:

image.png

三、基本数据类型与其封装类的区别

1、默认值不同

int 是基本类型,直接存放数值;Integer 是类,产生对象时用一个引用指向这个对象。
基本类型跟封装类型的默认值是不一样的。如 int i,i 的预设为 0;Integer j,j 的预设为 null,因为封装类产生的是对象,对象默认值为 null。

int i = 0;
float f = 0f;
double d = 0d;
System.out.println(i);//0
System.out.println(f);//0.0
System.out.println(d);//0.0

2、存储位置不同

基本类型在内存中是存储在 Java 虚拟机栈中,封装类的引用(值的地址)存储在 Java 虚拟机栈中,而实际的对象(值)是存在堆中。

3、作用不同

基本数据类型的好处就是速度快(在栈上分配内存效率高,且不涉及到对象的构造和回收),封装类的目的主要是更好的处理数据之间的转换(利用其方法和属性)。

四、封装类应用场景

封装类常用于接受参数,当不传参数时,参数为 null?

封装类作为参数,参数可以为 null。

public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
reference.setTimeout(20000);

五、需注意

要注意自动拆箱出现空指针异常:

List<Integer> list = new ArrayList<Integer>(3);
list.add(0);
list.add(1);
list.add(null);
for (int n:list){
System.out.println("n="+n);
}
Exception in thread "main" java.lang.NullPointerException

当方法重载时,考虑 Java 向前兼容,不自动装箱,而精准匹配。

public class BugInOverloading{
public static void test(Integer num){
System.out.println("Integer num");
}
public static void test(long num){
System.out.println("long num");
}
public static void main(String[] args) {
test(1);
}
}
long num

六、延伸阅读

评论默认使用 ,你也可以切换到 来留言。