Java 核心四大基石:从 Object 源码到包装类陷阱的全维度复盘

:::warning 其实还靠手敲来总结,汇总,编辑的人是比较笨的人,所以也许是最后一篇了吧,闲暇时光写的,耗时约3月....

💡 根据 遗忘曲线:如果没有记录和回顾,6天后便会忘记75%的内容

复制代码
  自我PUA:有人说"成功"是完成一个目标,取得相应的成就,收获到目标的果实,这是成功的标志。有人说"成功"是钱,权,与地位,因为这是成功的体现和标志。说实话,我也想要这样的"成功",因为它几乎可以无限满足我的一切欲望,可我本该糊涂的时候,却似乎又清醒着,世界上总有另一个我,在我的世界中疯狂捶打着我,告诉我,别想了,我并不是哪个幸运儿。

所以,我开始考虑到底什么是成功?什么是成功人士?成功,应该是一种无形的升华,而不是欲望的体现,不仅仅是只有实体的(成就、果实、钱、权、地位),才能被称为成功。付出,行动都不一定有收获的,那么,没有收获就是失败吗?我定下目标,我就有了第一个成功,我迈出第一步,我有了第二次成功。成功应当是唯物主义和唯心主义之间的平衡,站在某一个方向,讲成功,应当都是偏激的,不严谨的。所以请相信自己,你一直在成功的路上。即使在他人看来,你很失败,但请记住,你在成功的路上从未停止过。

成功 = 知行合一

:::

书名 Mem Reduct
作者 一毛钱钢镚儿
状态 更新中..已完结 暂停更新
简介 本书精选柯维博士"七个习惯"的最核心思想和方法,为忙碌人士带来超价值的自我提升体验。用最少的时间,参透高效能人士的持续成功之路。

思维导图

这个图,比较鸡贼,简单看看就好。

背景

在 Java 的世界里,有一句经典的话:"万物皆对象 "。
那么问题来了:时间是不是对象?文字是不是对象?我们日常处理的信息,能不能也变成对象?

让我们从两个常见的实际场景出发,看看开发者会遇到什么困惑。

场景一:如何在程序中获取"当前时间"?

你一定见过这样的界面:

直播画面右上角显示:2026 年 01 月 08 日 15:00:00(实时更新)

这个时间不是写死的,而是动态变化的 ,并且和你电脑、手机上的系统时间完全一致。
那么,如果你正在开发一个直播系统、日志记录器,或者一个简单的时钟应用,怎么让你的程序也拿到这个"当前时间"?

复制代码
- 难道要自己写一个 `MyTime` 类,手动维护年、月、日、时、分、秒?
- 如果这样,你怎么知道"现在到底是几点"?你的程序又如何和操作系统的时间保持同步?

显然,这不该由每个开发者从零实现。时间是通用需求,必须有标准、可靠、高效的解决方案。

场景二:如何处理"文字信息"并实现关键词搜索?

再看另一个常见需求:

你想在程序中实现类似搜索引擎的功能。比如用户输入关键词 "Java" 和 "姑娘",你的程序能从一堆文章标题中找出包含这些词的内容,例如:

复制代码
- 《为什么 Java 开发没有姑娘?》
- 《Java 工程师的浪漫:代码与她》

那么问题来了:

复制代码
- 这些"标题"是什么?是字符串,但字符串本身有没有"查找""匹配"的能力?
- 如果我要判断一段文字是否包含"Java",是自己写循环逐个字符比对吗?
- 如果以后还要支持模糊搜索、正则匹配、中文分词......难道每次都要重写一套逻辑?

这显然不现实。文字处理是基础能力,应该被封装成可复用的对象和方法。

Java 的答案:别重复造轮子,用 API!

面对这些问题,Java 的设计者早已替我们想好了------他们提供了一套强大、稳定、持续演进的标准类库 ,也就是我们常说的 JDK API(Application Programming Interface)

当你安装 JDK 时,其实不只是装了一个编译器(javac)或虚拟机(JVM),你还获得了一整套"开箱即用"的工具箱,包括:

  • JVM 虚拟机 :运行 Java 程序的核心引擎
  • 可执行程序 :如 javajavacjavadoc 等命令行工具
  • 配置与文档 :帮助你理解和使用这些工具
  • 最重要的是:JDK 提供的 API 类库 ------ 成千上万个已经写好、测试过、优化过的类!

这些类覆盖了时间处理、字符串操作、集合管理、网络通信、文件读写等几乎所有通用场景。

总之:我们需要做的就是,按照面向对象的开发思想 ,实现:认识对象获取对象调用对象的方法,做出我们相要的功能!

常用类

JDK 提供的 API 类库 ------ 成千上万个已经写好、测试过、优化过的类,不需要考虑它怎么实现的,不需要写底层逻辑,只需要认识它,获取它,执行它的功能。(如,已知手机:认识手机,读取说明书 | 听取发布会,获取手机,使用手机)

认识它:是什么?主要概括,说明书

获取它:面向对象的开发思想是,获取对象才能够使用对象,如何获取?

执行它:获取的对象,它有哪些功能是可以帮我们实现快速开发的;尝试使用这些功能,为每个常用的小功能写一个 Demo。

java.lang 包

Object

Object :Java 中所有类的"祖先"。无论你或我定义什么类,又或者今后某一天你看到的类(包括但不限于 JDK-API 提供的标准类),它们都默认继承自 Object。只要你用的是 Java 开发,使用了 Java 开发功能,那么任何方式出现的类"逃不掉当儿子/孙子的命运"。当然这也意味着继承部分"家产",也就是所有对象天生就具备一些基本能力(继承来自 Object 的能力)

方法

由于这种继承关系的默认存在, 因此所有的对象都自动获得了 Object 类中定义的方法,比如:

  • toString() :返回对象的一个字符串表示形式(默认行为:打印 类名@内存地址),常用于打印对象信息。
    • 默认 :打印 类名@内存地址
    • 实战痛点:默认的输出在日志里毫无意义,你根本看不出对象里的具体数据是多少。
    • 实战做法 :用于日志打印、调试,必须重写以提供有意义的信息,避免默认类名@哈希值
  • equals(Object obj) :判断当前对象是否等于另一个对象。
    • 默认比较引用地址(==);业务中常需重写(如用户ID相同即视为同一人);
  • hashCode() :返回对象的哈希码值-根据对象的内存地址计算出一个整数。通常与 equals() 方法一起重写(Java 对象契约规定的 以支持基于哈希的数据结构(如 HashMap)。
    • 必须与 equals() 保持一致:Java 对象契约规定:a.equals(b) 为 true,则 a.hashCode() == b.hashCode()
  • getClass() :返回运行时类的 Class<?> 对象。返回的是实际运行时类型 ,不是声明类型.

:::warning
如果你只重写了 equals() 而没有重写 hashCode() ,就会违反 Object 类中 equals()hashCode() 的通用契约,导致对象在基于哈希的集合(如 HashSetHashMapHashtable )中行为异常------即使两个对象逻辑上"相等"(equals() 返回 true ),也可能被当作"不同元素"存储,从而破坏集合的唯一性语义。

代码示例超纲:请在学习完成数据结构模块后,在进行回溯!

:::

java 复制代码
public class Person extends Object{ // 显式的继承
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    // ❌ 忘记重写 hashCode()!
}

当然这种继承关系,默认都是隐式的,我们自定义类的同时,可以选择显式的继承,不影响基本逻辑。

java 复制代码
// 这里隐藏式继承Object
public class Demo {// extends Object{
    public static void main(String[] args) {
        System.out.println("Hello World!");
        // 创建对象, 调用继承得到toString()方法
        new Demo().toString();

        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Alice", 30);

        System.out.println(p1.equals(p2)); // true ✅

        Set<Person> set = new HashSet<>();
        set.add(p1);
        // set.add(p2); // 本应去重,但实际会添加成功!
        // System.out.println(set.size()); // 输出 2 ❌(错误!)
        System.out.println(set.contains(p2)); // ❌ 输出 false!
    }

}

为什么 contains(p2)返回 false**?**

超纲:请在学习完成数据结构模块后,在进行回溯!

看**HashSet**** **怎么保存值,怎么判断值是否存在的?查源码!

java 复制代码
public class HashSet<E>
    ...略
    private transient HashMap<E,Object> map;
    
    public HashSet() {
        map = new HashMap<>();
    }
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
    ...略
java 复制代码
public class HashMap<K,V> extends
    ...省略
    public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    ...省略

HashSet**** 数据结构特点:

唯一、不重复、无序

底层确实是 ****HashMap

复制代码
- `new HashSet()`其内部实际执行:赋值操作,为全局变量 `map = new HshMap<>();`
- `add(E e)` 方法内部调用:`map.put(e, PRESENT)`(`PRESENT` 是一个静态常量)
- 所以 `HashSet` 本质是 **只用 key、忽略 value 的 ****HashMap**

HashMap.put(key, value)** 的关键逻辑**

复制代码
- 首先计算 `key.hashCode()` → 确定桶(bucket)位置
- 如果该桶已有元素,则:
    * **先比较 ****hashCode()**** 是否相等**
    * **只有 hashCode 相等时,才调用 ****equals()**** 进一步判断是否重复**
- `p1` 和 `p2` 的 `equals()` 返回 `true`(逻辑相等)
-  但它们继承自 `Object` 的 `hashCode()` 返回不同随机值(默认基于内存地址)
- `HashMap` 发现 `hashCode` 不同 → 直接认为是不同 key → 存入不同桶 → 去重失败

String

String :用来表示文本信息。它不仅是一个"容器",更是一个功能丰富的对象。StringJava 中用于表示不可变字符序列的类 。它是 Java 最核心、使用频率最高的类之一。你可以把它理解为一个封装好的、安全的文本容器。与其他对象不同,String 有着独特的内存管理机制(常量池),这使得它在性能和安全性上都有出色的表现。由于其不可变性一旦创建内容不可更改,所有"修改"操作-如 replace都返回新对象),它在多线程环境下是绝对安全的,常被用作Map的键(Key)或配置信息;

String 是 Java 中的"特权阶级"

对于其他对象(比如 UserOrder),你必须手动 new,因为 JVM 根本不知道你要创建什么样的对象、参数是多少。但 String 太常用了 ,为了让你写代码更爽、运行效率更高,Java 给它开了"后门",提供了语法糖和常量池机制(减少频繁 new 消耗的内存,提升性能)

String 变量名 = "内容";

过程:"内容" 被称为字符串字面量(String Literal)。JVM 在编译代码时,就已经把双引号里的内容识别出来了,并提前放入了字符串常量池。

例子String s = "abc"; ------ 编译时,"abc" 就已经作为一个常量存在于 class 文件中了。

为什么不需要 new,它也是个对象?

核心原因:字符串常量池(**String Pool**),这是 String 不需要 **new** 的根本原因。其他对象:没有"对象常量池"这种机制(Java "后门")。UserOrder每次 newJVM 都会在堆里造一个新房子(对象)。

String:JVM 维护了一个字符串常量池(一个特殊的缓存 Map)。当你写 String s = "abc"; 时,JVM 会先去池子里看有没有 "abc"

如果有,直接把池子里那个对象的引用给你(复用)。如果没有,JVM 会在池子里自动 new 一个 "abc" 放进去,然后再把引用给你。

所以,你没写 new,不代表没有 new,是 JVM 替你偷偷在常量池里 new 了,并且还帮你缓存了。

"特权阶级"的不可变和常量池

底层原理String 底层使用 private final char[](JDK 9+ 为 byte[])存储数据。final 关键字保证了数组引用不可变,且 String 类没有提供任何修改数组内容的公共方法。

System.identityHashCode(Object x) 返回的是 JVM 默认给这个对象分配的哈希码,如果传入 null,它会返回 0它无视任何重写****(Override)

java 复制代码
public class Demo {
    public static void main(String[] args) {
        System.out.println("========== 1. 不可变性验证 ==========");
        // 1. 声明一个字符串
        String str = "Hello";
        System.out.println("初始对象地址: " + System.identityHashCode(str));
        // 2. 尝试"修改"字符串(实际上是拼接)
        str = str + " World";
        System.out.println("拼接后对象地址: " + System.identityHashCode(str));


        System.out.println("\n========== 2. 常量池验证 (复用) ==========");
        String s1 = "Hello";
        String s2 = "Hello"; // 字符串常量池复用
        System.out.println("s1 == s2 (地址比较): " + (s1 == s2)); // true,同一个对象

    
        System.out.println("\n========== 3. new 和 不new 的区别 ==========");
        // 不new (字面量):走常量池
        String s3 = "XYZ"; 
        // new (构造器):强制在堆中新建对象,无视常量池(但内容可能共享)
        String s4 = new String("XYZ"); 
        System.out.println("s3 == s4 (地址比较): " + (s3 == s4)); // false,不同对象
        System.out.println("s3.equals(s4): " + s3.equals(s4));     // true,内容相同
    }

}

先"理"后"兵"

理论上不允许修改,那是正常情况下,多数请款下没人闲着出来推翻理论,更何况有些生存规则,不是推翻了,就更好用的,结合实际而言,还是原规则好一些(除非有一天,你能发明或创造出更加有利的替代品),但如果你的探索欲大于你的理智,那么可以想一想,暴力修改行不行?

这是最有趣的一个验证。既然说 String 不可变,那我能不能绕过 Java 的规则,用反射去强行修改它的内部数组?

java 复制代码
public class Demo {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello"; // 字符串常量池复用

        // 1. 获取 String 类中的 value 字段(字符数组)
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true); // 暴力访问 private 字段

        // 2. 获取 s1 内部的字符数组
        char[] value = (char[]) valueField.get(s1);

        // 3. 修改数组内容
        value[0] = 'h'; // 把第一个字符 'H' 改成 'h'

        // 4. 观察结果
        System.out.println("s1: " + s1); // 输出: hello 修改成功
        System.out.println("s2: " + s2); // 输出: hello  (卧槽,我也变了!)天杀的,s2不干净了。居然也变了
    }

}

验证结论****常量池的连锁反应

  1. 物理层面是可变的:JVM 内部的字符数组其实是可以被修改的。
  2. 逻辑层面是不可变的:正常业务代码中,我们无法获取到 value 字段,也无法修改它。String 类通过将 value 设为 private 且不提供修改方法,对外呈现出了"不可变"的特性。
  3. 常量池的副作用:因为 s1s2 指向常量池中的同一个对象,你通过反射改了内容,所有引用它的变量都会受影响(这在生产环境是灾难性的,所以正常代码绝对禁止反射修改 String)。

方法

  • concat(String str) ** 与 + / ****StringBuilder**字符串拼接。
    * 少量拼接用 ****+ :代码最简洁,编译器会自动优化。
    * 循环内大量拼接用 StringBuilder 拼接几千次字符串,千万别用 +concat,否则会创建成千上万个中间 String 对象(因为 String 不可变),导致内存飙升,直接用 StringBuilderappend 方法,最后 toString() 一下,性能提升百倍。
  • length ():获取字符串长度(即字符个数)。 * 实战避坑:如果字符串对象为 null`,调用此方法直接报错。
  • substring(int beginIndex, int endIndex) :截取子串(从beginIndex开始,到endIndex结束,包含开始,不包含结束 )。
    * 实战避坑:要注意下标越界
  • contains(CharSequence s) :判断字符串是否包含指定序列(底层其实是调用indexOf() != -1);
    * → 致命缺陷/实战避坑 :参数绝对不能为 null! 如果传入 null,底层会执行 null.toString(),直接抛出 NullPointerException (NPE)。这在处理外部不可控参数时极易导致服务崩溃
    * 在实战中,永远不要直接用原生 contains。请使用 StringUtils.contains(str, keyword)****(Apache Commons Lang) ,它对 null 输入会安全地返回 false,不会抛异常。
  • replace(CharSequence target, CharSequence replacement) :替换字符串中的某部分(将所有匹配的target替换为replacement)。
  • split(String regex) :根据正则表达式分割字符串(返回一个String数组,如果分隔符在正则中有特殊含义,记得要转义,如分割点号要用\\.)。
    * 实战避坑:参数是正则表达式,不是普通字符串!
  • equals(Object anObject) | equalsIgnoreCase(String str) :判断字符串内容是否完全相等。
    * 绝对不要用 == 判断业务数据! == 判断的是地址,equals 判断的是内容;
    * 防空指针:如果不确定字符串是否为空,建议把"确定不为空的字符串常量"放在前面调用 ,例如 "admin".equals(username)
    * 验证码校验、不区分大小写的搜索时,直接用 equalsIgnoreCase
  • trim() / strip() :去除字符串首尾的空白字符(trim()是老方法,只认ASCII空格;strip()Java11 引入的新方法,能正确处理Unicode空格,实战推荐优先使用strip())。
    * <font style="color:rgb(6, 10, 38);">trim()</font>如果遇到全角空格(中文空格),它去不掉。实战推荐优先使用<font style="color:rgb(6, 10, 38);">strip()</font>
  • indexOf(String str) | lastIndexOf(String str) :查找子串在字符串中第一次或最后一次出现的位置(如果找不到返回-1,实战中使用前务必判断是否为-1)。
  • startsWith(String prefix) / endsWith(String suffix) :判断字符串是否以指定前缀开头或以指定后缀结尾(常用于判断文件类型、URL路由等)。
  • isBlank() :判断字符串是否为空白(Java 11+新增,如果字符串为null、长度为0或全是空格,则返回true,是判空的终极利器)。替代 str == null || str.trim().isEmpty()

StringBuilder

StringBuilder 单线程字符串操作的性能之王。它是 Java 1.5 引入的可变字符序列,在确定没有多线程共享的场景下(绝大多数应用场景),应优先于 StringStringBuffer 使用。它的核心价值在于通过"可变"和"无线程安全"两大特性,解决了 String 拼接时产生的大量临时对象问题,是保障应用高性能、高可用的底层基石。

:::warning

关于线程,属于超出纲内容,可在熟悉线程后,再回溯关于StringBuilder的概述。

:::

StringBuilder | StringBuffer

  1. 可变性:底层维护一个可变的字符数组(char[] 或 byte[]),所有的修改操作(如追加、插入)都是直接在原数组上进行,不会像 String 那样创建新对象。
  2. 非线程安全:这是它与 StringBuffer 的唯一区别。它的所有方法都没有 synchronized关键字修饰,因此不能在多线程环境下共享使用,但这也让它拥有了最高的执行效率。
  3. 动态扩容:内部数组容量不足时会自动扩容(通常策略为 原容量 * 2 + 2)。虽然扩容会涉及数组拷贝,成本较高,但对开发者是透明的。
  4. 链式调用:几乎所有修改方法都返回 this(自身),支持将多个操作用点号连接起来,使代码更简洁、易读。

:::warning
StringBuffer - 自查

超纲:synchronized- 关键字,同步锁的一中,锁方法,锁代码块!

简单理解:多人同时操作,同一时间,只支持一个人操作某块业务内容。详情-参考线程

:::

方法

  • append(x):最核心的方法。将任意类型的数据转换为字符串后,追加到序列末尾。
  • insert(int offset, x):将任意类型的数据转换为字符串后,插入到指定索引 offset 处。
  • delete(int start, int end):删除从 start 索引开始到 end 索引(左闭右开)之间的字符。
  • deleteCharAt(int index):删除指定索引处的单个字符。
  • replace(int start, int end, String str):将从 startend(左闭右开)的字符替换为指定字符串 str
  • reverse():将字符序列原地反转。
  • toString():关键收尾动作。将可变的 StringBuilder 转换为不可变的 String 对象。注意,这会创建一个新的 String 对象。
  • capacity():返回当前内部缓冲区的总容量。
  • setLength(int newLength):设置字符序列的长度。可用于截断字符串,或用空字符填充。

实战中的坑

  1. 性能优化-预设初始容量

如果有大量数据需要拼接,甚至在循环拼接,那么在此之前不指定容量,StringBuilder会从默认容量(通常是16)开始,不够用,从而频繁触发扩容和数组拷贝。这会消耗大量 CPU 并产生垃圾对象,可能导致 Full GC,严重影响高可用性。如果能预估最终字符串的大致长度,务必在构造时指定初始容量,以减少扩容次数。

扩容虽然是智能的,但这个智能体,也需要做很多工作:

  1. 当你调用 append() 等方法时,JVM 首先会计算:当前已用字符数(count) + 新增字符数(len)
  2. 计算结果>当前 StringBuilder(实际维护的是数组)当前容量;触发扩容,按公式计算新容量
  3. JVM 会在堆内存中开辟一块新的连续空间,大小为"新容量"。
  4. 调用 Arrays.copyOf()(底层是 System.arraycopy),将旧数组里的所有字符原封不动地复制到新数组中。
  5. StringBuilder 内部的 value 指针指向这个全新的数组,旧的数组因为没有引用指向它了,等待垃圾回收器(GC)在合适的时候将其回收。

空间不够了,就建个更大的新房子**(N2+2)*,把旧家当全搬过去,扔掉旧房子。

  1. 转为字符串时需要调用的 toString() 可能是隐藏的"性能杀手"

如果有需要转字符串时,绕不开调用 toString() ,此时会出现共生问题,JVM 会根据当前字符序列的内容,创建一个新的 String 对象。如果 StringBuilder 里拼接了海量数据(如几百MB),调用 toString() 会瞬间申请等量的内存来存放副本,在程序未结束前,至少要满足>200MB 的内存来保证程序的稳定性。对于超大字符串,要谨慎调用 toString(),防止引发 OutOfMemoryError

大对象场景(几 MB 到几百 MB)如果必须在内存中处理,考虑使用 CharBufferByteBuffer,并尽量复用缓冲区。

  1. 绝对禁止多线程共享

StringBuilder 是线程不安全的。在 Web 服务中,绝不能将其定义为 ControllerService 的成员变量供多个请求线程共享。

多线程同时操作会导致字符错乱、丢失,甚至抛出运行时异常。

多线程场景必须使用线程安全的 StringBuffer,或者使用 ThreadLocal<StringBuilder> 来为每个线程提供独立副本。

包装类

基本数据类型:short、byte、int...基本类型不具备面向对象的特性;

Java"万物皆对象",包装类应运而生。它们让基本类型也能拥有"对象的身份",同时提供了类型转换、进制转换等实用工具。

Number

Java8 种基本数据类型各提供了一个对应的包装类。

其中Number 子类(数值型合计 6 个):IntegerLongShortByteDoubleFloat

Object 子类(非数值型 2 个):BooleanCharacter

不可变性

不可变性,这一特点,也被应用于所有包装类,一旦创建,其包装的值就不能被改变。任何"改变"操作都会返回一个新的包装类对象。

"坑"特别强调,注意,改变它,就是新的,需要正确引用,否则数值,可能产生意外,而且还是看不见错误的意外!

拆装箱

JDK 1.5 引入的语法糖。编译器可以自动在基本类型和包装类之间转换,让代码看起来更简洁。

java 复制代码
Integer i = 10; // 装箱
int j = i; // 拆箱

方法

  • parseXxx(String s):最常用。将字符串转换为对应的基本类型(如 Integer.parseInt("123"))。如果字符串格式不正确,会抛出 NumberFormatException
  • valueOf(String s):将字符串转换为包装类对象。它内部通常会调用 parseXxx,并且会利用缓存机制。
  • xxxValue():拆箱方法。将包装类对象转换回基本类型(如 Integer 对象调用 intValue())。
  • toString():将包装的数值转换为字符串表示。
  • toXxxString(xxx i):进制转换。如 Integer.toBinaryString(int i) 转换为二进制字符串,Long.toHexString(long i) 转换为十六进制字符串。

缓存机制(-128~127)

为了提高性能,IntegerShortByteLong 等数值包装类内部维护了一个静态缓存池,缓存了 -128 127 之间的对象。在这个范围内的值,使用 valueOf() 或自动装箱时,总是返回缓存中的同一个对象。

想不到吧

  1. 空指针异常(NPE)

基本类型(如 int)默认值是 0,而包装类(如 Integer)默认值是 null。包装类对象使用前,务必判空,否则一旦出现 null 值,那么这将直接抛出 NullPointerException

  1. 比较

永远不要用 == 比较两个包装类的值是否相等。务必使用 .equals() 方法。

java 复制代码
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true。
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false。

== 比较的是引用地址。由于缓存机制,-128~127 之间的对象是同一个,所以 ==true;超出这个范围是新创建的对象,引用不同,==false

相关资料

ProcessOn Mindmap Java图

净重21克 - 博客园

愛していますササ-CSDN博客