变量
进制及其转换
文件存储单位
- 计算机中的所有数据都是以二进制形式存储的,这源于早期电信号的开关表示方法。
 - 一个电信号或一个二进制位被称为
 Bit(位),8 个 Bit 组成 1 个Byte(字节)。- 因为二进制以 0 和 1 表示,所以存储单位通常以 2 的 n 次方计算。2 的 10 次方等于 1024,因此在文件存储中常用
 1 KB = 1024 Byte。
常见的进制
- 二进制(Binary):以
 0b开头,使用0和1两个数字表示数据。- 八进制(Octal):以
 0开头,使用0到7的数字表示数据。- 十六进制(Hexadecimal):以
 0x开头,使用0到9和A到F的字符表示数据。Note八进制和十六进制的引入,主要是为了更简洁地表示二进制数据,从而缩短二进制的长度,方便人类阅读和操作。
整数的存储和运算
- 正整数的存储:直接将正整数的值转换为二进制表示(实际上,计算机内部存储使用的是补码,正数的三码都是一样的)。在存储过程中,正数的符号位为0,存储的二进制就是该数的标准二进制表示。
 - 负整数的存储:
 
- 先将负整数转换为原码(负数的符号位为1,数值部分为该数的绝对值的二进制表示)。
 - 然后,将原码按位取反得到反码(反码就是把每一位的0变为1,1变为0,符号位不变)。
 - 最后,将反码加1得到补码,补码是计算机中存储负数的标准方式。
 原码、反码 和 补码
- 原码:表示负数时,符号位为1,数值部分为该数的绝对值二进制表示。
 - 反码:符号位不变,数值部分按位取反。
 - 补码:反码加1,计算机内部使用补码来存储负数。
 
常量和变量
关键字&保留字
- 关键字定义:JAVA 关键字是由语言本身预先定义的标识符,具有特殊的意义,不能用作变量名、方法名等其他用途。
 - 特殊的保留字:指的是那些目前没有实际功能,但为未来语言扩展而保留的词汇。这些词在当前版本的 Java 中未被使用,但它们不能作为标识符(例如变量名或方法名)使用。
 const和goto是保留字。- 字面量:Java 中的
 true、false和null不是关键字,而是字面量值,用于表示布尔值和空引用。- 版本更新:
 
- 在 Java 8 中,有 50 个关键字。
 - 截至 Java 21,Java 总共包含 54 个关键字,其中包括一些新增的关键字。
 - 新增的关键字:
 
var:自JDK10起,允许通过类型推断来声明变量,减少显式类型声明。yield:自JDK14起,用于在switch表达式中返回值。record:自JDK14起,引入的一种特殊的类类型,用于简化数据类的创建。sealed:自JDK15起,用于声明密封类和接口,限制其子类的创建。JDK8中的50个关键字
几个特殊的关键字
strictfp
strictfp关键字的主要作用是确保浮点数运算的精确性和一致性。在 Java 中,使用strictfp关键字声明的类、接口或方法会严格遵守 IEEE 754 标准进行浮点数运算,从而保证在不同平台和硬件上的计算结果一致。
strictfp关键字在 JDK 17 被弃用,主要原因是它对性能有显著影响,并且在现代硬件和编译器优化下,浮点运算的一致性问题已经得到了较好的解决。
transient
transient关键字在 Java 中用于声明一个类的成员变量,表示该变量不应被序列化。当对象进行序列化时,使用transient修饰的字段将被忽略,不会写入到目标字节流中,反序列化时这些字段会被初始化为默认值(基本数据类型为 0,引用类型为 null)。- 这对于敏感信息或不需要持久化的数据非常有用,例如:用户的密码、银行卡号等,可以避免在网络操作中传输或持久化到磁盘文件中。
 
volatile
volatile关键字是一个非常重要的特性,它用于保证变量的可见性和有序性。当一个变量被声明为volatile时,它会告诉编译器和运行时系统,这个变量可能会被多个线程并发访问,因此需要进行特殊的处理以确保线程安全。- 可见性(Visibility):当一个变量被声明为
 volatile,它保证了所有线程都能看到这个变量的最新值。当一个线程修改了变量的值并写回主内存时,其他线程可以立即看到这个新值。- 有序性(Ordering):
 volatile变量禁止指令重排序优化。在多线程环境下,为了提高程序性能,编译器和处理器可能会进行指令重排序优化。这种优化可能会打破多线程中操作的顺序性,导致并发问题。volatile关键字会阻止这种优化,确保在读取或写入volatile变量时,相关的操作不会因为优化而被重新排序。
标识符
- 标识符是用来给 Java 程序中的变量、类、方法、包等命名的符号。
 - 标识符必须由字母(A-Z 或 a-z)、数字(0-9)、下划线(
 _)和美元符号($)组成。- Java 语言采用 Unicode 字符集,因此标识符中的字母不仅限于英文字母,也可以使用其他语言字符(包括中文、阿拉伯字母等)。但是,建议避免使用汉字作为标识符,保持代码可读性和一致性。
 - 标识符不能以数字开头。否则,编译器无法区分数字和标识符,例如:
 1L会被解析为long类型字面量,而不是变量名。- Java 标识符是大小写敏感的。例如:
 myVariable和MyVariable是两个不同的标识符。- 标识符的长度理论上是无限制的,但为了保持代码的清晰和可读性,建议使用简短而有意义的名称。
 - 标识符不能是 Java 的关键字或保留字。它们在 Java 中有特定的功能,作为标识符使用会导致编译错误。
 
命名规范
为了提高代码的可维护性,Java 中通常遵循以下命名规范:
- 类名和接口名 使用大驼峰命名法(如
 MyClass)。- 方法名和变量名 使用小驼峰命名法(如
 myVariable)。- 常量名 使用全大写字母并用下划线分隔(如
 MAX_SIZE)。- 包名 使用全小写字母,通常采用反向域名命名法(如
 com.example.myproject)。Tip美元符号($)的使用: 在 Java 中,
$符号通常用于自动生成的代码,例如:编译器生成的类名,或者为实现某些编程技术(如工具类)所使用,不推荐在普通程序中作为命名的一部分。
变量(Variable)
- 变量的本质:变量代表一个“可操作的存储空间”,其位置是固定的,但存储的值是动态变化的。通过变量名,我们可以访问并操作存储空间中的数据。
 - 强类型语言:Java 是一种强类型语言,意味着每个变量在声明时必须指定数据类型。数据类型决定了变量占用内存的大小以及如何存储和操作数据。
 - 变量声明:变量声明本质上是在内存中开辟一块空间,用来存放指定数据类型的数据。
 - 变量赋值:变量赋值的本质是通过变量名定位内存空间,并将数据存入该空间。
 - 数据类型一致性:变量赋值时,赋值的数据类型必须与变量声明的类型匹配。如果类型不匹配,编译器会引发错误。
 - 变量的可变性:变量的最大特点就是“变”,即变量的值可以在程序运行时多次被修改。
 - 先赋值后使用:在方法中声明的变量必须先赋值后才能使用,否则会引发编译错误
 Variable 'xxx' might not have been initialized。而对于类变量,由于它们在类加载时会自动初始化为默认值,因此不需要显式赋值就可以使用。- 声明与赋值顺序:变量必须先声明,再赋值。如果尝试使用未声明的变量,编译器会报错
 Cannot resolve symbol 'xxx'。- 同一作用域内不能重复声明同名变量:在同一方法或作用域内,不能声明两个同名的变量,否则会导致编译错误
 Variable 'xxx' is already defined in the scope。
public class Main {
    public static void main(String[] args) {
        // 声明一个 int 类型的变量
        int age = 18; // 声明并初始化
 
        // 声明多个 int 类型的变量
        int num1, num2, num3;
        num1 = 5;
        num2 = 10;
        num3 = 15;
 
        // 同时声明和赋值多个变量
        int num4 = 10, num5 = 20, num6 = 30; // 不建议使用,可读性差
 
        // 输出变量
        System.out.println("age: " + age);
        System.out.println("num1: " + num1 + ", num2: " + num2 + ", num3: " + num3);
        System.out.println("num4: " + num4 + ", num5: " + num5 + ", num6: " + num6);
    }
}
 常量(Constant)
- 常量是指一个固定的值,在程序运行过程中不可更改。例如:
 1、1.1、'a'、true、false、"helloWorld"、""、null等。- 在 Java 中,常量通常是通过关键字
 final来定义的,如:final int MAX_VALUE = 100;。- 常量的特点:常量一旦被赋值后,其值就无法再更改。 如果修改常量的值,会导致编译错误
 Cannot assign a value to final variable 'xxx'。- ⚠️ final 修饰引用类型变量,表示引用本身不可更改,也就是不能再指向其他对象;但引用指向的对象内容仍然可以修改。
 - 常量名通常使用全大写字母来命名,并且如果由多个单词组成,则用下划线分隔各个单词。这是一种编码规范,旨在提高代码的可读性和维护性。
 - 常量的性能优化:常量在编译时就已经确定,因此它们常常被用来提高程序的性能。编译器可以进行一些优化,如:内联常量值、消除不必要的重复计算等。这样可以避免在运行时进行重复计算或赋值,从而减少程序的执行时间和内存开销。此外,常量在内存中的存储是固定的,且在程序生命周期内不会发生改变,编译器和虚拟机(如 JVM)可以更高效地进行内存管理和缓存,进一步提升程序的性能。
 
public class Main {
    public static final int a = 120;
 
    public static void main(String[] args) {
        System.out.println("a before: " + a);
 
        // 以下代码会编译错误,无法运行,可测试注释掉后正常运行
        a = 130; 
 
        System.out.println("a after: " + a);
    }
}import java.util.Arrays;
 
public class Main {
    public static final int[] NUM_ARRAY = {1, 2, 3};
 
    public static void main(String[] args) {
        System.out.println("Original array: " + Arrays.toString(NUM_ARRAY));
 
        // 可以修改数组内容,但不能对NUM_ARRAY重新赋值
        NUM_ARRAY[0] = 10; 
        System.out.println("Modified array: " + Arrays.toString(NUM_ARRAY));
 
        // 以下代码会报错,无法给 final 数组重新赋值
        // NUM_ARRAY = new int[]{4,5,6};
    }
}基本数据类型
Java 的数据类型分为两大类:基本数据类型(primitive type)和 引用数据类型(reference type)。 基本数据类型存储的是实际的数据值,而引用数据类型存储的是对对象的引用,也就是对象在内存中的地址。
整数型
- 整数型常量可以使用 10进制、16进制、8进制、2进制 来表示。
 
- 10进制:即常规的数字表示法,例如
 123。- 16进制:以
 0x或0X开头,表示十六进制数,例如0x1F(表示十六进制的 31)。- 8进制:以
 0开头,表示八进制数,例如010(表示八进制的 8)。- 2进制:以
 0b或0B开头,表示二进制数,例如0b101(表示二进制的 5)。- 注意数据类型的取值范围,避免超出数据类型的范围,导致溢出或错误。
 
byte b = 128;会导致编译错误,因为 byte 的最大值是 127。int i = 2147483648;会导致编译错误,因为 int 的最大值是 2,147,483,647。- 整型常量默认为
 int类型,如果需要声明long类型常量,应该在常量后加上L或l,建议使用大写L来避免与数字1混淆。
类型 占用存储空间 表数范围 byte 1字节 -128 ~ 127 、- 2⁷ ~ 2⁷ -1 short 2字节 -32768 ~ 32767 、- 2¹⁵ ~ 2¹⁵ - 1 int 4字节 -2147483648 ~ 2147483647,约21亿 、 - 2³¹ ~ 2³¹ - 1 long 8字节 -9223372036854775808 ~ 9223372036854775807、 
- 2⁶³ ~ 2⁶³ - 1
浮点型
- 小数类型在Java中称为浮点类型,用于表示带有小数点的数值。Java中的浮点类型有两种:
 float和double。- 浮点型常量可以使用 10进制 或 科学计数法 来表示。
 
314E2代表31400.0(10的2次方乘以314)。314E-2代表3.14(10的-2次方乘以314)。- float 类型:单精度浮点类型,占用 4 字节。它的尾数可以精确到 7 位有效数字。因为精度较低,在很多情况下,
 float类型的精度可能不足以满足需求。- double 类型:双精度浮点类型,占用 8 字节。它的尾数可以精确到 16 位有效数字。绝大多数应用程序使用
 double类型,尤其在涉及更高精度计算时。- 浮点型常量默认为 double 类型,即如果没有特别标明,Java 会默认将浮点常量当作
 double类型处理。如果需要将浮点常量赋值给float类型,则需要在常量后面加上f或F来标识。- 避免直接比较两个浮点数的大小。由于浮点数在存储时的精度问题,直接比较两个浮点数的大小可能会出现不准确的结果。常见做法是使用一定的容差值(epsilon)来进行比较,在需要高精度的金融计算、科学计算等场景中,应该避免使用浮点数,可以使用
 BigDecimal来处理精确小数。
类型 占用存储空间 表数范围 float 4字节 最大正数:3.4028235 × 10³⁸ 、E38 
最小正数:1.17549435 × 10⁻³⁸ 、E-38double 8字节 最大正数:1.7976931348623157 × 10³⁰⁸ 、E308 
最小正数:2.2250738585072014 × 10⁻³⁰⁸ 、E-308
浮点数在内存中的存储方式
浮点数在内存中的存储方式遵循 IEEE 754 标准。这个标准定义了浮点数的存储结构,包括:
符号位(sign)、指数位(exponent)和尾数位(mantissa)。
浮点数转换为二进制表示
我们把
3.14转换为 单精度(float) 的二进制表示。
- 确定符号位(S)
 
- 浮点数的符号位决定了数字的正负。
 - 在这个例子中,
 3.14是正数,因此符号位为0。- 转换为标准化的科学计数法
 
- 浮点数会被转换为
 1.xxxxx * 2^E的形式,其中E是指数。这个过程称为标准化。3.14可以表示为1.57 * 2^1。- 确定指数位(E)
 
- IEEE 754 标准采用了偏移量表示法,偏移量(bias)对于单精度浮点数来说是
 127,对于双精度浮点数来说是1023。- 存储的指数值 = 偏移量 + 原始指数。
 - 计算指数
 E:原始指数为1,偏移量为 127,所以存储的指数值为1 + 127 = 128。- 128 的二进制表示为
 10000000(8 位)。- 确定尾数位(M)
 
- 尾数部分是浮点数的有效数字部分。对于浮点数
 3.14来说,它的标准化形式是1.57 * 2^1,其中1.57即为尾数。- 取
 1.57的二进制表示,并去掉整数部分1(因为浮点数的表示遵循了标准化的形式,整数部分的1是固定的),只保留小数部分。1.57的二进制表示为:1.1001000111101011100001010001111...(注意,这个二进制是无限循环的,因此只保留前 23 位,尾数为10010001111010111000011)。- 组合所有部分
 
- 符号位:
 0- 指数位:
 10000000- 尾数位:
 10010001111010111000011(23 位)- 最终的 32 位单精度浮点数表示 为:
 0 | 10000000 | 10010001111010111000011Note💡注意:如果一个小数的分母是 2 的整数次方,它在二进制下通常能够精确表示。比如:
1/2(即0.5) 在二进制中表示为0.1。1/4(即0.25) 在二进制中表示为0.01。1/8(即0.125) 在二进制中表示为0.001。
public class Main {
    public static void main(String[] args) {
        float f = 3.14f;
        double d = 3.14;
 
        // float → int (32 位)
        int fBits = Float.floatToIntBits(f);
        String fBinary = String.format("%32s", Integer.toBinaryString(fBits)).replace(' ', '0');
 
        // double → long (64 位)
        long dBits = Double.doubleToLongBits(d);
        String dBinary = String.format("%64s", Long.toBinaryString(dBits)).replace(' ', '0');
 
        System.out.println("float 3.14 的二进制表示:");
        System.out.println(fBinary);
 
        System.out.println("\ndouble 3.14 的二进制表示:");
        System.out.println(dBinary);
    }
}比较两个浮点数的大小
在 Java 中比较浮点数时,由于浮点数是近似值存储的,可能会出现精度误差,因此直接使用
==、>或<来比较浮点数是不可靠的。
- 通过检查两个浮点数的差值是否小于一个极小阈值(epsilon)来判断是否近似相等。
 - 简单、常用,适合大部分科学计算或工程计算场景。
 
 
public class Main {
 
    // 比较两个 double 是否在允许误差范围内相等
    public static boolean areEqual(double a, double b, double epsilon) {
        // 处理 NaN 的情况
        if (Double.isNaN(a) || Double.isNaN(b)) {
            return false;
        }
        // 处理无穷大的情况
        if (Double.isInfinite(a) || Double.isInfinite(b)) {
            return a == b; // 无穷大只有同符号时才相等
        }
        return Math.abs(a - b) < epsilon;
    }
 
    public static void main(String[] args) {
        double a = 0.1 + 0.2;
        double b = 0.3;
        System.out.println("a = " + a + ", b = " + b);
        System.out.println("Are equal? " + areEqual(a, b, 1e-9)); // 输出 true
    }
}
 - 将浮点数转换为 
BigDecimal,并指定精度进行比较,避免精度误差。 - 适用于需要精确计算的场景(如金融领域)
 
import java.math.BigDecimal;
import java.math.RoundingMode;
 
public class Main {
 
    public static int compareWithPrecision(double a, double b, int precision) {
      	// 使用 BigDecimal(String) 避免 double 精度问题
        BigDecimal bdA = new BigDecimal(Double.toString(a))
                .setScale(precision, RoundingMode.HALF_UP);
 
        BigDecimal bdB = new BigDecimal(Double.toString(b))
                .setScale(precision, RoundingMode.HALF_UP);
 
      	// -1: 小于, 0: 等于, 1: 大于
        return bdA.compareTo(bdB);
    }
 
    public static void main(String[] args) {
        double x = 1.23456789;
        double y = 1.23456788;
 
        System.out.println("保留 6 位小数比较结果: " + compareWithPrecision(x, y, 6));
        System.out.println("保留 7 位小数比较结果: " + compareWithPrecision(x, y, 7));
    }
}
 Double.compare() / Float.compare()- Java 提供了内建方法 
Double.compare()、Float.compare()分别用于比较两个double、float类型的值。 - 它不仅可以比较大小,还能处理 
NaN和正负零的情况。 
public class Main {
    public static void main(String[] args) {
        System.out.println("Double.compare(1.234, 1.234): " + Double.compare(1.234, 1.234));       // 输出 0
        System.out.println("Double.compare(1.234, 1.235): " + Double.compare(1.234, 1.235));       // 输出 -1
 
        System.out.println("Float.compare(1.234f, 1.234f): " + Float.compare(1.234f, 1.234f));     // 输出 0
        System.out.println("Float.compare(1.234f, 1.235f): " + Float.compare(1.234f, 1.235f));     // 输出 -1
        System.out.println("Float.compare(1.234f, 1.233f): " + Float.compare(1.234f, 1.233f));     // 输出 1
    }
}
 布尔型
boolean类型是 Java 中的基础数据类型,只有两个值:true和false,true表示真,false表示假。boolean类型通常用于控制程序流程,比如if语句、while循环、for循环等。还可以用于逻辑运算,例如&&(与),||(或),!(非)等。- 与一些语言(如 C、C++)不同,Java 中的
 boolean类型不允许使用数字1或0来表示true和false。- 虽然
 boolean只有两个值,但在 Java 中,布尔类型的存储并不完全按照它的理论最小占用进行优化。- 在大多数 JVM 实现中,boolean 类型的字段(作为类的成员变量时)通常占用 1 个字节(8 位)。这是由于内存对齐和访问效率的原因,尽管实际只需要一个比特位来存储
 true(1)或false(0)。- 对于栈上的局部变量,JVM 的实现可能更加灵活。理论上,局部变量表只需一个比特位来存储布尔值。然而,由于 JVM 内存对齐的需求,局部变量通常按照 32 位(4 字节)或 64 位(8 字节)对齐。因此,即使是一个
 boolean类型的局部变量,也可能占用 4 字节(32 位)或 8 字节(64 位),具体取决于 JVM 的实现和平台架构(32 位系统或 64 位系统)。内存对齐内存对齐 是现代计算机系统中的一种优化技术,旨在提高内存访问效率。由于 CPU 对内存的读取速度不均匀,通常会根据一定的字节边界对数据进行对齐,以便提高读取速度。对于
boolean类型,虽然它理论上只需要 1 位,但在实际存储中,JVM 会按照更高的对齐方式来优化数据访问,确保内存分配与访问的效率。
字符型
- 在 Java 中,
 char类型占用 2 个字节(即 16 位)。- 字符常量由单引号
 '包裹起来,并且每个字符常量只能包含一个字符。char类型在 Java 中采用Unicode编码方式表示字符。Unicode 是一种字符编码标准,可以表示世界上几乎所有语言的字符。char类型和int类型在一定范围内是可以互相转换的,因为它们本质上都是基于数字的表示。char类型的值是 Unicode 编码的数值,而int类型可以用来存储这些数值。- Java 中常见字符及对应 Unicode/整数值:
 
字符 Unicode(十六进制) int 值(十进制) 说明 'A'\u004165 英文字母大写 A 'Z'\u005A90 英文字母大写 Z 'a'\u006197 英文字母小写 a 'z'\u007A122 英文字母小写 z '0'\u003048 数字字符 0 '9'\u003957 数字字符 9 ' '\u002032 空格 '\n'\u000A10 换行符 '\t'\u00099 制表符(Tab) 
public class Main {
   public static void main(String[] args) {
        char c1 = 'A'; // Unicode对应的数值为65
        System.out.println("c1 + 1 = " + (c1 + 1)); // 输出:66
 
        char c2 = 97;  // Unicode对应的字符为'a'
        System.out.println("(char)(c2 - 32) = " + (char)(c2 - 32)); // 输出:A
    }
}
 字符集
- 编码与解码:按照一定规则将字符转换为计算机能够处理的二进制数据(字节序列)称为编码。将存储在计算机中的二进制数据(字节序列)转换回字符称为解码。
 - ASCII:美国信息交换标准代码,是一种基于拉丁字母的字符编码系统,主要用于表示英语及西欧语言中的字符。使用 1 个字节(8 位)来表示一个字符,最多能表示 128 个字符,包括基本的拉丁字母、数字、标点符号等。
 - GBK:全称《汉字内码扩展规范》,是中文字符集的一种编码方式,扩展了早期的 GB2312 标准,能够表示更多汉字。英文占1个字节,中文占2字节。
 - Unicode:Unicode 是一种全球字符集标准,目的是为世界上几乎所有语言的字符提供统一的编码。Unicode 不指定字节表示方式,主要通过不同的编码实现方式(如 UTF-8、UTF-16)来表示字符。Unicode 包含了世界上所有的字符,包括汉字、拉丁字母、符号等,适合跨语言的计算机应用。
 - UTF-8:UTF-8 是一种可变长度的字符编码,能表示 Unicode 字符集中的任意字符。它使用 1 到 4 个字节表示一个字符。
 
- ASCII 字符(U+0000 到 U+007F)占 1 个字节。
 - 大部分中文字符(U+0800 到 U+FFFF)占 3 个字节。
 - 特殊字符(如表情符号、部分历史字符)占 4 个字节。
 - UTF-16 :UTF-16 是另一种字符编码方式,每个字符默认占用 2 字节。对于某些字符(如非 BMP 字符),需要使用 4 字节表示(通过代理对)。与 UTF-8 相比,UTF-16 对于仅包含拉丁字符的文本而言效率较低。
 
- ASCII 字符(U+0000 到 U+007F)、大部分中文字符(U+0800 到 U+FFFF)占 2 个字节。
 - 对于非基本多文种平面(BMP)内的字符(如某些表情符号和扩展符号),需要 4 字节。
 各种编码方式的对比
编码方式 字符长度 支持的字符范围 存储单位 特点 ASCII 1 字节 128 个字符 英文1字节,符号、数字 仅支持英文和符号,简单且高效 GBK 1-2 字节 约 2 万个字符 英文1字节,中文2字节 主要用于中文,支持简繁体中文 UTF-8 1-4 字节 全球所有字符 英文1字节,中文3字节,特殊字符4字节 变长编码,最适合互联网应用 UTF-16 2-4 字节 全球所有字符 英文和中文2字节,特殊字符4字节 更适合东亚语言,但在空间上不如 UTF-8 高效 
转义字符
在 Java 中,转义字符用于表示那些无法直接输入的字符,或者是一些具有特殊意义的字符。它通常由反斜杠(
\)加上一个或多个字符组成。转义字符能够帮助我们在字符串中插入特殊符号、控制字符、或者其他难以表示的字符。下面是一些常见的转义字符:
转义符 含义 Unicode值 \n 换行符 \u000a \t 制表符 u0009 \” 双引号 \u0022 \’ 单引号 \u0027 \ 反斜杠 \u005c 
public class Main {
 
 public static void main(String[] args) {
     System.out.println("换行示例: Hello\nWorld");
     System.out.println("制表符示例: Hello\tWorld");
     System.out.println("双引号示例: She said, \"Hello!\"");
     System.out.println("单引号示例: It\'s a test.");
     System.out.println("反斜杠示例: C:\\Program Files\\Java");
 }
}
 基本数据类型转换
- 在进行赋值运算或算术运算时,要求数据类型相同,否则就要进行类型转换。
 - 基本数据类型的转换主要包含:
 byte、short、int、long、float、double和char,不包含boolean类型。
自动类型转换
自动类型转换(隐式类型转换)发生在较小的数据类型赋值给较大的数据类型时。Java 会自动进行转换,无需显式指示。
常见的自动类型转换规则:
byte -> short -> int -> long -> float -> double char -> int -> long -> float -> double把整数常量(
int类型)赋值给byte、short和char类型变量,属于自动类型转换的特例,只要不超出其表数范围即可。public class Main { public static void main(String[] args) { // byte 范围:-128 ~ 127 byte b1 = 100; // 正常,自动转换 // byte b2 = 128; // 错误,超出范围 // short 范围:-32768 ~ 32767 short s1 = 20000; // 正常,自动转换 // short s2 = 40000; // 错误,超出范围 // char 范围:0 ~ 65535 char c1 = 65; // 正常,'A' // char c2 = -1; // 错误,负数不能赋给 char System.out.println("byte b1 = " + b1); System.out.println("short s1 = " + s1); System.out.println("char c1 = " + c1); // 输出字符 'A' } }算术运算中的类型自动转换原则:
- 如果两个操作数其中有一个是double类型,另一个操作就会转换为double类型,结果为double类型。
 - 否则,如果其中一个操作数是float类型,另一个将会转换为float类型,结果为float类型。
 - 否则,如果其中一个操作数是long类型,另一个会转换为long类型,结果为long类型。
 - 否则,两个操作数都转换为int类型,结果为int类型。
 public class Main { public static void main(String[] args) { int i = 10; long l = 20L; float f = 30.0f; double d = 40.0; // int + int -> int Object result1 = i + 5; System.out.println("int + int = " + result1 + " 包装类型: " + result1.getClass().getSimpleName()); // int + long -> long Object result2 = i + l; System.out.println("int + long = " + result2 + " 包装类型: " + result2.getClass().getSimpleName()); // int + float -> float Object result3 = i + f; System.out.println("int + float = " + result3 + " 包装类型: " + result3.getClass().getSimpleName()); // float + double -> double Object result4 = f + d; System.out.println("float + double = " + result4 + " 包装类型: " + result4.getClass().getSimpleName()); // long + double -> double Object result5 = l + d; System.out.println("long + double = " + result5 + " 包装类型: " + result5.getClass().getSimpleName()); } }
byte b1 = 11; byte b2 = 12; byte sum = b1 + b2;和int num1 = 100; int num2 = 300; int sum = num1 + num2;哪一个正确呢?第2个正确
因为在
b1 + b2运算时,b1和b2会被自动提升为int类型,因此其结果是int类型,而int类型的结果不能直接赋值给byte类型的变量sum。你需要显式地进行类型转换,或者将sum声明为int类型。public class Main { public static void main(String[] args) { // 情况1:byte类型运算,需要强制类型转换 byte b1 = 11; byte b2 = 12; byte bSum = (byte) (b1 + b2); // 自动提升为int,需要强制转换回byte System.out.println("byte sum = " + bSum); // 输出 23 // 情况2:int类型运算 int num1 = 100; int num2 = 300; int intSum = num1 + num2; // int + int -> int System.out.println("int sum = " + intSum); // 输出 400 } }
请说出
100000L*100000*100000和100000*100000*100000的区别?第2个超出int的表数范围,溢出。
100000L*100000*100000会使用long类型进行计算,因为100000L是long类型,long类型的结果会使整个表达式都变为long类型。因此,该计算不会发生溢出。
100000*100000*100000使用的是int类型进行计算,虽然100000*100000结果不会溢出,但最终结果1000000000000超出了int类型的最大值(int类型的最大值是2147483647),因此会发生溢出,得到的是负数:-1530494976。public class Main { public static void main(String[] args) { // 使用 long 类型参与运算,避免溢出 System.out.println(100000L * 100000 * 100000); // 输出: 1000000000000000 // 默认是 int 类型运算,超出 int 范围,会溢出 System.out.println(100000 * 100000 * 100000); // 输出: -1530494976(溢出) } }
int num1 = 90000; int num2 = 90000; int total = num1 * num2;请问total的结果是多少?-489934592
90000 * 90000计算结果是8100000000,这个数超过了int类型的最大值2147483647。因此会发生溢出,结果变为负数-489934592。public class Main { public static void main(String[] args) { int num1 = 90000; int num2 = 90000; int total = num1 * num2; // int 运算,90000*90000 = 8_100_000_000 超过 int 最大值 2_147_483_647 System.out.println("total = " + total); // 输出: -489934592(溢出) // 正确做法:使用 long 类型 long correctTotal = (long) num1 * num2; System.out.println("correct total = " + correctTotal); // 输出: 8100000000 } }
强制类型转换
强制类型转换(也叫显式类型转换)是指在编程中,显式地将一个数据类型转换为另一个数据类型。这通常通过特定的语法或函数实现,而非自动转换。在进行强制类型转换时,如果类型之间存在差异,可能会出现精度丢失、数据溢出等问题。
public class Main { public static void main(String[] args) { float a = 123.23f; byte b = (byte) a; // 强制转换为 byte,可能丢失精度 System.out.println("byte b = " + b); // 输出:123 // 原类型数据没有改变 System.out.println("float a = " + a); // 输出:123.23 } }
强制类型转换的风险
- 丢失精度:比如,将浮点数转换为整数时,小数部分会丢失。
 - 数据溢出:如果转换的数据超出目标类型的表示范围,可能会发生溢出错误。
 - 在 Java 中,使用
 Double.parseDouble(String)或Float.parseFloat(String)解析字符串时:
- 字符串为合法数字(如
 "123")→ 解析为对应数值- 字符串为
 "NaN"→ 解析为NaN- 字符串为
 "Infinity"或"−Infinity"→ 解析为正/负无穷大- 其他非数字字符串(如
 "abc","NANS")→ 抛出NumberFormatException异常public class Main { public static void main(String[] args) { // 1. double 转 int(强制转换,小数部分被截断) double num1 = 9.87; int num2 = (int) num1; // 强制转换 System.out.println("double -> int: " + num2); // 输出:9 // 2. long 转 int(超出 int 范围会溢出) long largeValue = 2147483648L; // 超出 int 的最大值 int result = (int) largeValue; // 强制转换 System.out.println("long -> int (overflow): " + result); // 输出:-2147483648 // 3. 字符串转 double String str = "NaN"; double num = Double.parseDouble(str); // 转换为 NaN System.out.println("String -> double: " + num); // 输出:NaN } }
 


