-
引言 在Java的世界里,我们每天都在与整数打交道:int age = 30;、long balance = 1000000L;。但你是否思考过,这些数字在计算机内部的真实形态?理解原码、反码、补码不仅是计算机科学的基础,更是深入Java底层、避免隐蔽bug的关键。本文将带你彻底掌握这些二进制表示法的奥秘及其在Java中的实际应用。一、 基础概念:三种编码的诞生背景计算机只能处理二进制(0和1)。如何表示有符号整数(正数、负数)?工程师们设计了三种方案:1、原码定义:最高位表示符号位(0=正数,1=负数),其余位表示数值的绝对值示例 (8位byte为例):+5原码:0000 0101 (符号位0,绝对值5)-5原码:1000 0101 (符号位1,绝对值5)优点:人类直观,理解容易致命缺陷:存在两种零:+0的原码为00000000 ,-0的原码为10000000( 以一个字节长表示 ),浪费表示范围,逻辑上冗余加减运算复杂:正数和负数相加,数值部分实际上是相减,符号取决于绝对值大者的符号,硬件不能直接相加符号位和数值位示例1:(+5) + (-5) = 0,程序直接运算❌原码的加法不支持符号修正(不能自动让结果的符号与数值正确对应) 0000 0101 (+5) + 1000 0101 (-5) ----------------- 1000 1010 结果为 -10示例2:(-5) + (+3) = -2,程序直接运算❌ 1000 0101 (-5) + 0000 0011 (+3) ----------------- 1000 1000 结果为 -8示例3:(-5) + (-5) = -10,程序直接运算❌原码的加法不支持进位消除(多出的进位被丢弃,不影响结果) 1000 0101 (-5) + 1000 0101 (-5) ----------------- 1 0000 1010 结果为 10(注意:最高位溢出了1位舍弃)2、反码定义:正数:反码 = 原码负数:反码 = 负数原码符号位不变,数值位按位取反,或者更简单点正数原码全部按位取反示例 (8位byte为例):+5反码:0000 0101 (同原码)--5反码:1111 1010 (-5的原码1000 0101按位取反,符号位不变或者+5原码全部按位取反)遗留问题:两种零依然存在:+0反码为0000 0000,-0反码1111 1111(+0的原码按位取反)循环进位:计算结果最高位有进位时,需要把进位“回加”到最低位(称为“末位加1”或“循环进位”),硬件实现仍不理想示例1:(+5) + (-5) = 0,程序直接运算✅反码的加法在数值上可以看作正确,但从表达角度来看,有点不完美 0000 0101 (+5) + 1111 1010 (-5) ----------------- 1111 1111 结果是反码,符号位不变其他按位取反,原码就是1000 0000也就是-0(负零)示例2:(-5) + (+3) = -2,程序直接运行✅ 1111 1010 (-5) + 0000 0011 (+3) ----------------- 1111 1101 结果是反码,原码为1000 0010也就是-2示例3:(-5) + (-5) = -10,程序直接运行❌反码的加法需要循环进位✅ 1111 1010 (-5) + 1111 1010 (-5) ----------------- 1 1111 0100 结果是反码(注意:最高位溢出了1位舍弃),最高位进位:1(需循环加回最低位) 1111 0100 + 1 ----------------- 1111 0101 这里还是反码,原码为1000 1010也就是-103、补码 🏆 - Java的选择定义:正数:补码 = 原码负数:补码 = 反码 + 1示例 (8位byte为例):+5补码:0000 0101-5补码:+5的原码按位取反获得反码1111 1010,再加1获得补码1111 1011核心优势 (完美解决前两者问题):唯一的零:+0补码为0000 0000,-0补码是+0的原码全部按位取反再加1得到还是0000 0000,溢出一位舍去减法变加法:A - B = A + (-B) 直接成立,无需额外判断符号位或处理循环进位。硬件只需一套加法电路示例1:(+5) + (-5) = 0,程序直接运算✅补码加法支持进位消除(多出的进位被丢弃,不影响结果) 0000 0101 (+5) + 1111 1011 (-5) ----------------- 1 0000 0001 结果是补码,先减1获得反码0000 0000,也就是0示例2:(-5) + (+3) = -2,程序直接运算✅ 1111 1011 (-5) + 0000 0011 (+3) ----------------- 1111 1110 结果是补码,先减1获得反码1111 1101,符号位不变其他按位取反获取原码1000 0010,也就是-2示例3:(-5) + (-5) = -10,程序直接运算✅ 1111 1011 (-5) + 1111 1011 (-5) ----------------- 1 1111 0110 结果为是补码,先减1获得反码1111 0101,符号位不变其他按位取反获得原码1000 1010,也就是-10二、 Java的坚定选择:补码一统天下1、为什么Java整数表示使用补码?补码解决了原码和反码的固有问题(双零、复杂运算),简化了CPU硬件设计,提高了运算效率最大优势正数和负数的加法、减法可以统一处理(解决痛点:原码需要判断正负)补码中只有一个 0(解决痛点:原码和反码有+0和-0,也需要单独判断)补码支持进位消除(解决痛点:符号参加运行,多出的进位丢弃,不影响结果,反码需要循环进位)Java的所有整数类型(byte, short, int, long)均使用补码表示!这是现代计算机体系结构的标准2、为什么byte的范围是-128到127,而不是-127到127?8 位二进制的表示能力Byte 类型占用 8 位(1 字节)存储空间,共有2^8 = 256种可能的二进制组合在补码体系中,最高位为符号位(0 正 1 负),剩余 7 位表示数值补码表示法的规则正数和零:补码与原码相同,范围是 0000 0000(0)到 0111 1111(127),共 128 个值负数:补码 = 原码取反 + 1,范围是 1000 0001(-127)到 1111 1111(-1),占 127 个值关键点:1000 0000 被定义为 -128二进制(补码) 十进制值1000 0000 -1281000 0001 -1271000 0010 -126... …1111 1110 -21111 1111 -10000 0000 00000 0001 10000 0010 2... …0111 1110 1260111 1111 127为何不是 -127 到 127?若范围设为 -127 到 127(含 0),仅能表示 127 + 128 = 255 127 + 128 = 255127+128=255 个值,无法覆盖全部 256 种组合补码的连续性要求:将 1000 0000 分配给 -128 后:数值序列形成闭环:127(0111 1111)+1 溢出为 -128(1000 0000),实现连续循环若不这样设计,会浪费一个二进制组合(1000 0000),且破坏数值连续性,-127(1000 0001)-1正好是-128(1000 0000)编程语言中的实际表现Byte.MAX_VALUE = 127,Byte.MIN_VALUE = -128赋值超出范围(如 byte b = 128;)会触发编译错误,如127 + 1 = -128(因 0111 1111 + 1 = 1000 0000)三、 眼见为实:Java代码验证补码Integer.toBinaryString(int i) 方法会返回一个整数补码表示的字符串(省略前导零,负数显示完整的32位,int占4个字节)public class ComplementDemo { public static void main(String[] args) { int positive = 5; int negative = -5; // 打印正数5的二进制(补码,省略前导零) System.out.println(Integer.toBinaryString(positive)); // 输出: 101 // 打印负数-5的二进制(32位完整补码) System.out.println(Integer.toBinaryString(negative)); // 输出: 1111 1111 1111 1111 1111 1111 1111 1011 }}解读负数输出:11111111111111111111111111111011 就是 -5 的 32 位补码它是由 +5 (00000000_00000000_00000000_00000101) 按位取反 (11111111_11111111_11111111_11111010)再加 1 得到的 (11111111_11111111_11111111_11111011)总结原码:直观但有双零,运算复杂(历史概念)反码:试图改进运算,仍有双零和循环进位问题(历史概念)补码 (Java的选择):统一零表示,完美支持 A - B = A + (-B),硬件实现高效简单,是现代计算机整数表示的标准Java实践:byte, short, int, long 均用补码。Integer.toBinaryString() 可查看补码形式————————————————原文链接:https://blog.csdn.net/qq_35512802/article/details/148684625
-
在排查 JVM 内存泄漏问题时,需要综合利用监控工具、日志分析、堆转储(Heap Dump)分析和代码审查,逐步缩小问题范围,定位泄漏源。下面介绍一种系统化的排查流程及常用方法:1. 监控与日志分析监控内存使用趋势利用 Prometheus、Grafana、JConsole、VisualVM 等工具,监控堆内存、非堆内存、GC 次数和停顿时间。如果发现堆内存使用率持续上升,或者 GC 次数频繁增加(尤其是 Full GC 频率增加),可能存在内存泄漏。开启 GC 日志配置 JVM 参数(例如:-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log)记录垃圾回收日志。分析 GC 日志,观察垃圾回收前后堆内存的变化,是否存在“GC overhead limit exceeded”异常等。2. Heap Dump 分析采集 Heap Dump当发现内存使用异常时,可以使用 jmap -dump 或者通过监控工具(如 JVisualVM、Java Mission Control)生成堆转储文件(.hprof 文件)。可以在 OOM(OutOfMemoryError)发生时自动生成 Heap Dump(通过 -XX:+HeapDumpOnOutOfMemoryError)。使用内存分析工具利用 Eclipse Memory Analyzer Tool(MAT)、VisualVM 的插件或 JProfiler 分析 Heap Dump。主要分析点:找出占用内存最多的对象及其数量(疑似泄漏点)。查看对象的引用链(Reference Chain),定位哪些引用没有及时释放,比如长生命周期的静态变量、集合(List、Map)等未清理的容器。分析类加载器问题,排查是否存在因为类未卸载导致的内存泄漏(尤其在动态部署的场景)。3. 代码审查与排查工具代码审查检查是否存在未关闭的资源(如数据库连接、IO 流)、缓存未清理、静态集合持续累积数据等常见内存泄漏原因。注意循环引用、事件监听器未注销、定时任务中使用匿名内部类等情况。使用专业工具使用 JProfiler、YourKit 等商业分析工具,进行实时内存监控和泄漏检测,观察对象分配和垃圾回收情况。断点调试与日志记录对可疑模块加入内存监控日志、统计特定对象的创建与释放,辅助排查内存泄漏的细节。4. 调优与验证调整 GC 参数根据监控结果和 Heap Dump 分析结果,调优垃圾回收参数(如堆大小、年轻代比例、GC 算法),改善内存回收效果,并观察是否能缓解内存增长问题。持续集成和测试编写压力测试和长时间运行测试,验证问题修复情况。采用内存泄漏检测工具(如 Java 的 LeakCanary 或 Apache JMeter 配合内存监控)持续监控内存使用。总结排查 JVM 内存泄漏问题的基本步骤是:监控与日志分析:观察内存使用趋势和 GC 日志,确认异常现象。Heap Dump 分析:采集堆转储,利用 MAT 或其他工具查找内存占用最多的对象和引用链。代码审查与调试:检查资源释放、缓存管理和事件监听等代码,结合专业工具进行实时监控。调优与验证:调整 JVM 参数,优化代码后通过测试验证内存泄漏是否得到解决。通过这种系统化的排查方法,可以较快定位内存泄漏的根本原因,并在此基础上进行相应的优化和修复。————————————————原文链接:https://blog.csdn.net/longgelaile/article/details/145513376
-
一、static(静态)static表示静态,是Java中的一个修饰符,可以修饰成员方法,成员变量。1.static 静态变量被static修饰的成员变量,叫做静态变量。特点:被该类的所有对象共享不属于对象,属于类随着类的加载而加载,优于对象存在调用方式:类名调用(推荐)对象名调用代码展示需求:写一个JavaBean类来描述这个班级的学生属性:姓名、年龄、性别行为:学习JAVA Bean 类package staticdemo;public class Student { //属性:姓名,年龄,性别 //新增:老师的姓名 private String name; private int age; private String gender; public static String teacherName; public Student() { } public Student(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } /** * 获取 * @return gender */ public String getGender() { return gender; } /** * 设置 * @param gender */ public void setGender(String gender) { this.gender = gender; } //行为 public void study(){ System.out.println(name+"正在学习"); } public void show(){ System.out.println(name+","+age+","+gender+","+teacherName); }}测试类package staticdemo;public class StudentTest { public static void main(String[] args) { //1.创建第一个学生对象 Student s1 = new Student(); s1.setName("张三"); s1.setAge(20); s1.setGender("男"); //公共类,但是s2没有创建对象,所以无法访问teacherName,为null //public String teacherName; //于是我们想了个方法,用static修饰teacherName,但是这样就变成了静态属性,所有对象共享 //public static String teacherName; s1.teacherName = "王老师"; //还可以用类名.属性名来访问 //Student.teacherName = "王老师"; s1.study(); s1.show(); //2.创建第二个学生对象 Student s2 = new Student(); s2.setName("李四"); s2.setAge(21); s2.setGender("女"); s2.study(); s2.show(); }}内存图栈内存方法调用时会在栈内存中创建栈帧。这里main方法首先入栈 ,在main方法执行过程中:执行 Student.teacherName = "阿玮老师"; ,这一步只是对静态变量赋值,在栈内存中记录这个操作。执行 Student s1 = new Student(); 时,在栈内存为引用变量 s1 分配空间,存放指向堆内存中 Student 对象的地址(假设为 0x0011 ) 。执行 s1.name = "张三"; 和 s1.age = 23; ,是通过 s1 引用操作堆内存中对象的实例变量。执行 s1.show(); 时,show 方法的栈帧入栈,在栈帧中记录方法内的局部变量(这里无额外局部变量)以及要操作的对象属性(通过 s1 找到堆内存对象属性)。执行 Student s2 = new Student(); ,在栈内存为引用变量 s2 分配空间,存放指向堆内存中另一个 Student 对象的地址(假设为 0x0022 ) 。执行 s2.show(); 时,show 方法栈帧再次入栈,通过 s2 引用操作其对应的堆内存对象属性。堆内存当执行new Student()时,在堆内存创建Student对象实例。第一个 Student 对象(对应 s1 ),在堆内存中分配空间存储实例变量 name 值为 “张三” ,age 值为 23 。第二个 Student 对象(对应 s2 ),在堆内存中分配空间存储实例变量 name 初始值 null (字符串默认初始值) ,age 初始值 0 (整数默认初始值) 。静态变量 teacherName 存储在堆内存的静态存储位置(静态区),值为 “阿玮老师” ,所有 Student 类的对象共享这个静态变量。注意:静态变量随类的出现而出现,优于变量。2.static 静态方法被static修饰的成员方法,叫做静态方法。特点:多用在测试类和工具类中Javabean类中很少会用调用方式:类名调用(推荐)对象名调用工具类:帮助我们做一些事情的,但是不描述任何事物的类Javabean类:用来描述一类事物的类。比如:Student、Teather、Dog等测试类:用来检查其他类是否书写正确,带有main方法的类,是程序的入口遵守的规范:类名见名知意私有化构造方法方法定义为静态的练习:第一题:需求:在实际开发中,经常会遇到一些数组使用的工具类请按照如下要求编写一个数组的工具类:ArrayUtil工具类:package sta02;public class ArrayUtil { //私有构造方法,防止外部实例化 private ArrayUtil() {} public static String printArray(int[] arr) { StringBuilder sb = new StringBuilder(); sb.append( "["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]); if (i < arr.length - 1) { sb.append(", "); }else { sb.append("]"); } } return sb.toString(); } public static double getArray(double[] arr) { double sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; //累加数组元素 } return sum/arr.length; }}测试类package sta02;public class Testdemo { public static void main(String[] args) { //测试printArray方法 int[] arr = {1, 2, 3, 4, 5}; String result = ArrayUtil.printArray(arr); System.out.println(result); //测试getArray方法 double[] arr2 = {1.0, 2.0, 3.0, 4.0, 5.0}; double average = ArrayUtil.getArray(arr2); System.out.println(average); }}第二题:需求:定义一个集合,用于存储3个学生对象学生类的属性:name、age、gender定义一个工具类,用于获取集合中最大学生的年龄JavaBean类:package sat03;public class Student { private String name; private int age; private String gender; public Student() { } public Student(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } /** * 获取 * @return gender */ public String getGender() { return gender; } /** * 设置 * @param gender */ public void setGender(String gender) { this.gender = gender; }}方法类:package sat03;import java.util.ArrayList;public class StudentUtil { //私有构造方法,防止外部实例化 private StudentUtil() {} //静态方法 public static int getMaxScore(ArrayList<Student> list ) { //1.定义一个参照物 int maxAge = list.get(0).getAge(); //2.遍历集合 for (int i = 1; i < list.size(); i++) { if (list.get(i).getAge() > maxAge) { maxAge = list.get(i).getAge(); } } return maxAge; }}测试类:package sat03;import java.util.ArrayList;public class testmax { public static void main(String[] args) { //1.创建一个集合来存储 ArrayList<Student> list = new ArrayList<Student>(); //2.创建3个学生对象 Student s1 = new Student("Zhangsan", 20, "男"); Student s2 = new Student("lisi", 23, "男"); Student s3 = new Student("wangwu", 25, "女"); //3.将学生对象添加到集合中 list.add(s1); list.add(s2); list.add(s3); //4.调用方法 int max=StudentUtil.getMaxScore(list); System.out.println(max); }}3.static注意事项静态方法只能访问静态变量和静态方法非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法静态方法中是没有this关键字的总结:静态方法中,只能访问静态非静态方法可以访问所有静态方法中没有this关键字4.重新认识main方法public class HelloWorld{ public static void main (String[] args){ System.out.println("HelloWorld"); }}public : 被JVM调用,访问权限足够大static :被JVM调用,不用创建对象,直接类名访问 因为main方法是静态的,所以测试类中其他方法也是需要是静态的void : 被JVM调用,不需要给JVM返回值main : 一个通用的名称,虽然不是关键字,但是被JVM识别String[] args :以前用于接受键盘录入数据的,现在没用二、继承面向对象三大特征:封装、继承、多态封装:对象代表什么,就得封装对应的数据,并提供数据对应的行为。我们发现在Student类与Teacher类中有重复的元素,于是为了使程序更加便捷便出现了”继承“1.继承概述java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系public class Student extends Person{}1Student称为子类(派生类),Person称为父类(基类或超类)优点:可以把多个子类中重复的代码抽取到父类中,提高代码的复用性子类可以在父类的基础上,增加其他的功能,使子类更强大什么时候用继承?当类与类之间,存在相同(共性)的内容,并满足子类是父类中的一种,就可以考虑使用继承,来优化代码2.继承的特点java中只支持单继承,不支持多继承,但支持多层继承单继承:一个子类只能继承一个直接父类不支持多继承:子类不能同时继承多个父类多层继承:子类A继承父类B,父类B可以继承父类CC是A的间接父类每一个类都直接或者间接的继承于Object练习:核心点:共性内容抽取,子类是父类中的一种写代码是从父类开始写,最后写子类JAVABean类package st5;public class Animal { public void eat() { System.out.println("我会吃饭"); } public void water (){ System.out.println("我会喝水"); }}package st5;public class Cat extends Animal { public void mice() { System.out.println("我会抓老鼠"); }}package st5;public class Dog extends Animal { public void lookhome() { System.out.println("我会看家"); }}package st5;public class Ragdoll extends Cat{}package st5;public class Lihua extends Cat{}package st5;public class Husky extends Dog{ public void breakhome() { System.out.println("我会拆家"); }}package st5;public class Teddy extends Dog{ public void Ceng(){ System.out.println("我喜欢蹭一蹭"); }}测试类:package st5;public class Test { public static void main(String[] args) { //创建对象并调用方法 //创建布偶猫的对象 Ragdoll rd = new Ragdoll(); System.out.println("我是布偶猫"); rd.mice(); rd.water(); rd.eat(); System.out.println("-------------------"); //创建狸花猫的对象 Lihua lh = new Lihua(); System.out.println("我是狸花猫"); lh.mice(); lh.water(); lh.eat(); System.out.println("-------------------"); //创建泰迪的对象 Teddy td = new Teddy(); System.out.println("我是泰迪"); td.lookhome(); td.water(); td.eat(); td.Ceng(); System.out.println("-------------------"); //创建哈士奇的对象 Husky hs = new Husky(); System.out.println("我是哈士奇"); hs.lookhome(); hs.water(); hs.eat(); hs.breakhome(); }}试运行:注意:子类只能访问父类中非私有的成员3.子类到底能继承父类中的哪些内容构造方法 非私有 不能 private 不能成员变量 非私有 能 private 能 但不能直接用成员方法 虚方法表 能 否则 不能虚方法表:就是经常要用的方法,什么叫虚方法表呢?非private 非static非final4.继承中访问特点继承中:成员变量的访问特点public class Fu{ String name = "Fu";}public class Zi extends Fu{ String name = "Zi"; public void ziShow(){ String name = "ziShow"; System.out.println(name); }}//就近原则:谁离我近,我就用谁//完整版就近原则:先在局部位置找,本类成员位置找,父类成员位置找,逐级往上//run:ziShow如果出现了重名的成员变量怎么找:System.out.println(name);//从局部位置开始往上找System.out.println(this.name);//从本类成员位置开始往上找System.out.println(super.name);//从父类成员位置开始往上找public class Test{ public static void main (String [] args){ Zi z = new Zi(); z.ziShow(); }}public class Fu{ String name = "Fu";}public class Zi extends Fu{ String name = "Zi"; public void ziShow(){ String name = "ziShow"; System.out.println(name);//ziShow System.out.println(this.name);//Zi System.out.println(super.name);//Fu }}继承中:成员方法的访问特点直接调用满足就近原则:谁离我近,我就用谁super调用,直接访问父类package jicehng;public class test { public static void main(String[] args) { //创建一个对象 Student s = new Student(); s.lunch(); /* 吃面条 咖啡 吃米饭 喝水 */ }}class Person { public void eat(){ System.out.println("吃米饭"); } public void water(){ System.out.println("喝水"); }}class Student extends Person { public void lunch(){ this.eat();//就近读取子类吃面条 this.water();//就近读取子类咖啡 super.eat();//调用父类吃米饭 super.water();//调用父类喝水 } public void eat(){ System.out.println("吃面条"); } public void water(){ System.out.println("咖啡"); }}方法的重写:当父类的方法不能满足子类现在的需求时,需要进行方法重写书写格式:在继承体系中 ,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法@Override重写体系@Override是放在重写后的方法上,校验子类重写时语法是否正确加上注解后如果有红色波浪线,表示语法错误建议重写方法都加@Override注解,代码安全,优雅!方法重写注意事项和要求:重写方法的名称、形参列表必须于父类中的一致子类重写父类方法时,访问权限子类必须大于等于父类(暂时了解:空着不写<protected<public)子类重写父类方法时,返回值类型必须小于等于父类建议:重写的方法尽量和父类保持一致只有被添加到虚方法表中的方法才能被重写JAVABean类:package jice;public class Dog { public void eat() { System.out.println("Dog is eating."); } public void drink() { System.out.println("Dog is drinking."); } public void lookhome() { System.out.println("Dog is lookhome."); }}package jice;public class hashiqi extends Dog { public void breakhome() { System.out.println("hashiqi is breakhome."); }}package jice;public class shapi extends Dog{ @Override public void eat() { super.eat();// 调用父类的eat方法 System.out.println("shapi is eating gouliang."); }}package jice;public class chinesedog extends Dog{ @Override public void eat() { super.eat();// 调用父类的eat方法 System.out.println("Chinesedog is eating chinesefood."); }}测试类package jice;public class test { public static void main(String[] args) { hashiqi hashiqi = new hashiqi(); hashiqi.eat(); hashiqi.drink(); hashiqi.lookhome(); shapi shapi = new shapi(); shapi.eat(); shapi.drink(); shapi.lookhome(); chinesedog chinesedog = new chinesedog(); chinesedog.eat(); chinesedog.drink(); }}继承中:构造方法的访问特点父类中的构造方法不会被子类继承子类中所有的构造方法默认先访问父类中的无参构造,再执行自己为什么?子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化怎么调用父类构造方法的?子类构造方法的第一行语句默认都是:super(),不写也存在,且必须在第一行如果想调用父类有参构造,必须手动写super进行调用this、super使用总结this:理解为一个变量,表示当前发给发调用者的地址值;super:代表父类存储空间关键字 访问成员变量 访问成员方法 访问构造方法this this.成员变量 访问本类成员变量 this.成员方法(…) 访问本类成员方法 this(…) 访问本类构造方法super super.成员变量 访问父类成员变量 super.成员方法(…) 访问父类成员方法 super(…) 访问父类构造方法 chinesedog chinesedog = new chinesedog(); chinesedog.eat(); chinesedog.drink();}}### 继承中:构造方法的访问特点- 父类中的构造方法不会被子类继承- 子类中所有的构造方法默认先访问父类中的无参构造,再执行自己 为什么? - 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。 - 子类初始化之前,一定要调用父类构造方法先完成父类数据空间的初始化 怎么调用父类构造方法的? - 子类构造方法的第一行语句默认都是:`super()`,不写也存在,且必须在第一行 - 如果想调用父类有参构造,必须手动写`super`进行调用### this、super使用总结this:理解为一个变量,表示当前发给发调用者的地址值;super:代表父类存储空间| 关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 || ------ | -------------------------------- | -------------------------------------- | ---------------------------- || this | this.成员变量 访问本类成员变量 | this.成员方法(...) 访问本类成员方法 | this(...) 访问本类构造方法 || super | super.成员变量 访问父类成员变量 | super.成员方法(...) 访问父类成员方法 | super(...) 访问父类构造方法 |————————————————原文链接:https://blog.csdn.net/2401_87533975/article/details/148388640
-
1. 踩坑经历最近做了个需求,需要调用第三方接口获取数据,在联调时一直失败,代码抛出javax.net.ssl.SSLHandshakeException异常,具体错误信息如下所示:javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target2.原因分析因为调用第三方接口的代码是复用项目中原有的工具类(基于httpclient封装),所以在确认完传参没问题后,第一时间排除了编码问题。然后开始怀疑第三方提供的接口地址(因为竟然是IP+端口访问),在和第三方确认没有域名访问后,在浏览器里输入第三方的接口地址,发现证书有问题:又使用Postman调用第三方接口,也是失败,提示自签名证书:通过以上分析,可以发现出现该问题的根本原因是Java客户端不信任目标服务器的SSL证书,比如这个第三方使用的自签名证书。3.解决方案解决方案一般有2种,第1种方案是将服务器证书导入Java信任库,第2种方案是绕过SSL验证,这里采用第2种方案。首先,新建HttpClient工具类:import org.apache.http.conn.ssl.NoopHostnameVerifier;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import javax.net.ssl.SSLContext;import javax.net.ssl.TrustManager;import javax.net.ssl.X509TrustManager;import java.security.KeyManagementException;import java.security.NoSuchAlgorithmException;import java.security.cert.X509Certificate;public class HttpClientUtils { public static CloseableHttpClient createIgnoreCertClient() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, new TrustManager[]{new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { } }}, new java.security.SecureRandom()); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build(); }}然后将原来声明httpClient的代码改为如下所示:CloseableHttpClient httpClient = HttpClientUtils.createIgnoreCertClient();注意事项:确保项目中引入了httpclient依赖:<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version></dependency>转载自https://www.cnblogs.com/zwwhnly/p/18795523
-
1. 前言最近在自测接口时,发现一个问题:字段类型定义的是Date,但接口返回值里却是时间戳(1744959978674),而不是预期的2025-04-18 15:06:18。private Date useTime;{ "code": "200", "message": "", "result": [ { "id": 93817601, "useTime": 1744959978674 } ]}这种返回值,无法快速的知道是哪个时间,如果想知道时间对不对,还得找一个时间戳转换工具做下转换才能确定,非常不方便。因此想让接口直接返回预期的2025-04-18 15:06:18格式。刚开始,在字段上添加了@JsonFormat注解,发现没生效,返回的还是时间戳:import com.fasterxml.jackson.annotation.JsonFormat;@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")private Date useTime;然后,改成了@JSONField注解,发现生效了,达到了预期的结果:import com.alibaba.fastjson.annotation.JSONField;@JSONField(format = "yyyy-MM-dd HH:mm:ss")private Date useTime;{ "code": "200", "message": "", "result": [ { "id": 93817601, "useTime": "2025-04-18 15:06:18" } ]}那么问题来了,为啥@JSONField生效,@JsonFormat不生效?2. 原因分析默认情况下,Spring Boot使用的JSON消息转换器是Jackson的MappingJackson2HttpMessageConverter,核心依赖为:<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.11.3</version></dependency>现在使用Jackson的@JsonFormat注解不生效,说明Spring Boot没有使用默认的MappingJackson2HttpMessageConverter。使用fastjson的@JSONField注解生效了,说明Spring Boot使用的是fastjson下的JSON消息转换器,也就是FastJsonHttpMessageConverter,依赖为:<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version></dependency>那么怎么找到代码在哪配置的呢?第一步,先在项目中全局搜索FastJsonHttpMessageConverter(Windows快捷键:Ctrl+Shift+F),不过大概率是搜索不到,因为公司里的项目一般都继承自公司公共的xxx-spring-boot-starter。第二步,连按2次Shift键搜索FastJsonHttpMessageConverter,然后查找该类的引用或者子类(子类很可能是公司底层框架中写的)。然后,很可能会找到类似下面的代码:@Configurationpublic class FastJsonMessageConverterConfig { @Bean public HttpMessageConverters customConverters() { FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); return new HttpMessageConverters(new HttpMessageConverter[]{fastJsonHttpMessageConverter}); }}以上代码显式注册了一个FastJsonHttpMessageConverter,并通过HttpMessageConverters覆盖了默认的HTTP 消息转换器(Jackson的MappingJackson2HttpMessageConverter),所以Spring MVC将只使用fastjson处理JSON序列化/反序列化。这也是@JSONField生效,@JsonFormat不生效的根本原因。3. 默认行为及全局配置fastjson 1.2.36及以上版本,默认将日期序列化为时间戳(如1744959978674),如果要默认将日期序列化为yyyy-MM-dd HH:mm:ss(如2025-04-18 15:06:18),需要启用WriteDateUseDateFormat特性:@Configurationpublic class FastJsonMessageConverterConfig { @Bean public HttpMessageConverters customConverters() { FastJsonConfig fastJsonConfig = new FastJsonConfig(); // 启用日期格式化特性(禁用时间戳) fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat); // 设置日期格式(不指定时,默认为yyyy-MM-dd HH:mm:ss,但即使与默认值一致,也建议明确指定) fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); return new HttpMessageConverters(new HttpMessageConverter[]{fastJsonHttpMessageConverter}); }}如果某个日期字段有特殊序列化要求,可以使用@JSONField注解灵活配置(该注解会覆盖全局配置):import com.alibaba.fastjson.annotation.JSONField;@JSONField(format = "yyyy-MM-dd")private Date anotherUseTime;注意事项:修改全局配置需慎重,如果一个老项目,原来日期类型返回的都是时间戳,突然全部改为返回字符串,可能会造成调用方报错。转载自https://www.cnblogs.com/zwwhnly/p/18838062
-
1. 踩坑经历假设有这样一个业务场景,需要对各个城市的订单量排序,排序规则为:先根据订单量倒序排列,再根据城市名称正序排列。示例代码:import lombok.Getter;import lombok.Setter;import lombok.ToString;@Getter@Setter@ToStringpublic class OrderStatisticsInfo { private String cityName; private Integer orderCount; public OrderStatisticsInfo(String cityName, Integer orderCount) { this.cityName = cityName; this.orderCount = orderCount; }}public static void main(String[] args) { List<OrderStatisticsInfo> orderStatisticsInfoList = Arrays.asList( new OrderStatisticsInfo("上海", 1000), new OrderStatisticsInfo("北京", 1000), new OrderStatisticsInfo("成都", 700), new OrderStatisticsInfo("常州", 700), new OrderStatisticsInfo("广州", 900), new OrderStatisticsInfo("深圳", 800) ); orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder()) .thenComparing(OrderStatisticsInfo::getCityName)); orderStatisticsInfoList.forEach(System.out::println);}预期结果:北京 1000上海 1000广州 900深圳 800常州 700成都 700实际结果:OrderStatisticsInfo(cityName=上海, orderCount=1000)OrderStatisticsInfo(cityName=北京, orderCount=1000)OrderStatisticsInfo(cityName=广州, orderCount=900)OrderStatisticsInfo(cityName=深圳, orderCount=800)OrderStatisticsInfo(cityName=常州, orderCount=700)OrderStatisticsInfo(cityName=成都, orderCount=700)从以上结果可以看出,根据订单量倒序排列没啥问题,但根据城市名称正序排列不符合预期:上海竟然排到了北京的前面,但常州与成都的顺序又是对的。2. 原因分析Comparator.comparing对字符串类型进行排序时,默认使用的是字符串的自然排序,即String的compareTo方法,该方法是基于Unicode编码值进行比较的,未考虑语言特定的字符顺序(如中文拼音)。先看下String的compareTo方法的源码:public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2;}以上海与北京的比较为例,先比较第一个字符,也就是字符上和字符北,字符上对应的Unicode编码值是19978,因此c1 = 19978,字符北对应的Unicode编码值是21271,因此c2 = 21271,因为c1 != c2,所以返回值为-1293,也就是说上海小于北京(要排在北京的前面),不符合预期。以常州与成都的比较为例,先比较第一个字符,也就是字符常和字符成,字符常对应的Unicode编码值是24120,因此c1 = 24120,字符成对应的Unicode编码值是25104,因此c2 = 25104,因为c1 != c2,所以返回值为-984,也就是说常州小于成都(要排在成都的前面),符合预期。可以通过Character.codePointAt方法获取字符的Unicode编码值:// 输出:19978System.out.println(Character.codePointAt("上海", 0));// 输出:21271System.out.println(Character.codePointAt("北京", 0));// 输出:24120System.out.println(Character.codePointAt("常州", 0));// 输出:25104System.out.println(Character.codePointAt("成都", 0));3. 解决方案Java提供了本地化的排序规则,可以按特定语言规则排序(如中文拼音),代码如下所示:orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder()) .thenComparing(OrderStatisticsInfo::getCityName, Collator.getInstance(Locale.CHINA)));orderStatisticsInfoList.forEach(System.out::println);此时的输出结果为:OrderStatisticsInfo(cityName=北京, orderCount=1000)OrderStatisticsInfo(cityName=上海, orderCount=1000)OrderStatisticsInfo(cityName=广州, orderCount=900)OrderStatisticsInfo(cityName=深圳, orderCount=800)OrderStatisticsInfo(cityName=常州, orderCount=700)OrderStatisticsInfo(cityName=成都, orderCount=700)可以看到,北京排到了上海的前面,符合预期。上述代码指定了Collator.getInstance(Locale.CHINA),在排序比较时不再执行String的compareTo方法,而是执行Collator的compare方法,实际上是RuleBasedCollator的compare方法。可以执行以下代码单独看下上海与北京的比较结果:Collator collator = Collator.getInstance(Locale.CHINA);// 输出:1,代表上海大于北京,也就是要排在北京的后面System.out.println(collator.compare("上海", "北京"));转载自https://www.cnblogs.com/zwwhnly/p/18846441
-
1. 前言在我接触过的大部分Java项目中,经常看到使用@Autowired注解进行字段注入:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService;}在IDEA中,以上代码@Autowired注解下会显示波浪线,鼠标悬停后提示:Field injection is not recommended,翻译过来就是不建议使用字段注入。关于该提示问题,有直接修改IDEA设置关闭该提示的,有替换为使用@Resource注解的,但这都不是该问题的本质。该问题的本质是Spring官方推荐使用构造器注入,IDEA作为一款智能化的IDE,针对该项进行了检测并给以提示。所以该提示背后的本质问题是:为什么Spring官方推荐构造器注入而不是字段注入?2. 推荐构造器注入的理由相比字段注入,构造器注入有以下几个优点:支持不可变性依赖明确单元测试友好循环依赖检测前置,提前暴露问题2.1 支持不可变性构造器注入允许将依赖字段声明为final,确保对象一旦创建,其依赖关系不再被修改。字段注入无法使用final,依赖可能在对象生命周期中被意外修改,破坏状态一致性。构造器注入示例:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService; @Autowired public OrderService(PaymentService paymentService, InventoryService inventoryService) { this.paymentService = paymentService; this.inventoryService = inventoryService; }}说明:如果Spring版本是4.3或者更高版本且只有一个构造器,构造器上的@Autowired注解可以省略。2.2 依赖明确构造器注入通过在类的构造函数中显式声明依赖,并且强制要求在创建对象时必须提供所有必须的依赖项,通过构造函数参数,使用者对该类的依赖一目了然。字段注入通过在类的字段上直接使用@Autowired注解注入依赖,依赖关系隐藏在类的内部,使用者无法直接看到该类的依赖。2.3 单元测试友好构造器注入允许直接通过new创建对象,无需依赖Spring容器或反射,降低了测试复杂度。字段注入需要依赖Spring容器或反射,增加了测试复杂度。2.4 循环依赖检测前置,提前暴露问题构造器注入在应用启动时直接暴露循环依赖,强制开发者通过设计解决问题。字段注入在应用启动时不会暴露循环依赖,直到实际调用时才可能暴露问题,增加调试难度。示例:假设项目中有以下两个Service存在循环依赖:import org.springframework.stereotype.Service;@Servicepublic class OrderService { private final PaymentService paymentService; public OrderService(PaymentService paymentService) { this.paymentService = paymentService; }}import org.springframework.stereotype.Service;@Servicepublic class PaymentService { private final OrderService orderService; public PaymentService(OrderService orderService) { this.orderService = orderService; }}此时启动项目会报错,抛出org.springframework.beans.factory.BeanCurrentlyInCreationException异常,大致的异常信息如下所示:Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?将以上两个Service修改为字段注入:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { @Autowired private PaymentService paymentService;}import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class PaymentService { @Autowired private OrderService orderService;}此时启动项目不会报错,可以启动成功。3. @RequiredArgsConstructor注解的使用及原理为了避免样板化代码或者为了简化代码,有的项目中可能会使用@RequiredArgsConstructor注解来代替显式的构造方法:import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;@RequiredArgsConstructor@Servicepublic class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService;}接下来简单讲解下@RequiredArgsConstructor注解的原理。@RequiredArgsConstructor注解用于在编译时自动生成包含特定字段的构造方法。字段筛选逻辑如下所示:被final修饰的未显式初始化的非静态字段被@NonNull注解标记的未显式初始化的非静态字段示例:import lombok.NonNull;import lombok.RequiredArgsConstructor;@RequiredArgsConstructorpublic class User { private final String name; @NonNull private Integer age; private final String address = ""; private String email; private static String city; @NonNull private String sex = "男";}以上代码在编译时自动生成的构造方法如下所示:public User(String name, @NonNull Integer age) { if (age == null) { throw new NullPointerException("age is marked non-null but is null"); } else { this.name = name; this.age = age; }}从生成的构造方法可以看出:1)如果字段被lombok.NonNull注解标记,在生成的构造方法内会做null值检查。2)address字段虽然被final修饰,但因为已初始化,所以未包含在构造方法中。3)email字段既没被final修饰,也没被lombok.NonNull注解标记,所以未包含在构造方法中。4)city字段是静态字段,所以未包含在构造方法中。5)sex字段虽然被lombok.NonNull注解标记,但因为已初始化,所以未包含在构造方法中。4. 总结@Autowired注解在IDEA中提示:Field injection is not recommended,其背后的本质问题是:Spring官方推荐构造器注入而不是字段注入。而Spring官方推荐构造器注入,是因为相比字段注入,构造器注入有以下几个优点:支持不可变性依赖明确单元测试友好循环依赖检测前置,提前暴露问题使用构造器注入时,为了避免样板化代码或者为了简化代码,可以使用@RequiredArgsConstructor注解来代替显式的构造方法,因为@RequiredArgsConstructor注解可以在编译时自动生成包含特定字段的构造方法。至于项目中要不要使用构造器注入,使用显式的构造方法还是使用@RequiredArgsConstructor注解来简化代码,可以根据个人喜好及团队规范自行决定。
-
@RestController 和 @Controller 是Spring框架中用于定义控制器(Controller)的两个非常重要的注解,它们都用于处理HTTP请求,但它们之间存在一些关键的区别。1.@Controller@Controller 注解是Spring MVC的一部分,用于定义一个控制器类。当Spring MVC接收到一个请求时,它会根据请求的URL映射到相应的控制器类上。@Controller 注解的类中的方法返回的是字符串(通常是视图名)或ModelAndView对象,这些返回值会用于渲染视图(通常是JSP页面)。@Controller 注解通常与@RequestMapping或它的变体(如@GetMapping, @PostMapping等)一起使用来定义请求处理的方法。如果你希望将MVC模式中的“控制器”部分与“视图”部分分离,并且希望由Spring MVC来管理视图的渲染,那么你应该使用@Controller。2.@RestController@RestController 是Spring 4引入的一个方便的注解,它实际上是@Controller和@ResponseBody的组合注解。它意味着,当控制器中的方法返回一个对象时,Spring会自动将这个对象转换为JSON或XML(取决于请求的Accept头部)并写入HTTP响应体中。@RestController更适合构建RESTful Web服务,因为它简化了返回JSON或XML数据的过程。使用@RestController注解的控制器类中的方法通常会返回一个对象或对象列表,而不是视图名或ModelAndView对象。@RestController也常与@RequestMapping或它的变体一起使用来定义请求处理的方法。3.注意如果你正在构建一个需要渲染视图的Web应用(如基于JSP的Web应用),那么你应该使用@Controller。如果你正在构建一个RESTful Web服务,希望直接返回JSON或XML等数据格式,那么@RestController将是更好的选择。@RestController简化了返回数据的过程,因为它自动将返回的对象转换为JSON或XML,而@Controller则需要额外的步骤来渲染视图。上代码和效果图@Controller注解@Controller@RequestMapping("/user")public class UserController { @Autowired private UserService userService; @RequestMapping("/init") public String init() { return "login"; }}@RestController注解@RestController@RequestMapping("/user")public class UserController { @Autowired private UserService userService; @RequestMapping("/init") public String init() { return "login"; }}转载自https://www.cnblogs.com/sailCoding/p/18420217
-
在 Java 中操作 Map 时,高效遍历和安全删除数据可以通过以下方式实现:一、遍历 Map 的 4 种高效方式1. 传统迭代器(Iterator)Map<String, Integer> map = new HashMap<>();map.put("key1", 5);map.put("key2", 3);Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + ": " + value);}2. Java 8+ forEach + Lambdamap.forEach((key, value) -> { System.out.println(key + ": " + value);});3. 增强 for 循环(遍历 EntrySet)for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); // ...}4. Stream API(Java 8+)map.entrySet().stream() .filter(entry -> entry.getValue() > 3) // 过滤条件 .forEach(entry -> { System.out.println(entry.getKey(); });二、安全删除 Map 中的数据1. 遍历时删除Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); if (entry.getValue() < 3) { iterator.remove(); }}2. Java 8+ removeIfmap.entrySet().removeIf(entry -> entry.getValue() < 3);3. 直接删除(已知 Key)map.remove("key1");三、关键注意事项避免并发修改异常遍历时直接调用 map.remove(key) 会导致 ConcurrentModificationException,必须使用 Iterator.remove() 或 removeIf。性能优化对 HashMap,优先遍历 entrySet()(直接获取 Key-Value)。对只读操作,forEach 和 Stream 性能接近;需过滤/删除时优先用 removeIf。并发场景多线程环境下使用 ConcurrentHashMap 并结合 Iterator.remove() 或原子操作。四、完整示例代码Map<String, Integer> map = new HashMap<>(Map.of( "key1", 5, "key2", 3, "key3", 2));// 遍历并删除 value < 3map.entrySet().removeIf(entry -> entry.getValue() < 3);// 输出结果:{key1=5, key2=3}System.out.println(map);通过上述方法,可以高效且安全地操作 Java 中的 Map 数据结构。转载自https://www.cnblogs.com/sailCoding/p/18933466
-
在Java的Stream API中,anyMatch和allMatch是终端操作(Terminal Operation),用于对流中的元素进行布尔值匹配检查。它们的核心区别在于匹配逻辑和短路行为:🚀1. anyMatch(Predicate)功能:检查流中是否至少有一个元素满足给定的断言条件。返回值:boolean(找到第一个匹配项时立即返回true,否则遍历完所有元素后返回false)。短路特性:具有短路能力,找到第一个匹配项后立即终止流处理。典型场景:// 检查是否有至少一个偶数List<Integer> numbers = List.of(1, 3, 5, 7, 9);boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // 返回false// 检查是否有权限(用户权限列表包含"ADMIN")List<String> permissions = List.of("READ", "WRITE");boolean isAdmin = permissions.stream().anyMatch("ADMIN"::equals); // 返回false🔒2. allMatch(Predicate)功能:检查流中是否所有元素都满足给定的断言条件。返回值:boolean(发现第一个不匹配项时立即返回false,否则遍历完所有元素后返回true)。短路特性:具有短路能力,发现第一个不匹配项后立即终止流处理。典型场景:// 检查是否所有数字都是偶数List<Integer> numbers = List.of(2, 4, 6, 8);boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // 返回true// 检查所有用户是否已激活(用户状态列表全为"ACTIVE")List<String> userStatuses = List.of("ACTIVE", "INACTIVE");boolean allActive = userStatuses.stream().allMatch("ACTIVE"::equals); // 返回false🔑核心对比特性anyMatchallMatch匹配逻辑至少一个元素匹配所有元素都必须匹配短路行为找到第一个匹配项后终止找到第一个不匹配项后终止典型返回值常见true(易满足)常见false(难满足)性能敏感场景适合快速失败的正向检查适合严格的验证场景🔐底层原理这两个方法都通过迭代流中的元素进行判断,但实现上有关键差异:anyMatch:一旦遇到true立即返回,后续元素不再处理。allMatch:一旦遇到false立即返回,后续元素不再处理。💎注意事项空流处理:对空流调用时,anyMatch返回false,allMatch返回true(符合逻辑学中的"存在量词"和"全称量词"定义)。与findAny的区别:anyMatch返回布尔值,而findAny返回Optional<T>元素。性能影响:在大数据量场景下,短路特性可以显著减少计算量。🔍代码示例: /** * 判断listA中的所有字符串是否包含listB中的所有字符串(子串匹配) * @param listA 待检查的主列表(长字符串) * @param listB 需要被包含的子串列表 * @return true 如果listB所有元素都是listA中某个元素的子串 */ public static boolean containsAllSubstrings(List<String> listA, List<String> listB) { return listB.stream() .filter(Objects::nonNull) // 过滤listB中的null元素 .allMatch(b -> listA.stream() .filter(Objects::nonNull) // 过滤listA中的null元素 .anyMatch(a -> a.contains(b)) // 检查子串存在性 ); } //注意:如果list中没有null的情况下可以不加.filter(Objects::nonNull)处理 public static void main(String[] args) { // 初始化测试数据(根据您提供的示例) List<String> listA = List.of( "READ.USER-KBN-KNJ", "READ.USER-ID.", // ...(此处省略中间元素) "READ.USER-GUIDE-MSG.", "READ.USER-ERR-MSG." ); List<String> listB = List.of( "KBN-KNJ", "USER-ID", // ...(此处省略中间元素) "GUIDE-MSG", "ERR-MSG" ); // 执行匹配检查 boolean result = containsAllSubstrings(listA, listB); System.out.println("listA是否包含listB的所有子串: " + result); }转载自https://www.cnblogs.com/sailCoding/p/18950812
-
一、Deflater实现(原始DEFLATE格式)1. 压缩方法 public static String compress(String rawData) { Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION, true); // nowrap=true try { deflater.setInput(rawData.getBytes(StandardCharsets.UTF_8)); deflater.finish(); try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; while (!deflater.finished()) { int compressedBytes = deflater.deflate(buffer); bos.write(buffer, 0, compressedBytes); } return Base64.getUrlEncoder().withoutPadding() .encodeToString(bos.toByteArray()); } } finally { deflater.end(); } }2. 解压方法 public static String decompress(String compressedData) { Inflater inflater = new Inflater(true); try { byte[] compressedBytes = Base64.getUrlDecoder().decode(compressedData); inflater.setInput(compressedBytes); try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; while (!inflater.finished()) { try { int decompressedBytes = inflater.inflate(buffer); bos.write(buffer, 0, decompressedBytes); } catch (DataFormatException e) { throw new RuntimeException("数据损坏", e); } } return bos.toString(StandardCharsets.UTF_8); } } finally { inflater.end(); } }二、GZIP实现(标准gzip格式)1. 压缩方法 public static String compress(String rawData) throws IOException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(bos)) { gzip.write(rawData.getBytes(StandardCharsets.UTF_8)); gzip.finish(); return Base64.getEncoder().encodeToString(bos.toByteArray()); } }2. 解压方法 public static String decompress(String compressedData) throws IOException { byte[] compressedBytes = Base64.getDecoder().decode(compressedData); try (ByteArrayInputStream bis = new ByteArrayInputStream(compressedBytes); GZIPInputStream gzip = new GZIPInputStream(bis); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buffer = new byte[8192]; int len; while ((len = gzip.read(buffer)) > 0) { bos.write(buffer, 0, len); } return bos.toString(StandardCharsets.UTF_8); } }三、优缺点对比特性Deflater (DEFLATE)GZIP压缩率≈95%(无头部,极限压缩)≈93%(含18字节头部)速度稍快(无头部开销)稍慢(需处理头部)兼容性需特殊解析器通用解压工具支持典型应用场景网络传输、内存敏感型应用文件存储、通用数据交换头部开销无18字节(含时间戳等元数据)校验和无有(CRC32)流式处理需手动管理缓冲区支持流式API四、选型建议优先选GZIP的场景:需要与其他系统交互时处理文件存储或日志压缩时需要内置校验和验证数据完整性时优先选Deflater的场景:追求极限压缩率时进行网络传输(尤其对延迟敏感时)需要自定义协议格式时通用原则:短文本(<1KB)建议直接存储中等长度文本(1KB-1MB)优先GZIP大文本(>1MB)可结合缓冲流处理实际测试显示,对于典型英文文本,Deflater比GZIP压缩率高约2-5%,但解压速度慢约10-15%。
-
前言递归(Recursion)在编程中是一个非常重要的概念。简单来说,递归指的是一个函数在其定义中直接或间接调用自身。这种调用机制允许函数通过分解问题为更小的相似子问题来解决复杂问题。递归的定义:递归是一种在函数定义中调用函数自身的方法。它通常包含一个或多个基准情况(base case),用于结束递归调用链,以及一个或多个递归步骤,用于将问题分解为更小的子问题。递归的原理:1.基准情况:这是递归调用的终止条件。当满足基准情况时,函数将不再调用自身,而是返回结果。2.递归步骤:在递归步骤中,函数会调用自身来解决一个规模更小的相似问题。这个过程会一直持续,直到达到基准情况。递归的示例:现在我将获取部门树作为示例1.示例数据准备/* Navicat Premium Data Transfer Source Server : localhost-本地 Source Server Type : MySQL Source Server Version : 80036 Source Host : localhost:3306 Source Schema : test Target Server Type : MySQL Target Server Version : 80036 File Encoding : 65001 Date: 22/04/2025 01:41:05*/SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for sys_dept-- ----------------------------DROP TABLE IF EXISTS `sys_dept`;CREATE TABLE `sys_dept` ( `dept_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '部门id', `parent_id` bigint(0) NULL DEFAULT 0 COMMENT '父部门id', `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '祖级列表', `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '部门名称', `order_num` int(0) NULL DEFAULT 0 COMMENT '显示顺序', `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '负责人', `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '联系电话', `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱', `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`dept_id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 210 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;-- ------------------------------ Records of sys_dept-- ----------------------------INSERT INTO `sys_dept` VALUES (100, 0, '0', '中国移动', 0, '张三', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', 'admin', '2025-03-04 10:15:38');INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '深圳总公司', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (103, 100, '0,100', '研发部门', 1, '若依', '', '', '0', '0', 'admin', '2025-02-17 08:58:32', 'admin', '2025-03-12 10:26:18');INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2025-02-17 08:58:32', '', NULL);2.获取组装的部门树:public List<SysDept> selectDeptList(SysDept dept) { // 最终的部门树结果 List<SysDept> deptTree = new ArrayList<>(); try { // 1.获取所有部门信息 List<SysDept> sysDebts = sysDeptMapper.selectDeptList(dept); // 2.获取根节点的部门信息(parentId=0的部门为根节点) List<SysDept> rootDept = sysDebts.stream().filter(s-> s.getParentId().equals(0L)) .collect(Collectors.toList()); for (SysDept root : rootDept) { // 3.递归获取子节点 SysDept tree = getChildren(root, sysDebts); // 4.添加到部门树中 deptTree.add(tree); } return deptTree; } catch (Exception e) { throw new RuntimeException(e); } }3.递归获取子部门:/** * 递归获取子节点 * @param rootDept 根节点 * @param sysDepts 所有部门信息 * @return 树形结构的部门信息 */ private SysDept getChildren(SysDept rootDept, List<SysDept> sysDepts) { ArrayList<SysDept> children = new ArrayList<>(); for (SysDept dept : sysDepts) { if (rootDept.getDeptId().equals(dept.getParentId())){ children.add(getChildren(dept, sysDepts)); } } rootDept.setChildren(children); return rootDept; }4.接口数据返回:[ { "deptId": 100, "parentId": 0, "ancestors": "0", "deptName": "中国移动", "orderNum": 0, "leader": "张三", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [ { "deptId": 101, "parentId": 100, "ancestors": "0,100", "deptName": "深圳总公司", "orderNum": 1, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [ { "deptId": 104, "parentId": 101, "ancestors": "0,100,101", "deptName": "市场部门", "orderNum": 2, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 105, "parentId": 101, "ancestors": "0,100,101", "deptName": "测试部门", "orderNum": 3, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 106, "parentId": 101, "ancestors": "0,100,101", "deptName": "财务部门", "orderNum": 4, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 107, "parentId": 101, "ancestors": "0,100,101", "deptName": "运维部门", "orderNum": 5, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null } ], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 103, "parentId": 100, "ancestors": "0,100", "deptName": "研发部门", "orderNum": 1, "leader": "若依", "phone": "", "email": "", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 102, "parentId": 100, "ancestors": "0,100", "deptName": "长沙分公司", "orderNum": 2, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [ { "deptId": 108, "parentId": 102, "ancestors": "0,100,102", "deptName": "市场部门", "orderNum": 1, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }, { "deptId": 109, "parentId": 102, "ancestors": "0,100,102", "deptName": "财务部门", "orderNum": 2, "leader": "若依", "phone": "15888888888", "email": "ry@qq.com", "status": "0", "delFlag": "0", "parentName": null, "children": [], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null } ], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null } ], "createBy": "admin", "createTime": "2025-02-17 00:58:32", "updateBy": null, "updateTime": null }]注意事项效率问题:递归可能引发栈溢出或重复计算(如斐波那契数列的朴素递归),需结合尾递归优化或动态规划改进。理解难度:需通过“递进+回归”过程分析,如阶乘的逐步展开与回溯。总结:递归是一种强大的编程范式,适用于问题可分解为自相似结构的场景,但需谨慎设计终止条件和优化性能。转载自https://www.cnblogs.com/songweipeng/p/18830967
-
前言1. Flowable中Java服务任务的依赖注入方法,比如使用Spring的@Autowired或构造函数注入,并确保服务任务类由Spring管理。2. 流程引擎配置中启用依赖注入和表达式解析的相关设置。3. 异步任务执行器的配置,确保其与Spring上下文集成,处理依赖注入的Bean。4. 常见问题排查,如检查Bean的作用域、是否正确使用delegateExpression等。5. 可能的示例代码或配置片段,帮助用户理解如何正确配置。另外,可能需要提醒用户注意流程定义文件中的服务任务配置是否正确,比如是否使用了expression或delegateExpression来引用Spring Bean,或者是否直接指定了Java类导致Spring的依赖注入不生效。最后,如果有必要,可以比较一下在非Spring环境下如何手动处理依赖注入,但大部分情况下用户可能在使用Spring,所以重点放在Spring集成方面。一、Java服务任务依赖注入场景:在实现JavaDelegate的类中注入Spring管理的Bean(如Service、Repository)。正确配置方法:步骤1:将服务类注册为Spring Bean@Component("myServiceTask") // 使用名称便于流程中引用public class MyServiceTask implements JavaDelegate { private final MyService myService; @Autowired // 构造器注入推荐 public MyServiceTask(MyService myService) { this.myService = myService; } @Override public void execute(DelegateExecution execution) { myService.businessMethod(); }}步骤2:在流程定义中引用Bean使用delegateExpression指向Spring Bean的名称:<serviceTask id="serviceTask1" name="My Task" flowable:delegateExpression="${myServiceTask}" />常见问题解决:Bean注入为null:确保类被Spring扫描(位于@ComponentScan路径下)。检查流程定义中是否错误使用class属性而非delegateExpression。确认流程引擎配置启用了Spring集成。表达式解析失败:在FlowableConfig中启用表达式解析和Spring注入:@Beanpublic SpringProcessEngineConfiguration processEngineConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager) { SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setDataSource(dataSource); config.setTransactionManager(transactionManager); config.setDatabaseSchemaUpdate(Flowable.DB_SCHEMA_UPDATE_TRUE); config.setAsyncExecutorEnabled(true); // 启用异步执行器 config.setExpressionManager(new SpringExpressionManager(applicationContext, null)); // 启用Spring表达式 return config;}二. 异步任务执行器配置当服务任务标记为异步(flowable:async="true")时,任务由异步执行器处理。配置异步执行器:@Beanpublic AsyncExecutor asyncExecutor(DataSource dataSource) { DefaultAsyncExecutor asyncExecutor = new DefaultAsyncExecutor(); asyncExecutor.setDataSource(dataSource); asyncExecutor.setCorePoolSize(5); // 核心线程数 asyncExecutor.setMaxPoolSize(10); // 最大线程数 asyncExecutor.setQueueSize(100); // 任务队列大小 return asyncExecutor;}在processEngineConfiguration中启用:config.setAsyncExecutor(asyncExecutor);config.setAsyncExecutorEnabled(true);异步任务依赖注入要点:Bean作用域:异步任务可能在新线程中运行,确保注入的Bean是线程安全的或使用@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)。事务管理:若异步任务涉及数据库操作,需配置@Transactional并确保事务传播正确。三、通过ApplicationContextAware接口的方式获取ApplicationContext对象实例可能的错误情况包括:没有在流程引擎配置中设置asyncExecutorEnabled为true,或者在服务任务中没有正确使用表达式导致注入失败。另外,Bean的作用域问题也可能导致依赖注入失败,例如,如果Bean的作用域是原型(prototype),但在注入时可能需要不同的处理方式。以下是我通过ApplicationContextAware接口的方式获取ApplicationContext对象实例,再通过applicationContext.getBean("myService")方法获取对应的bean代码示例:@Componentpublic class MyListener implements TaskListener, ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { applicationContext = arg0; } @Override public void notify(DelegateTask delegateTask) { String processInsId = delegateTask.getProcessInstanceId(); MyService myService = (MyService) applicationContext.getBean("myService"); // TODO 执行service方法 System.out.println("==========执行监听器======"); }}四. 常见问题排查错误:无法解析表达式${myServiceTask}检查Bean名称是否匹配。确认流程引擎配置中设置了SpringExpressionManager。异步任务不执行检查asyncExecutor是否启动:调用asyncExecutor.start()。查看日志中是否有任务提交异常。事务不生效确保异步执行器配置了事务管理器:asyncExecutor.setTransactionManager(transactionManager);五. Spring Boot集成若使用flowable-spring-boot-starter,简化配置如下:(1) application.yml:flowable: async-executor-enabled: true database-schema-update: true async-executor: core-pool-size: 5 max-pool-size: 10 queue-size: 100(2) 服务任务类:@Componentpublic class EmailServiceTask implements JavaDelegate { private final EmailService emailService; public EmailServiceTask(EmailService emailService) { this.emailService = emailService; } @Override public void execute(DelegateExecution execution) { String recipient = (String) execution.getVariable("email"); emailService.send(recipient, "流程通知", "您的任务已处理完成。"); }}(3) 流程定义XML:<serviceTask id="sendEmail" flowable:delegateExpression="${emailServiceTask}" />六.总结依赖注入:确保服务任务类为Spring Bean,流程中使用delegateExpression引用。异步执行:配置AsyncExecutor并启用,注意线程安全和事务。ApplicationContextAware接口的方式获取ApplicationContext对象实例Spring集成:正确配置SpringProcessEngineConfiguration以支持表达式和Bean解析。转载自https://www.cnblogs.com/songweipeng/p/18820536
-
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba</artifactId> <version>2023.0.1.2</version> <relativePath/> </parent> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-backend</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>intelligent-teaching-commons</module> <module>intelligent-teaching-user</module> <module>intelligent-teaching-chat</module> <module>intelligent-teaching-exam</module> <module>intelligent-teaching-chat/intelligent-teaching-chat-api</module> <module>intelligent-teaching-chat/intelligent-teaching-chat-shared</module> <module>intelligent-teaching-exam/intelligent-teaching-exam-api</module> <module>intelligent-teaching-exam/intelligent-teaching-exam-shared</module> <module>intelligent-teaching-oss</module> <module>intelligent-teaching-job</module> <module>intelligent-teaching-search</module> <module>intelligent-teaching-ai</module> </modules> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-ai.version>1.0.3</spring-ai.version> </properties> <repositories> <repository> <id>aliyunmaven</id> <url>https://maven.aliyun.com/repository/public</url> </repository> </repositories> <dependencies> <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-commons</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.4</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2023.0.1.2</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.34</version> <scope>compile</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-annotation</artifactId> <version>3.5.9</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.23</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.6.0</version> </dependency> <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.33</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.zxing/core --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.5.3</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.17.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.minio/minio --> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.13</version> </dependency> <!-- https://mvnrepository.com/artifact/io.netty/netty-all --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.115.Final</version> </dependency> <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.43.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core --> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.4.2</version> </dependency> <!-- https://mvnrepository.com/artifact/co.elastic.clients/elasticsearch-java --> <dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> <version>8.17.1</version> </dependency> <dependency> <groupId>com.luhuiguo</groupId> <artifactId>aspose-words</artifactId> <version>23.1</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>3.0.4</version> </dependency> <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-core --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-core</artifactId> <version>9.1.0</version> <type>pom</type> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.12.1</version> </dependency> <dependency> <groupId>io.springboot.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>${spring-ai.version}</version> </dependency> <dependency> <groupId>io.springboot.ai</groupId> <artifactId>spring-ai-ollama</artifactId> <version>${spring-ai.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.32.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.milvus/milvus-sdk-java --> <dependency> <groupId>io.milvus</groupId> <artifactId>milvus-sdk-java</artifactId> <version>2.5.10</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-oss</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-job</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-search</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-ai</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-chat-shared</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-exam-shared</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>cn.cat</groupId> <artifactId>intelligent-teaching-user-shared</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.4</version> </dependency> </dependencies> </dependencyManagement> </project>
-
在Java中,当你尝试从Map<String, Object>中移除一个不存在的键时,不会抛出异常。Map接口的remove方法被设计为安全地处理这种情况 - 如果键不存在,方法会简单地返回null(或指定的默认值,如果使用remove(Object key, V defaultValue)方法),而不会抛出任何异常。示例代码:import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) { Map<String, Object> map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", 123); // 尝试移除存在的键 Object removedValue = map.remove("key1"); System.out.println("Removed value: " + removedValue); // 输出: Removed value: value1 // 尝试移除不存在的键 Object nonExistentValue = map.remove("nonExistentKey"); System.out.println("Removed value: " + nonExistentValue); // 输出: Removed value: null // 使用Java 8+的remove方法,可以指定默认值 Object defaultValue = map.remove("anotherNonExistentKey", "default"); System.out.println("Removed value with default: " + defaultValue); // 输出: Removed value with default: default } } 关键点:标准remove方法:map.remove(key) - 如果键存在,返回对应的值;如果键不存在,返回null。带默认值的remove方法(Java 8+):map.remove(key, defaultValue) - 如果键存在,返回对应的值;如果键不存在,返回指定的默认值。不会抛出异常:无论键是否存在,remove方法都不会抛出异常。因此,你可以安全地尝试移除任何键,而不必担心会因为键不存在而导致程序抛出异常。
上滑加载中
推荐直播
-
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中 -
华为云一键云上部署Openclaw 实现“龙虾自由”2026/04/11 周六 14:00-16:00
秦拳德-中软国际教育卓越研究院研究员
还在为搭建AI环境而焦头烂额、彻夜难眠? 还在苦苦等待复杂工具的缓慢响应、迟迟无法推进项目? 别再犹豫,快来华为云,一键部署OpenClaw,轻松告别 繁琐配置,即刻畅享极速体验!更有龙虾实操演示全程护 航,真正解放双手,让办公效率实现质的飞跃。限时重磅 福利火热来袭,干万Tokens等你来瓜分,机会难得,不容 错过!
即将直播
热门标签