8669 字
43 min
0

变量

在 Java 编程语言中,变量是存储数据的基本单位。本文将深入探讨 Java 中的变量声明、数据类型以及如何在代码中正确使用变量,同时,也会介绍不同类型变量的存储机制。

进制及其转换

文件存储单位

  1. 计算机中的所有数据都是以二进制形式存储的,这源于早期电信号的开关表示方法。
  2. 一个电信号或一个二进制位被称为Bit(位),8 个 Bit 组成 1 个Byte(字节)
  3. 因为二进制以 0 和 1 表示,所以存储单位通常以 2 的 n 次方计算。2 的 10 次方等于 1024,因此在文件存储中常用1 KB = 1024 Byte

常见的进制

  1. 二进制(Binary):以 0b 开头,使用 01 两个数字表示数据。
  2. 八进制(Octal):以 0 开头,使用 07 的数字表示数据。
  3. 十六进制(Hexadecimal):以 0x 开头,使用 09AF 的字符表示数据。
Note

八进制和十六进制的引入,主要是为了更简洁地表示二进制数据,从而缩短二进制的长度,方便人类阅读和操作。

整数的存储和运算

  1. 正整数的存储:直接将正整数的值转换为二进制表示(实际上,计算机内部存储使用的是补码,正数的三码都是一样的)。在存储过程中,正数的符号位为0,存储的二进制就是该数的标准二进制表示。
  2. 负整数的存储
    • 先将负整数转换为原码(负数的符号位为1,数值部分为该数的绝对值的二进制表示)。
    • 然后,将原码按位取反得到反码(反码就是把每一位的0变为1,1变为0,符号位不变)。
    • 最后,将反码加1得到补码,补码是计算机中存储负数的标准方式。
原码、反码 和 补码
  • 原码:表示负数时,符号位为1,数值部分为该数的绝对值二进制表示。
  • 反码:符号位不变,数值部分按位取反。
  • 补码:反码加1,计算机内部使用补码来存储负数。

1756959571805 (1)

常量和变量

关键字&保留字

  1. 关键字定义JAVA 关键字是由语言本身预先定义的标识符,具有特殊的意义,不能用作变量名、方法名等其他用途。
  2. 特殊的保留字:指的是那些目前没有实际功能,但为未来语言扩展而保留的词汇。这些词在当前版本的 Java 中未被使用,但它们不能作为标识符(例如变量名或方法名)使用。constgoto 是保留字。
  3. 字面量:Java 中的 truefalsenull 不是关键字,而是字面量值,用于表示布尔值和空引用。
  4. 版本更新
    • 在 Java 8 中,有 50 个关键字。
    • 截至 Java 21,Java 总共包含 54 个关键字,其中包括一些新增的关键字。
  5. 新增的关键字
    • var:自JDK10 起,允许通过类型推断来声明变量,减少显式类型声明。
    • yield:自 JDK14 起,用于在 switch 表达式中返回值。
    • record:自 JDK14 起,引入的一种特殊的类类型,用于简化数据类的创建。
    • sealed:自 JDK15 起,用于声明密封类和接口,限制其子类的创建。

JDK8中的50个关键字

1756959228399 (1)

几个特殊的关键字

strictfp

  1. strictfp 关键字的主要作用是确保浮点数运算的精确性和一致性。在 Java 中,使用 strictfp 关键字声明的类、接口或方法会严格遵守 IEEE 754 标准进行浮点数运算,从而保证在不同平台和硬件上的计算结果一致。

  2. strictfp 关键字在 JDK 17 被弃用,主要原因是它对性能有显著影响,并且在现代硬件和编译器优化下,浮点运算的一致性问题已经得到了较好的解决。

transient

  1. transient 关键字在 Java 中用于声明一个类的成员变量,表示该变量不应被序列化。当对象进行序列化时,使用 transient 修饰的字段将被忽略,不会写入到目标字节流中,反序列化时这些字段会被初始化为默认值(基本数据类型为 0,引用类型为 null)。
  2. 这对于敏感信息或不需要持久化的数据非常有用,例如:用户的密码、银行卡号等,可以避免在网络操作中传输或持久化到磁盘文件中。

volatile

  1. volatile 关键字是一个非常重要的特性,它用于保证变量的可见性和有序性。当一个变量被声明为 volatile 时,它会告诉编译器和运行时系统,这个变量可能会被多个线程并发访问,因此需要进行特殊的处理以确保线程安全。
  2. 可见性(Visibility):当一个变量被声明为 volatile,它保证了所有线程都能看到这个变量的最新值。当一个线程修改了变量的值并写回主内存时,其他线程可以立即看到这个新值
  3. 有序性(Ordering)volatile 变量禁止指令重排序优化。在多线程环境下,为了提高程序性能,编译器和处理器可能会进行指令重排序优化。这种优化可能会打破多线程中操作的顺序性,导致并发问题。volatile 关键字会阻止这种优化,确保在读取或写入 volatile 变量时,相关的操作不会因为优化而被重新排序。

标识符

  1. 标识符是用来给 Java 程序中的变量、类、方法、包等命名的符号
  2. 标识符必须由字母(A-Z 或 a-z)、数字(0-9)、下划线(_)和美元符号($)组成。
  3. Java 语言采用 Unicode 字符集,因此标识符中的字母不仅限于英文字母,也可以使用其他语言字符(包括中文、阿拉伯字母等)。但是,建议避免使用汉字作为标识符,保持代码可读性和一致性。
  4. 标识符不能以数字开头。否则,编译器无法区分数字和标识符,例如: 1L 会被解析为 long 类型字面量,而不是变量名。
  5. Java 标识符是大小写敏感的。例如:myVariableMyVariable 是两个不同的标识符。
  6. 标识符的长度理论上是无限制的,但为了保持代码的清晰和可读性,建议使用简短而有意义的名称。
  7. 标识符不能是 Java 的关键字或保留字。它们在 Java 中有特定的功能,作为标识符使用会导致编译错误。

命名规范

为了提高代码的可维护性,Java 中通常遵循以下命名规范:

  • 类名和接口名 使用大驼峰命名法(如 MyClass)。
  • 方法名和变量名 使用小驼峰命名法(如 myVariable)。
  • 常量名 使用全大写字母并用下划线分隔(如 MAX_SIZE)。
  • 包名 使用全小写字母,通常采用反向域名命名法(如 com.example.myproject)。

Tip

美元符号($)的使用: 在 Java 中,$ 符号通常用于自动生成的代码,例如:编译器生成的类名,或者为实现某些编程技术(如工具类)所使用,不推荐在普通程序中作为命名的一部分。

变量(Variable)

  1. 变量的本质变量代表一个“可操作的存储空间”,其位置是固定的,但存储的值是动态变化的。通过变量名,我们可以访问并操作存储空间中的数据
  2. 强类型语言:Java 是一种强类型语言,意味着每个变量在声明时必须指定数据类型。数据类型决定了变量占用内存的大小以及如何存储和操作数据。
  3. 变量声明:变量声明本质上是在内存中开辟一块空间,用来存放指定数据类型的数据。
  4. 变量赋值:变量赋值的本质是通过变量名定位内存空间,并将数据存入该空间。
  5. 数据类型一致性变量赋值时,赋值的数据类型必须与变量声明的类型匹配。如果类型不匹配,编译器会引发错误。
  6. 变量的可变性:变量的最大特点就是“变”,即变量的值可以在程序运行时多次被修改。
  7. 先赋值后使用:在方法中声明的变量必须先赋值后才能使用,否则会引发编译错误Variable 'xxx' might not have been initialized。而对于类变量,由于它们在类加载时会自动初始化为默认值,因此不需要显式赋值就可以使用。
  8. 声明与赋值顺序:变量必须先声明,再赋值。如果尝试使用未声明的变量,编译器会报错Cannot resolve symbol 'xxx'
  9. 同一作用域内不能重复声明同名变量:在同一方法或作用域内,不能声明两个同名的变量,否则会导致编译错误Variable 'xxx' is already defined in the scope
Note
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. 常量是指一个固定的值,在程序运行过程中不可更改。例如:11.1'a'truefalse"helloWorld"""null 等。
  2. 在 Java 中,常量通常是通过关键字 final 来定义的,如:final int MAX_VALUE = 100;
  3. 常量的特点:常量一旦被赋值后,其值就无法再更改。 如果修改常量的值,会导致编译错误Cannot assign a value to final variable 'xxx'
  4. ⚠️ final 修饰引用类型变量,表示引用本身不可更改,也就是不能再指向其他对象;但引用指向的对象内容仍然可以修改
  5. 常量名通常使用全大写字母来命名,并且如果由多个单词组成,则用下划线分隔各个单词。这是一种编码规范,旨在提高代码的可读性和维护性。
  6. 常量的性能优化:常量在编译时就已经确定,因此它们常常被用来提高程序的性能。编译器可以进行一些优化,如:内联常量值、消除不必要的重复计算等。这样可以避免在运行时进行重复计算或赋值,从而减少程序的执行时间和内存开销。此外,常量在内存中的存储是固定的,且在程序生命周期内不会发生改变,编译器和虚拟机(如 JVM)可以更高效地进行内存管理和缓存,进一步提升程序的性能。
Note
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);
    }
}
Note
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)。 基本数据类型存储的是实际的数据值,而引用数据类型存储的是对对象的引用,也就是对象在内存中的地址。1756959228423

整数型

  1. 整数型常量可以使用 10进制、16进制、8进制、2进制 来表示。
    • 10进制:即常规的数字表示法,例如 123
    • 16进制:以 0x0X 开头,表示十六进制数,例如 0x1F(表示十六进制的 31)。
    • 8进制:以 0 开头,表示八进制数,例如 010(表示八进制的 8)。
    • 2进制:以 0b0B 开头,表示二进制数,例如 0b101(表示二进制的 5)。
  2. 注意数据类型的取值范围,避免超出数据类型的范围,导致溢出或错误
    • byte b = 128; 会导致编译错误,因为 byte 的最大值是 127。
    • int i = 2147483648; 会导致编译错误,因为 int 的最大值是 2,147,483,647。
  3. 整型常量默认为 int 类型,如果需要声明 long 类型常量,应该在常量后加上 Ll,建议使用大写 L 来避免与数字 1 混淆。

类型占用存储空间表数范围
byte1字节-128 ~ 127 、- 2⁷ ~ 2⁷ -1
short2字节-32768 ~ 32767 、- 2¹⁵ ~ 2¹⁵ - 1
int4字节-2147483648 ~ 2147483647,约21亿 、 - 2³¹ ~ 2³¹ - 1
long8字节-9223372036854775808 ~ 9223372036854775807、
- 2⁶³ ~ 2⁶³ - 1

浮点型

  1. 小数类型在Java中称为浮点类型,用于表示带有小数点的数值。Java中的浮点类型有两种:floatdouble
  2. 浮点型常量可以使用 10进制 或 科学计数法 来表示
    • 314E2 代表 31400.0(10的2次方乘以314)。
    • 314E-2 代表 3.14(10的-2次方乘以314)。
  3. float 类型:单精度浮点类型,占用 4 字节。它的尾数可以精确到 7 位有效数字。因为精度较低,在很多情况下,float 类型的精度可能不足以满足需求。
  4. double 类型:双精度浮点类型,占用 8 字节。它的尾数可以精确到 16 位有效数字。绝大多数应用程序使用 double 类型,尤其在涉及更高精度计算时。
  5. 浮点型常量默认为 double 类型,即如果没有特别标明,Java 会默认将浮点常量当作 double 类型处理。如果需要将浮点常量赋值给 float 类型,则需要在常量后面加上 fF 来标识。
  6. 避免直接比较两个浮点数的大小。由于浮点数在存储时的精度问题,直接比较两个浮点数的大小可能会出现不准确的结果。常见做法是使用一定的容差值(epsilon)来进行比较,在需要高精度的金融计算、科学计算等场景中,应该避免使用浮点数,可以使用 BigDecimal 来处理精确小数。

类型占用存储空间表数范围
float4字节最大正数:3.4028235 × 10³⁸ 、E38
最小正数:1.17549435 × 10⁻³⁸ 、E-38
double8字节最大正数:1.7976931348623157 × 10³⁰⁸ 、E308
最小正数:2.2250738585072014 × 10⁻³⁰⁸ 、E-308

浮点数在内存中的存储方式

1756959228417

浮点数在内存中的存储方式遵循 IEEE 754 标准。这个标准定义了浮点数的存储结构,包括:符号位(sign)指数位(exponent)尾数位(mantissa)

浮点数转换为二进制表示

我们把 3.14 转换为 单精度(float) 的二进制表示。

  1. 确定符号位(S)
    • 浮点数的符号位决定了数字的正负。
    • 在这个例子中,3.14 是正数,因此符号位为 0
  2. 转换为标准化的科学计数法
    • 浮点数会被转换为 1.xxxxx * 2^E 的形式,其中 E 是指数。这个过程称为标准化。
    • 3.14 可以表示为 1.57 * 2^1
  3. 确定指数位(E)
    • IEEE 754 标准采用了偏移量表示法,偏移量(bias)对于单精度浮点数来说是 127,对于双精度浮点数来说是 1023
    • 存储的指数值 = 偏移量 + 原始指数。
    • 计算指数 E:原始指数为 1,偏移量为 127,所以存储的指数值为 1 + 127 = 128
    • 128 的二进制表示为 10000000(8 位)。
  4. 确定尾数位(M)
    • 尾数部分是浮点数的有效数字部分。对于浮点数 3.14 来说,它的标准化形式是 1.57 * 2^1,其中 1.57 即为尾数。
    • 1.57 的二进制表示,并去掉整数部分 1(因为浮点数的表示遵循了标准化的形式,整数部分的 1 是固定的),只保留小数部分。
    • 1.57 的二进制表示为:1.1001000111101011100001010001111...(注意,这个二进制是无限循环的,因此只保留前 23 位,尾数为 10010001111010111000011)。
  5. 组合所有部分
    • 符号位:0
    • 指数位:10000000
    • 尾数位:10010001111010111000011(23 位)
    • 最终的 32 位单精度浮点数表示 为:0 | 10000000 | 10010001111010111000011

Note

💡注意:如果一个小数的分母是 2 的整数次方,它在二进制下通常能够精确表示。比如:

  • 1/2 (即 0.5) 在二进制中表示为 0.1
  • 1/4 (即 0.25) 在二进制中表示为 0.01
  • 1/8 (即 0.125) 在二进制中表示为 0.001
Note
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
  • 将浮点数转换为 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() 分别用于比较两个 doublefloat类型的值。
  • 它不仅可以比较大小,还能处理 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
    }
}
 

布尔型

  1. boolean 类型是 Java 中的基础数据类型,只有两个值:truefalsetrue 表示真,false 表示假。
  2. boolean 类型通常用于控制程序流程,比如 if 语句、while 循环、for 循环等。还可以用于逻辑运算,例如 &&(与),||(或),!(非)等。
  3. 与一些语言(如 C、C++)不同,Java 中的 boolean 类型不允许使用数字 10 来表示 truefalse
  4. 虽然 boolean 只有两个值,但在 Java 中,布尔类型的存储并不完全按照它的理论最小占用进行优化
  5. 在大多数 JVM 实现中,boolean 类型的字段(作为类的成员变量时)通常占用 1 个字节(8 位)。这是由于内存对齐和访问效率的原因,尽管实际只需要一个比特位来存储 true(1)或 false(0)。
  6. 对于栈上的局部变量,JVM 的实现可能更加灵活。理论上,局部变量表只需一个比特位来存储布尔值。然而,由于 JVM 内存对齐的需求,局部变量通常按照 32 位(4 字节)或 64 位(8 字节)对齐。因此,即使是一个 boolean 类型的局部变量,也可能占用 4 字节(32 位)或 8 字节(64 位),具体取决于 JVM 的实现和平台架构(32 位系统或 64 位系统)。

内存对齐

内存对齐 是现代计算机系统中的一种优化技术,旨在提高内存访问效率。由于 CPU 对内存的读取速度不均匀,通常会根据一定的字节边界对数据进行对齐,以便提高读取速度。对于 boolean 类型,虽然它理论上只需要 1 位,但在实际存储中,JVM 会按照更高的对齐方式来优化数据访问,确保内存分配与访问的效率

字符型

  1. 在 Java 中,char 类型占用 2 个字节(即 16 位)。
  2. 字符常量由单引号 ' 包裹起来,并且每个字符常量只能包含一个字符。
  3. char 类型在 Java 中采用 Unicode 编码方式表示字符。Unicode 是一种字符编码标准,可以表示世界上几乎所有语言的字符。
  4. char 类型和 int 类型在一定范围内是可以互相转换的,因为它们本质上都是基于数字的表示。char 类型的值是 Unicode 编码的数值,而 int 类型可以用来存储这些数值。
  5. 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)
Note
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
    }
}
 

字符集

  1. 编码与解码:按照一定规则将字符转换为计算机能够处理的二进制数据(字节序列)称为编码。将存储在计算机中的二进制数据(字节序列)转换回字符称为解码。
  2. ASCII:美国信息交换标准代码,是一种基于拉丁字母的字符编码系统,主要用于表示英语及西欧语言中的字符。使用 1 个字节(8 位)来表示一个字符,最多能表示 128 个字符,包括基本的拉丁字母、数字、标点符号等。
  3. GBK:全称《汉字内码扩展规范》,是中文字符集的一种编码方式,扩展了早期的 GB2312 标准,能够表示更多汉字。英文占1个字节,中文占2字节
  4. Unicode:Unicode 是一种全球字符集标准,目的是为世界上几乎所有语言的字符提供统一的编码。Unicode 不指定字节表示方式,主要通过不同的编码实现方式(如 UTF-8、UTF-16)来表示字符。Unicode 包含了世界上所有的字符,包括汉字、拉丁字母、符号等,适合跨语言的计算机应用。
  5. UTF-8:UTF-8 是一种可变长度的字符编码,能表示 Unicode 字符集中的任意字符。它使用 1 到 4 个字节表示一个字符
    • ASCII 字符(U+0000 到 U+007F)占 1 个字节。
    • 大部分中文字符(U+0800 到 U+FFFF)占 3 个字节。
    • 特殊字符(如表情符号、部分历史字符)占 4 个字节。
  6. UTF-16 :UTF-16 是另一种字符编码方式,每个字符默认占用 2 字节。对于某些字符(如非 BMP 字符),需要使用 4 字节表示(通过代理对)。与 UTF-8 相比,UTF-16 对于仅包含拉丁字符的文本而言效率较低
    • ASCII 字符(U+0000 到 U+007F)、大部分中文字符(U+0800 到 U+FFFF)占 2 个字节。
    • 对于非基本多文种平面(BMP)内的字符(如某些表情符号和扩展符号),需要 4 字节。

各种编码方式的对比

编码方式字符长度支持的字符范围存储单位特点
ASCII1 字节128 个字符英文1字节,符号、数字仅支持英文和符号,简单且高效
GBK1-2 字节约 2 万个字符英文1字节,中文2字节主要用于中文,支持简繁体中文
UTF-81-4 字节全球所有字符英文1字节,中文3字节,特殊字符4字节变长编码,最适合互联网应用
UTF-162-4 字节全球所有字符英文和中文2字节,特殊字符4字节更适合东亚语言,但在空间上不如 UTF-8 高效

转义字符

在 Java 中,转义字符用于表示那些无法直接输入的字符,或者是一些具有特殊意义的字符。它通常由反斜杠(\)加上一个或多个字符组成。转义字符能够帮助我们在字符串中插入特殊符号、控制字符、或者其他难以表示的字符。下面是一些常见的转义字符:

转义符含义Unicode值
\n换行符\u000a
\t制表符u0009
\”双引号\u0022
\’单引号\u0027
\反斜杠\u005c
Note
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");
 }
}
 

基本数据类型转换

  1. 在进行赋值运算或算术运算时,要求数据类型相同,否则就要进行类型转换。
  2. 基本数据类型的转换主要包含:byteshortintlongfloatdoublechar,不包含boolean类型

自动类型转换

  1. 自动类型转换(隐式类型转换)发生在较小的数据类型赋值给较大的数据类型时。Java 会自动进行转换,无需显式指示。

  2. 常见的自动类型转换规则:

    byte -> short -> int -> long -> float -> double
            char  -> int -> long -> float -> double
  3. 把整数常量(int类型)赋值给byteshortchar类型变量,属于自动类型转换的特例,只要不超出其表数范围即可

    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'
        }
    }
     
  4. 算术运算中的类型自动转换原则:

    • 如果两个操作数其中有一个是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());
        }
    }
                                     
  1. byte b1 = 11; byte b2 = 12; byte sum = b1 + b2;int num1 = 100; int num2 = 300; int sum = num1 + num2;哪一个正确呢?

    第2个正确

    因为在 b1 + b2 运算时,b1b2 会被自动提升为 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
        }
    }
                                     

  1. 请说出100000L*100000*100000100000*100000*100000的区别?

    第2个超出int的表数范围,溢出。

    100000L*100000*100000 会使用 long 类型进行计算,因为 100000Llong 类型,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(溢出)
        }
    }
                                     

  1. 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
    }
}
 

相关文章

评论区