4.3 公用窗口:System、Math与Objects


4.3 公用窗口:System、Math 与 Objects

本节摘要:System、Math、Objects 是三个纯静态工具类——构造器私有、方法全静态、不需要 new。System 管标准输入输出、计时、数组搬运与退出;Math 管取整、最值、幂与随机;Objects 提供空安全的判等、断言与取哈希。本节过一遍日常最高频的方法,并解释工具类的标准构造与为什么 Objects 能让判等代码不再到处写判空。

工具类的长相:一个构造器都没有

第 1 章讲 static 时说过:静态方法属于类不属于对象,调用不需要实例。有一类类把这个思路推到极致——全部成员都是静态的,构造器写成私有,从根上禁止 new。这类类不登记住户、不存状态,是户籍科大厅里的公用窗口:随到随办,办完就走。JDK 里最有代表性的就是本节的三个,加上第 5 章会见的集合工具类。

// 工具类的标准构造(自己写工具类时照这个样子) class RegistryKit { private RegistryKit() { } // 私有构造:堵死 new 的可能 static boolean isValidNo(int no) { // 全静态:类名直接调 return no > 0 && no < 100000; } } public class KitDemo { public static void main(String[] args) { System.out.println(RegistryKit.isValidNo(3001)); // 输出:true // RegistryKit k = new RegistryKit(); // 编译报错:构造器私有 } }

System 窗口:输出、计时与搬运

System 是最老的窗口之一,日常高频方法不多但个个常用。System.out 是标准输出流,从第一个程序用到现在;System.currentTimeMillis 返回毫秒级时间戳,是 4.2 节耗时实测的计时器;System.arraycopy 是数组批量搬运的最快路径;System.exit 强制退出;System.getProperty 读取运行环境属性:

import java.util.Arrays; public class SysDemo { public static void main(String[] args) { // 计时:毫秒时间戳 常用于性能对比的起止点 long start = System.currentTimeMillis(); int sum = 0; for (int i = 0; i < 1000000; i++) sum += i; long cost = System.currentTimeMillis() - start; System.out.println("循环耗时毫秒:" + cost); // 实测输出:个位数毫秒 System.out.println("求和结果:" + sum); // 输出:1783293664 // 数组批量搬运:比循环逐个赋值快的系统级拷贝 int[] src = {1, 2, 3, 4, 5}; int[] dst = new int[5]; System.arraycopy(src, 0, dst, 0, 5); // 源数组 源起点 目标数组 目标起点 个数 System.out.println(Arrays.toString(dst)); // 输出:[1, 2, 3, 4, 5] // 运行环境属性:不依赖外部文件 读 JVM 自身的信息 System.out.println(System.getProperty("java.version")); // 输出类似:17.0.10 } }

计时要注意一个精度坑:currentTimeMillis 的粒度是毫秒,测特别短的代码会得到零毫秒——要么把被测代码循环放大再平均(上面的求和就是放大到百万次的例子),要么用粒度到纳秒的计时方法。arraycopy 的五个参数顺序是"源、源起点、目标、目标起点、个数",写反起点是常见笔误,重叠区域的拷贝方向由系统保证正确,不需要自己处理。

Math 窗口:取整、最值与随机

Math 装的是数学常用件,全部静态方法。取整四兄弟最容易混,用一行输出对比最清楚:

public class MathDemo { public static void main(String[] args) { double v = 3.6, w = -3.6; System.out.println(Math.round(v)); // 输出:4 四舍五入 返回 long System.out.println(Math.round(w)); // 输出:-4 负数也一样五入到更远 System.out.println(Math.ceil(v)); // 输出:4.0 天花板 向上取整 System.out.println(Math.floor(v)); // 输出:3.0 地板 向下取整 System.out.println((int) v); // 输出:3 强转是直接截断 不是四舍五入 System.out.println(Math.max(7, 12)); // 输出:12 最值 System.out.println(Math.min(7, 12)); // 输出:7 System.out.println(Math.abs(-8)); // 输出:8 绝对值 System.out.println(Math.pow(2, 10)); // 输出:1024.0 幂 System.out.println(Math.sqrt(144)); // 输出:12.0 平方根 double r = Math.random(); // 输出区间:大于等于0 小于1 的小数 System.out.println("随机小数:" + r); int dice = (int) (Math.random() * 6) + 1; // 映射到 1 到 6 的整数 System.out.println("掷骰子:" + dice); } }

两个易错点单独圈出来。其一,强转截断不是四舍五入:小数转整数时直接扔掉小数部分,3.9 变 3——把金额、平均值直接强转再展示,是账目对不上的经典来源,需要四舍五入时显式用 round。其二,random 的区间映射:Math.random 返回零到一之间(含零不含一)的小数,乘六得零到六(不含六),强转得零到五的整数,加一得一到六——每一步的区间都要在心里过一遍,差一步就出现"骰子掷出零"的事故。第 6 章讲时间时还会见到更专业的随机数工具,日常掷骰子这种精度 Math.random 足够。

Objects 窗口:让判空从散装变成标配

第三个窗口最年轻也最容易被忽略。第 4.1 节说过包装类可能为 null,第 2.3 节说过判等要重写 equals——两件事撞在一起就产生了一种散装代码:判等之前先写一长串 if 判空。Objects 把这些收拢成了标准方法:

import java.util.Objects; class Holder { String label; Holder(String label) { this.label = label; } } public class ObjectsDemo { public static void main(String[] args) { Holder a = new Holder("档案甲"); Holder b = new Holder("档案甲"); Holder c = null; // 空安全判等:内部先处理 null 再调 equals System.out.println(Objects.equals(a, b)); // 输出:true(前提 Holder 重写了 equals) System.out.println(Objects.equals(a, c)); // 输出:false 不会空指针 // a.equals(c) 会抛 NullPointerException c.equals(a) 更是直接炸 // 判空断言:参数校验的标准写法 出错直接抛异常并带提示 Holder need = Objects.requireNonNull(a, "档案对象不能为空"); System.out.println(need.label); // 输出:档案甲 // 散装写法与标准写法的对比 boolean oldWay = (a != null) && (a.label != null) && a.label.equals("档案甲"); boolean newWay = Objects.equals(a != null ? a.label : null, "档案甲"); System.out.println(oldWay + " " + newWay); // 输出:true true System.out.println(Objects.hashCode("档案甲")); // 取哈希 null 安全 System.out.println(Objects.isNull(c)); // 输出:true 就是 c == null 的语义版 } }

(说明:Holder 若不重写 equals,第一行会输出 false——默认 equals 比引用,这正是 2.3 节的结论在工具方法里的延续。)requireNonNull 是参数校验的推荐写法:方法入口一行,传 null 立刻抛异常并带上自定义提示,比方法体深处某个字段访问时炸出一个无提示的空指针好排查得多。equals、isNull、hashCode 这些方法的共同卖点是把判空从"想到才写"变成"顺手就调"——散装的三行判空合并成一次调用,可读性和安全同时上升。

完整案例背景:报表模块到处写着 if (x != null && x.equals(y)) 式的判等,有人漏写判空,线上空指针每周一单。操作:全库替换为 Objects.equals,公开方法入口统一加 requireNonNull。结果:空指针类工单消失,且异常信息带上了"哪个参数为空"的提示,定位时间从翻堆栈降到读一行。解读:工具类的价值不只是省行数,更是把正确做法变成默认做法。变式:若判空后要走复杂分支(缺省值、降级逻辑),Objects 提供了取值带缺省的方法,比三元表达式更直白;集合与数组的判空在各自工具类里,第 5 章的集合工具类会补齐。

本节要点回顾

  • 工具类的标准长相:私有构造器堵死 new、成员全静态、无状态;自己写工具类照此构造
  • System 高频四件:currentTimeMillis 计时(短任务要放大循环再平均)、arraycopy 五参数批量搬运、out 标准输出、getProperty 读环境属性
  • Math 取整四兄弟:round 四舍五入、ceil 向上、floor 向下、强转是截断不是四舍五入——账目误差的经典来源
  • 随机数区间映射要逐步心算:零到一乘六加强转加一,才是一到六;差一步就掷出零
  • Objects 是判空标配:equals 空安全判等、requireNonNull 入口断言、isNull 与 hashCode 补齐散装写法

工具房一逛完,单件的家伙什都齐了。下一章进最大的那间仓库——集合框架:队列、查重窗、索引柜,加上给登记表加类型栏的泛型,日常业务代码的一半要靠它们搭起来。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U