一、前言
记录时间 [2024-04-22]
系列文章简摘:
Java 笔记 01:Java 概述,MarkDown 常用语法整理
Java 笔记 02:Java 开发环境的搭建,IDEA / Notepad++ / JDK 安装及环境配置,编写第一个 Java 程序
Java 笔记 03:Java 基础知识,使用 IDEA 创建 Java 项目、设置注释颜色,以及自动生成 JavaDoc
本文介绍了 Java 数据类型和数据类型转换,包括数据类型的八大分类,以及各种数据类型的问题拓展,介绍其在不同应用场景下可能存在的问题。
二、数据类型基础
1. 强类型语言
强类型语言要求变量的使用必须严格符合规定,且所有变量都必须先定义后使用,因而安全性高。
Java 就是一种强类型语言。
弱类型语言,如,VB、JavaScript,它们的要求就相对来说没有那么高。
例如,String
是字符串类型,那么它的内容只能是字符串;int
是整数类型,那么它的内容只能是整数。否则就会报错。
// 这段用于说明Java是强类型语言
String a = "hello";
int num = 10;
2. 数据类型分类
- 基本类型(primitive type)
- 引用类型(reference type)
基本类型(primitive type)
Java 基本数据类型分为 8 类,分别是:byte;short;int;long;float;double;boolean;char;表示的类型和范围如上图所示。
基本数据类型在 Java 中的定义方式:
注意:字符串 String
不是关键字,String
是一个类。
// 八大基本数据类型
public class Demo02 {
public static void main(String[] args) {
// 整数
int num1 = 10; // 最常用
byte num2 = 20;
short num3 = 30;
long num4 =30L; // Long 类型要在数字后面加 L
// 小数:浮点数
float num5 = 50.1F; // float 类型要在数字后面加 F
double num6 = 3.141592653589793;
// 字符
char name = 'A'; // char 里面只能写一个字符
// 字符串,String 不是关键字,String 是一个类
// String namea = "Ahhh";
// 布尔值:是非
boolean flag = true;
boolean flag1 = false;
}
}
如何查看数据类型的取值范围?以 int
举例:
在 Java 中存在这样一个类:Integer
,其中规定了 int
类型的取值范围。
public final class Integer extends Number implements Comparable<Integer> {
// MIN_VALUE 最小值 -2^31
@Native public static final int MIN_VALUE = 0x80000000;
// MAX_VALUE 最大值 2^31
@Native public static final int MAX_VALUE = 0x7fffffff;
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
// ...
}
引用类型(reference type)
引用类型包括类、接口和数组,除了基本数据类型,基本都是引用类型。
3. 什么是字节
字节概述
- 位(bit):是计算机内部数据储存的最小单位,11001100 是一个 8 位二进制数;
- 字节(byte):是计算机中数据处理的基本单位,习惯上用大写 B 来表示;
- 字符:是指计算机中使用的字母、数字、字、符号。
字节单位转换
常见的字节单位有:bit;B;KB;MB;GB;TB 等。
- 1 bit 表示 1 位,可以表示二进制的 0 或者 1
- 1 Byte 表示 1 个字节
基本换算单位如下:
1 B = 8 b(bit)
1024 B = 1 KB
1024 KB = 1 MB
1024 M = 1 G
1024 G = 1 T
计算机 32 /64 位的区别
这里的位,就是字节单位中的位(bit)
32 位的计算机只能安装 32 位的操作系统;64 位的计算机 32 / 64 位的操作系统都能安装。
三、数据类型拓展
1. 整数拓展:进制问题
在生活中,我们常用十进制 0~9来进行数字计算,逢十进一。
在计算机中,我们还将接触到:二进制、八进制、十六进制。
各种进制在计算机中的表示如下:
- 二进制:
0b
,数字前面加一个0b
- 八进制:
010
,数字前面加一个0
- 十进制:正常写
- 十六进制:
0x10
,数字前面加一个0x
例如,在 Java 中各类进制的定义方式:
// 整数拓展: 进制
// 二进制 0b 十进制 八进制 0 十六进制 0x
int i = 10;
int i2 = 010; // 八进制
int i3 = 0x10; // 十六进制 0~9 A~F
// 输出查看,是以十进制形式输出的
System.out.println(i); // 10
System.out.println(i2); // 8
System.out.println(i3); // 16
2. 浮点数拓展:银行业务
思考一个问题:银行业务,比如计算金额,能使用浮点数吗?
案例分析
先来查看这样一个案例:
定义了两个浮点数,分别是 float
类型的 f,和 double
类型的 d,给它们的赋值都为 0.1,正常情况下,f 和 d 的值应该是相等的。
public class Demo03 {
public static void main(String[] args) {
// 定义 f 和 d
float f = 0.1f; //0.1
double d = 1.0/10; //0.1
// 输出 f 和 d 的值进行查看
System.out.println( "f = " + f);
System.out.println( "d = " + d);
// 判断 f 和 d 是否相等
System.out.println( "f == d ? " + (f==d) ); //false
}
}
程序输出的结果如下:显示 f 和 d 不相等。
f = 0.1
d = 0.1
f == d ? false
再看下面这个案例:
定义了两个浮点数 d1 和 d2,都是 float
类型的,我们把 d2 的值设置成 d1+1 的值,然后判断 d1 和 d2 是否相等,事实上它们是不相等的。
public class Demo03 {
public static void main(String[] args) {
// 定义浮点数 d1 和 d2
float d1 = 2323233333333333333f;
float d2 = d1 + 1; // 让 d2 等于 d1+1
// 输出 d1 和 d2 的值进行查看
System.out.println( "d1 = " + d1);
System.out.println( "d2 = d1 + 1 = " + d2);
// 判断 d1 和 d2 是否相等
System.out.println( "d1 == d2 ? " + (d1==d2) ); //true
}
}
程序输出的结果如下:显示 d1 和 d2 相等。
d1 = 2.32323343E18
d2 = d1 + 1 = 2.32323343E18
d1 == d2 ? true
误差背后的原因
由于取值范围的约束,浮点数类型表示的小数位数有限,是离散的。而计算中经常会出现计算值为无限小数的情况,浮点数不能把无限小数全部表示出来,就会进行舍入,因而存在舍入误差,出现了上面那些数值比较的问题。
可以说,浮点数类型表示数值,只能是大约,接近但不等于。
最好完全避免使用浮点数进行比较,而是使用 BigDecimal 数学工具类。
import java.math.BigDecimal;
解决方案
如何正确比较两个浮点数呢?除了使用 BigDecimal 数学工具类,还可以使用作差的方式。
简单介绍下作差的方式:如果两个浮点数之差的绝对值小于 0.0001,那么判断它们是相等的。
public class Demo03 {
public static void main(String[] args) {
// 定义 f 和 d
float f = 0.1f; //0.1
double d = 1.0/10; //0.1
// 输出 f 和 d 的值进行查看
System.out.println( "f = " + f);
System.out.println( "d = " + d);
// 作差绝对值,判断 f 和 d 是否相等
System.out.println( "Math.abs(f-d) < 0.0001 ? " + (Math.abs(f-d) < 0.0001) ); //true
}
}
程序输出的结果如下:显示 f 和 d 相等。
f = 0.1
d = 0.1
Math.abs(f-d) < 0.0001 ? true
3. 字符拓展
关于 ASCII 和 Unicode
ASCII 和 Unicode 是计算机中的两种字符编码标准。详细的编码放置在文末附录部分,需要的朋友可以参考。
- ASCII(American Standard Code for Information Interchange)使用 7 位表示 128 个字符,包括基本的拉丁字母、数字、标点符号等。ASCII 编码主要适用于英文字符,不支持多字节字符,因此无法表示汉字和其他非英文字符。
- Unicode 旨在为世界上的所有字符提供一个唯一的编码。Unicode 使用16 位(基础的 BMP 区域)或更多位来表示字符,因此可以表示全球各种语言中的字符,包括汉字、日文字、韩文字等。
关于字符类型
所有的字符本质上是数字,字符有对应的编码。
在 Java 中,字符类型 char
,占 16 位,存储形式为 Unicode 编码。例如,当在 Java 中声明一个字符变量并赋予一个字符(如汉字)时,该字符实际上是使用 Unicode 编码存储的。
当强制将 char
类型的变量转换为 int
或 byte
类型时,它实际上是将 Unicode 编码值转换为对应的数值。对于英文字符而言,这个数值就是其 ASCII 编码值。
例如下面这个例子:
char ch = 'A'; // ch 的 ASCII 值是 65, Unicode 值是 U+0041
char zh = '中'; // zh 的 Unicode 编码值是 U+4E2D
int asciiValue = (int) ch; // asciiValue 的值是 65
int unicodeValue = (int) zh; // unicodeValue 的值是 20013
// 4E2D 是十六进制,转换成十进制就是 20013
可能会存在这样一个疑问,字符 A 的 ASCII 值是 65, Unicode 值是 U+0041,为什么说 ASCII 和 Unicode 值是一样的呢?这主要是进制转换问题,0041 是十六进制,转换成十进制就是 65。
char 中使用 Unicode 编码(十六进制)形式存储字符,强制类型转换为 int 时,输出为十进制。
我们还可以直接将 Unicode 编码赋值给 char 类型变量:
char c3 = '\u0061';
System.out.println(c3); //a
由编码表可知,0061 是 a 的编码,故 c3 的值为 a;其中,\u
是转义字符。
转义字符
如上述所说,\u
是转义字符。
在编程中,转义字符是一种特殊的字符序列,用于表示一些不可见的字符或特殊字符,例如换行符、制表符或引号等。在 Java 中,转义字符以反斜杠 \
开头。
通过使用这些转义字符,可以在字符串和字符常量中插入或表示特殊的字符。
以下是一些常见的 Java 转义字符:
\n
:换行符(newline),将光标移到下一行开头。\r
:回车符(carriage return),将光标移到当前行的开头。\t
:制表符(tab),在文本中插入一个制表符。\\
:反斜杠自身,用于表示一个单独的反斜杠字符。\'
:单引号,用于在字符或字符常量中表示单引号。\"
:双引号,用于在字符串中表示双引号。\uXXXX
:Unicode 转义序列,其中XXXX
是一个 4 位十六进制数,用于表示 Unicode 字符。
例如,可以使用 \n
来插入一个换行符:
System.out.println("Hello,\nWorld!");
其输出形式为:
Hello,
World!
或者使用 \t
插入一个制表符:
System.out.println("Name\tAge");
其输出形式为:
Name Age
判断字符串相同
先来分析这样一个案例:我们使用 String
类定义了 4 个字符串变量,它们的内容都是 hello world
,但 stra 和 strb 使用的是构造对象的方式,strc 和 strd 是直接赋值的方式。
判断:stra 和 strb 是否相同?strc 和 strd 是否相同?
public class Demo03 {
public static void main(String[] args) {
// 对象 从内存分析
String stra = new String("hello world");
String strb = new String("hello world");
System.out.println(stra == strb); //false
String strc = "hello world";
String strd = "hello world";
System.out.println(strc == strd); //true
}
}
产生疑问:根据判断的结果,stra 和 strb 不相同;strc 和 strd 相同。
为什么呢?明明 stra 和 strb 中都是 hello world
,怎么不同呢?
原因在于:在 Java 中,stra 和 strb 是 String 类构造的两个对象,使用 ==
运算符比较两个对象引用时,它比较的是对象的引用地址,而不是对象的内容。
在上述代码中,stra
和 strb
是两个不同的对象,即使它们包含相同的字符串内容 hello world
。因此,使用 ==
运算符比较它们会返回 false
,因为它们的引用地址不同。
如果想比较两个字符串对象的内容是否相同,应该使用 equals()
方法。
例如:在下面的代码中,equals()
方法比较的是两个字符串对象的内容,因此输出为 true
,表示两个字符串的内容相同。
String strb = new String("hello world");
System.out.println(stra.equals(strb)); // 输出 true
4. 布尔值拓展
在 Java 中,布尔表达式本身已经是 true
或 false
,所以在检查布尔变量时,你可以直接使用变量名而不是与 true
进行比较。
if (flag == true) {}
:这是一种显式的写法,对于初学者可能更容易理解,但是相对冗长。if (flag) {}
:这是一种简洁的写法,对于有经验的开发者来说,这种写法更为常见和推荐。
// 布尔值扩展
boolean flag = true;
if (flag == true) {} // 新手
if (flag) {} // 老手
// Less is More 代码要精简易读
两者在功能上是等效的,都是检查 flag
是否为 true
。然而,第二种写法更为简洁,更符合 Java 的编码习惯和约定。
四、类型转换
由于 Java 是强类型语言,运算时会涉及类型转换问题。
数据类型的优先级,从低到高依次为:byte, short, char -> int -> long -> float -> double
小数优先级大于整数
运算中,不同类型的数据要先转化为同一类型,然后进行运算。
1. 强制类型转换
强制类型转换,是数据类型优先级从高转向低,使用时需要在变量前面加 (类型)
。
来看这样一个案例:将一个 int
类型的变量 i 的值,通过强制类型转换赋值给一个 byte
类型的变量 b,那么 b 的值是什么?
// i 是 int 类型的
int i = 128;
// 把 i 强制转换为 byte 类型
byte b = (byte) i; // b 的值实际上是 -128,而不是 128
b 的值实际上是 -128,而不是 128,这是由于数据溢出导致的。因为 byte
类型的取值范围是 -128 ~ 127,当将一个大于 byte
类型最大值或小于最小值的整数值赋给 byte
类型变量时,会发生数据溢出。
在上述例子中,int i = 128;
的值超出了 byte
类型的范围。将这个 int
值转换为 byte
类型时,会发生数据溢出。数据溢出会导致结果不可预测,通常是截断最高有效位,得到一个不正确的值。
在进行强制类型转换之前,最好检查值是否在目标数据类型的有效范围内。
2. 自动类型转换
自动类型转换,是数据类型优先级从低转向高,使用时不需要做处理。
比如,int
类型可以自动转换成 double
类型。
int i = 128;
double a = i;
3. 注意点
- 不能对布尔类型进行转换;
- 不能把对象类型转换为不相干的类型;
- 在把高容量转换到低容量时,是强制转换;
- 转换时可能存在内存溢出,或者精度问题;
char
类型也可以和数值类型互相转换。
精度问题
在 Java 中,当将一个浮点数转换为整数类型时,会执行向下取整操作。这意味着小数部分会被舍弃,只保留整数部分。
// 将浮点数转换成整数,转换时遇到精度问题
System.out.println( (int)23.7 ); //23
System.out.println( (int)-45.89f ); //-45
char 和数值类型互转
在 Java 中,当对 char
类型的字符进行数学运算时,字符会被隐式转换为其对应的 Unicode编码值。
char 类型也可以和数值类型互相转换。
// char 类型也可以和数值类型互相转换
char c = 'a';
int d = c + 1;
System.out.println(d); //98
System.out.println( (char)d ); //b
在上面的代码中:
char c = 'a';
:这里c
的 Unicode 编码值是 97,因为小写字母 a 的 Unicode 编码值是 97。int d = c + 1;
:在这里,字符 a 被转换为其 Unicode 编码值 97。然后,这个值加上 1,得到的结果是 98。- 所以,
d
的值是 98,这是字符 a 的下一个字符 b 的 Unicode 编码值。
溢出问题
操作比较大的数的时候,注意溢出问题。
分析这样一个案例:给 money 赋值为 10 亿,year 赋值为 20,money 和 year 均为 int 类型。
当 money 和 year 相乘时,是否能得到 200 亿?
public class Demo06 {
public static void main(String[] args) {
// JDK7 新特性,数字之间可以用下划线分割,下划线不会被输出。
int money = 10_0000_0000;
int years = 20;
int total1 = money * years;
System.out.println("long total1 = " + total1); //-1474836480 , 计算时溢出了
long total2 = money * years; // 默认是int,转换之前就已经出问题了
System.out.println("long total2 = " + total2); //-1474836480
// 解决问题:先把一个转换为 Long
long total3 = money * ((long)years);
System.out.println("long total3 = " + total3);
}
}
在上述案例中,money
和 years
都是 int
类型,所以当执行 money * years
运算时,结果会是 int
类型。结果超过了 int
类型的最大值(Integer.MAX_VALUE
,约为 2^31 - 1,即 2147483647),所以会发生整数溢出。即使使用 long
类型来存放结果,也无济于事。
这导致结果 total1
和 total2
的值是负数 -1474836480
。
正确的做法是:先将其中一个操作数转换为 long
类型,然后执行乘法运算:首先将 money
或 year
强制转换为 long
类型,然后执行乘法运算,这样可以避免整数溢出,并得到正确的结果。
五、总结
本文介绍了 Java 数据类型和数据类型转换,包括数据类型的八大分类,以及各种数据类型的问题拓展,介绍其在不同应用场景下可能存在的问题。
1. 附录:ASCII 码表
2. 一些参考资料
狂神说 Java 零基础:https://www.bilibili.com/video/BV12J41137hu/
TIOBE 编程语言走势: https://www.tiobe.com/tiobe-index/
Typora 官网:https://www.typoraio.cn/
Oracle 官网:https://www.oracle.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
IDEA 官网:https://www.jetbrains.com.cn/idea/
Unicode 汉字编码表:https://max.book118.com/html/2017/0125/86765015.shtm