跳转至

6 核心类2

约 2586 个字 463 行代码 预计阅读时间 19 分钟

包括:枚举类、记录类、BigInteger类、BigDecimal类、Math类、HexFormat类、Random类、SecureRandom类、常见方法类型。

输出结果都写在了注释中

1. 枚举类enum

1.1 static final的劣势

在Java中,我们可以通过static final来定义常量

  • 定义一周七天
Java
public class Weekday {
    public static final int SUN = 0;
    public static final int MON = 1;
    public static final int TUE = 2;
    public static final int WED = 3;
    public static final int THU = 4;
    public static final int FRI = 5;
    public static final int SAT = 6;
}
  • 定义颜色
Java
public class Color {
    public static final String RED = "r";
    public static final String GREEN = "g";
    public static final String BLUE = "b";
}

使用常量的时候,可以这么引用:

Java
if (day == Weekday.SAT || day == Weekday.SUN) {
    // Work at home
}

或:

Java
if (Color.RED.equals(color)) {
    // Process red color
}
问题 描述
无法检查范围合法性 比如if (day == 7)不报错,但7不在定义中
不同用途常量可混用 可将Color.RED赋给Weekday,编译不报错
类型信息缺失 无法明确表示这是一种“星期类型”

1.2 enum的使用

使用 enum 定义枚举类型:

Java
enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}

引用:

Java
Weekday day = Weekday.SUN;
if (day == Weekday.SAT || day == Weekday.SUN) {
    System.out.println("Work at home!");
    // Work at home!
}
优势 描述
类型安全 Weekday是强类型,不能与其他类型混用
唯一实例 每个枚举常量在 JVM 中只有一个
编译器校验 不可能引用到非枚举的值,比如if (day == 7)会直接报错;
不同类型的枚举也不能比较或者赋值,比如不能给Weekday枚举类型的变量赋值为Color枚举类型的值
支持switch 直接在switch中使用 enum

类型混用不可能编译通过:

Java
int day = 1;
if (day == Weekday.SUN) { // 编译错误
}

类型不符不可能编译通过:

Java
Weekday x = Weekday.SUN; // ok
Weekday y = Color.RED; // 编译错误

虽然 enum 看似特别,其本质还是 Java 类,只是具有以下特点:

特性 描述
自动继承java.lang.Enum 编译器自动继承
不能被继承 所有 enum 都是final
每个枚举值是类的实例 不能用new创建
可添加字段、方法、构造函数 像普通类一样扩展功能

编译器编译出的class大概就像这样:

Java
public final class Color extends Enum { // 继承自Enum,标记为final class
    // 每个实例均为全局唯一:
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();
    // private构造方法,确保外部无法调用new操作符:
    private Color() {}
}

编译后的enum类和普通class并没有任何区别,但不能按定义普通class那样来定义enum,必须使用enum关键字,这是Java语法规定的。

Java
public class demo30 {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if(day == Weekday.SAT || day == Weekday.SUN){
            System.out.println("Work at home!");
        }else{
            System.out.println("Work at office!");
        }
        // Work at home!
    }
}

enum Weekday{
    SUN,MON,TUE,WED,THU,FRI,SAT;
}

1.3 enum添加字段与构造方法

Java
package Exp03;

public class demo31 {
    public static void main(String[] args) {
        Weekday day = Weekday.FRI;

        //name():返回常量名,不可被覆写
        System.out.println(day.name());
        // FRI

        // ordinal():返回定义的常量的顺序,从0开始计数(不建议使用)
        System.out.println(day.ordinal());
        // 4

        // dayValue:建议使用自定义字段来做逻辑判断
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Today is " + day + ". Work at home!");
        }else{
            System.out.println("Today is " + day + ". Work at office!");
        }
        // Today is 星期五. Work at office!
        // 返回“星期五”而不是“5”或者“FRI”或者别的,是因为自动调用了day.toString()方法

        // 覆写的toString()
        System.out.println(day.toString());
        // 星期五
    }
}

enum Weekday{
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"),
    THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(7, "星期日");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue,String chinese){
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString(){   //覆写toString()
        return this.chinese;
    }
}

1.4 enum支持switch语句

Java
public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        switch(day) {
        case MON:
        case TUE:
        case WED:
        case THU:
        case FRI:
            System.out.println("Today is " + day + ". Work at office!");
            break;
        case SAT:
        case SUN:
            System.out.println("Today is " + day + ". Work at home!");
            break;
        default:
            throw new RuntimeException("cannot process " + day);
        }
        // Today is 星期五. Work at office!
    }
}

enum Weekday {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

总结

比较项 static final常量 enum枚举类型
类型安全 ❌ 否 ✅ 是
范围检查 ❌ 否 ✅ 是
使用限制 可随意使用 仅限自身定义的常量
支持switch ✅ 支持int/String ✅ 更适合enum使用
编译期错误检查 ❌ 运行时可能出错 ✅ 编译期报错
可读性 一般 高(可覆盖toString()

2. 记录类record

自Java 14起,引入了新的record类型,用于简洁地定义不变类。

2.1 传统的不变类

传统的不可变类(如 String, Integer)具有以下特点:

特点 说明
final修饰类 防止被继承
所有字段为final 创建后字段值不可变
提供只读方法 无 setter,仅有 getter
正确重写equals()hashCode() 用于集合或比较操作
Java
public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }

    // 还需覆写 equals(), hashCode(), toString()
}

2.2 使用record类简化不变类

只需写:

Java
record Point(int x, int y) {}

即相当于:

Java
final class Point extends Record {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }

    public String toString() {
        return String.format("Point[x=%s, y=%s]", x, y);
    }

    public boolean equals(Object o) {
        ...
    }
    public int hashCode() {
        ...
    }
}

使用示例:

Java
public class demo32 {
    public static void main(String[] args) {
        Point p = new Point(10,20);
        System.out.println(p.x());
        // 10
        System.out.println(p);
        // Point[x=10, y=20]
    }
}

record Point(int x,int y){
}

record与传统类对比:

项目 传统不变类 record写法
类定义 需手动添加final 自动为final,不可继承
字段 手动定义private final 自动为private final
构造方法 手写 自动生成
访问方法 手写 自动生成x(),y()
toString() 手写 自动生成
equals()hashCode() 手写 自动生成

2.3 构造方法

Compact Constructor(紧凑构造方法)用于参数验证:

Java
record Point(int x,int y){
    public Point{
        if(x<0 || y<0){
            throw new IllegalArgumentException("坐标不能为负数");
        }
    }
}

可以定义静态方法帮助构造对象,如 of() 方法:

Java
record Point(int x, int y) {
    public static Point of() {
        return new Point(0, 0);
    }

    public static Point of(int x, int y) {
        return new Point(x, y);
    }
}

这样我们可以写出更简洁的代码:

Java
var p1 = Point.of();
var p2 = Point.of(100, 200);

什么是静态方法和工厂方法?

静态方法:

是使用 static 关键字修饰的方法,属于类本身,而不是类的实例。

特性 说明
无需创建对象 可以直接通过类名调用,例如:Math.abs(-1)
不能访问实例变量 不能访问this,因为它不属于任何一个实例
常用于工具类、辅助方法 Math.max()Collections.sort()

示例:

Java
public class Utils {
    public static int square(int x) {
        return x * x;
    }
}

// 调用方式
int result = Utils.square(5);
// 25

工厂方法:

是一个专门用于创建对象的静态方法。它可以根据参数或条件返回不同的对象,是构造方法的替代方式。

优点 描述
可控制对象创建 比如可以添加参数校验、缓存重复对象、返回子类等
代码更清晰,语义更明确 Point.of(1, 2)new Point(1, 2)可读性更强
支持重载返回不同类型 可根据不同参数决定返回何种实现

小结:

特性 record是否支持 说明
不变类支持 所有字段为final,自动生成访问器
自动构造方法 按声明字段顺序生成
参数验证 通过 Compact Constructor 实现
添加静态方法 of()工厂方法
自定义方法 可以添加普通方法
继承其他类 继承自java.lang.Record,不可再继承其他类

3. BigInteger类

在 Java 中,基本整型的最大范围由 long 类型决定,其范围是:

Java
-2^63 ~ 2^63 - 1 ±9.2×10^18

long 类型的数据可以由 CPU 原生支持,计算速度快。但如果整数超出这个范围,就必须使用软件模拟的大整数 —— 即 BigInteger类。

定义所在包: java.math.BigInteger

作用: 表示任意大小的整数(不限于64位)

内部实现: 使用 int[] 数组来存储大整数的每一部分

特性: 不可变类(immutable),继承自 Number

3.1 运算方法

运算类型 方法 示例代码
加法 add(BigInteger val) i1.add(i2)
减法 subtract(BigInteger val) i1.subtract(i2)
乘法 multiply(BigInteger val) i1.multiply(i2)
除法 divide(BigInteger val) i1.divide(i2)
幂运算 pow(int exponent) i1.pow(5)
Java
import java.math.BigInteger;

public class demo33 {
    public static void main(String[] args) {
        BigInteger bi = new BigInteger("1234567890");
        BigInteger bi2 = new BigInteger("9876543210");

        System.out.println(bi.add(bi2));
        // 11111111100

        System.out.println(bi2.subtract(bi));
        // 8641975320

        System.out.println(bi.multiply(bi2));
        // 12193263111263526900

        System.out.println(bi2.divide(bi));
        // 8

        System.out.println(bi.pow(5)); 
        // 2867971860299718107233761438093672048294900000
    }
}

3.2 基本类型转换

方法名 说明 是否精确转换 超范围行为
byteValue() 转换为 byte 丢失高位
shortValue() 转换为 short 丢失高位
intValue() 转换为 int 丢失高位
longValue() 转换为 long 丢失高位
floatValue() 转换为 float 超范围返回 Infinity
doubleValue() 转换为 double 超范围返回 Infinity
intValueExact() 精确转换为 int 超范围抛出异常
longValueExact() 精确转换为 long 超范围抛出异常

建议使用 xxxValueExact() 防止溢出。


4. BigDecimal类

BigInteger类似,BigDecimal表示一个任意大小且精度完全准确的浮点数。

特性 说明
精度高 可以表示任意大小且精度完全准确的浮点数
常用于场景 财务、科学等对小数精度要求高的场合
不可变对象 所有修改操作返回新对象
内部结构 由一个BigInteger表示整数部分 + 一个int scale表示小数位数

4.1 运算方法

加、减、乘、幂运算方法与BigInteger类同理。

1.获取小数位scale()

Java
BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");

System.out.println(d1.scale()); 
// 2
System.out.println(d2.scale()); 
// 4
System.out.println(d3.scale()); 
// 0

2.去除末尾0(stripTrailingZeros() ):

Java
BigDecimal d1 = new BigDecimal("123.4500");
BigDecimal d2 = d1.stripTrailingZeros();

System.out.println(d1.scale()); 
// 4
System.out.println(d2.scale()); 
// 2

BigDecimal d3 = new BigDecimal("1234500");
BigDecimal d4 = d3.stripTrailingZeros();

System.out.println(d3.scale()); 
// 0
System.out.println(d4.scale()); 
// -2   (表示整数末尾有两个0)

3.可以对一个BigDecimal设置它的scale ,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:

Java
import java.math.BigDecimal;
import java.math.RoundingMode;

public class Main {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("123.456789");

        // 四舍五入
        BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP);
        // 直接截断
        BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN);

        System.out.println(d2);
        // 123.4568
        System.out.println(d3);
        // 123.4567
    }
}

4.除法与异常处理(divide() ):

存在无法除尽的情况时,必须指定精度以及如何进行截断

Java
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
// 保留10位小数并四舍五入
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP);
// 报错:ArithmeticException,因为除不尽
BigDecimal d4 = d1.divide(d2); 

5.同时获取商和余数(divideAndRemainder() ):

Java
import java.math.BigDecimal;

public class Main {
    public static void main(String[] args) {
        BigDecimal n = new BigDecimal("12.345");
        BigDecimal m = new BigDecimal("0.12");

        BigDecimal[] dr = n.divideAndRemainder(m);

        // 商:12.345/0.12 = 102.875
        System.out.println(dr[0]);
        // 102

        // 余数:12.345/0.12 ... 0.105
        System.out.println(dr[1]);
        // 0.105
    }
}

判断是否是整数倍数:

Java
BigDecimal n = new BigDecimal("12.75");
BigDecimal m = new BigDecimal("0.15");
BigDecimal[] dr = n.divideAndRemainder(m);
if (dr[1].signum() == 0) {
    System.out.println("n是m的整数倍数");
    // n是m的整数倍数
}

4.2 比较值

方法 是否比较数值 是否比较scale() 推荐使用
equals() ✅(必须一致) ❌ 不推荐
compareTo() ❌ 仅比较数值大小 ✅ 推荐使用
Java
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
System.out.println(d1.equals(d2)); 
// false
System.out.println(d1.equals(d2.stripTrailingZeros())); 
// true
System.out.println(d1.compareTo(d2));
// true

如果查看BigDecimal的源码,可以发现,实际上一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数:

Java
public class BigDecimal extends Number implements Comparable<BigDecimal> {
    private final BigInteger intVal;
    private final int scale;
}

5. 常用工具类

Java 的核心库中提供了大量实用的工具类(Utils)

  • Math:数学计算工具类
  • HexFormat:十六进制字符串与 byte[] 转换工具
  • Random:伪随机数生成器
  • SecureRandom:安全随机数生成器

5.1 Math类:数学计算

方法 示例代码 说明
绝对值 Math.abs(-100)100 适用于 int、long、float、double
最大/最小值 Math.max(10, 20)20 比较两个数的大小
幂运算 Math.pow(2, 10)1024 x^y
平方根 Math.sqrt(2)1.414...
自然指数 Math.exp(2)7.389... e^x
自然对数 Math.log(4)1.386... 以 e 为底的对数
常用对数 Math.log10(100)2 以 10 为底的对数
三角函数 Math.sin(π/6)0.5 弧度制
常量 Math.PI,Math.E 圆周率与自然常数
随机数 Math.random()[0,1) 每次结果不同

生成指定范围的随机数:

Java
public class demo35 {
    public static void main(String[] args) {
        double min = 5;
        double max = 15;
        double x = Math.random(); // [0,1)
        double y = x * (max - min) + min; // [min,max)
        System.out.println(y);
        // 7.01306198489437
        long n = (long) y; // 取整后的随机整数
        System.out.println(n);
        // 7
    }
}

5.2 HexFormat类:十六进制格式化

HexFormat 用于在 byte[] 与十六进制字符串之间转换

方法 示例 说明
formatHex(byte[]) "Hello""48656c6c6f" 转换为无分隔小写十六进制
parseHex(String) "48656c6c6f"byte[] 解析为原始字节数组
自定义格式 HexFormat.ofDelimiter(" ").withPrefix("0x").withUpperCase() 添加分隔符、前缀、大小写

byte[]数组转换为十六进制字符串(formatHex()

定制转换格式(HexFormat

从十六进制字符串到byte[]数组转换(parseHex()

十六进制 十进制 字符
48 72 H
65 101 e
6c 108 l
6c 108 l
6f 111 o
Java
import java.util.HexFormat;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // 将字符串 "Hello" 转换为对应的 byte 数组
        byte[] data = "Hello".getBytes();
        // byte[] {72, 101, 108, 108, 111}

        // 创建一个默认配置的HexFormat实例
        HexFormat hf = HexFormat.of();

        // 自定义格式:分隔符为空格,添加前缀0x,大写字母:
        HexFormat hf2 = HexFormat.ofDelimiter(" ").withPrefix("0x").withUpperCase();

        // 将byte[]数组格式化为十六进制字符串
        String hexData = hf.formatHex(data); 
        String hexData2 = hf2.formatHex(data); 

        System.out.println(hexData);
        // 48656c6c6f

        System.out.println(hexData2);
        // 0x48 0x65 0x6C 0x6C 0x6F

         // 将十六进制字符串转化为byte[]
        byte[] bs = hf.parseHex(hexData);
        String str = new String(bs);

        System.out.println(str);
        // Hello

        System.out.println(Arrays.toString(bs));
        //[72, 101, 108, 108, 111]
    }
}

5.3 Random类:伪随机数生成器

Random 生成伪随机数,可指定种子得到确定性序列,所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。

方法 示例 说明
nextInt() r.nextInt() 任意 int
nextInt(n) r.nextInt(10)[0,10) 限定范围内的 int
nextLong() r.nextLong() 任意 long
nextFloat() r.nextFloat()[0,1) 浮点随机数
nextDouble() r.nextDouble()[0,1) 双精度随机数

固定种子:

Java
import java.util.Random;

public class demo37 {
    public static void main(String[] args) {
        //种子为:12345
        Random r = new Random(12345);
        for (int i = 0; i < 5; i++) {
            // 每次输出都获得相同的随机数
            System.out.println(r.nextInt(100));
            // 51
            // 80
            // 41
            // 28
            // 55
        }
    }
}

5.4 SecureRandom类:安全随机数生成器

SecureRandom 用于生成安全的、不可预测的随机数,常用于加密和安全场景。

SecureRandom无法指定种子,它使用RNG(random number generator)算法。

JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。

实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器。

Java
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

public class demo37 {
    public static void main(String[] args) {
        SecureRandom sr = null;
        try {
            // 获取高强度安全随机数生成器
            sr = SecureRandom.getInstanceStrong(); 
        } catch (NoSuchAlgorithmException e) {
            // 获取普通的安全随机数生成器
            sr = new SecureRandom(); 
        }

        byte[] buffer = new byte[5];
        // 用安全随机数填充buffer
        sr.nextBytes(buffer); 
        System.out.println(Arrays.toString(buffer));
    }
}

6. 附:Java方法类型

6.1 常见方法类型对比

方法类型 是否 static 是否依赖对象 是否可访问this 说明
实例方法 操作对象的行为和属性
静态方法 属于类本身,用于工具函数等
工厂方法 静态方法,用来构造对象
构造方法 ❌(特殊) ✅(创建中) new时调用,初始化对象
默认方法 接口中带实现的方法(Java 8+)
抽象方法 ✅(需重写) ❌(无方法体) 必须由子类实现
私有方法 ✅ / ❌ 接口中用于内部辅助(Java 9+)

6.2 方法声明

Java
// 实例方法
public void greet() {}

// 静态方法
public static int sum(int a, int b) {
    return a + b;
}

// 工厂方法(静态构造)
public static Dog of(String name) {
    return new Dog(name);
}

// 构造方法(无返回值)
public Dog(String name) {
    this.name = name;
}

// 默认方法(接口中)
default void log() {
    System.out.println("Logging...");
}

// 抽象方法(必须被实现)
abstract void draw();

6.3 方法调用

方法类型 调用方式示例
实例方法 object.method()
静态方法 ClassName.method()
工厂方法 ClassName.of(...)
构造方法 new ClassName(...)

6.4 示例

Java
public class Dog {
    private String name;

    // 构造方法
    public Dog(String name) {
        this.name = name;
    }

    // 实例方法
    public void bark() {
        System.out.println(name + " says woof!");
    }

    // 静态方法
    public static void info() {
        System.out.println("Dogs are great pets.");
    }

    // 工厂方法
    public static Dog of(String name) {
        return new Dog(name);
    }
}

// 调用方式
Dog.info();                 // 静态方法
Dog d = Dog.of("Buddy");    // 工厂方法
d.bark();                   // 实例方法

首页 - 廖雪峰的官方网站