BigDecimal和Long表示金额哪个更合适,怎么选择?

典型回答 大家都知道,不能用Float和Double来表示金额,会存在丢失精度的问题。 ✅为什么不能用浮点数表示金额? 那么要表示金额,业内有两种做法: 1、单位为分,数据库存bigint,代码中用long。如100.16元,存储为10016(在不考虑多币种的情况下)。 2、单位为元,数据库用decimal,代码中用BigDecimal(我们一般数据库存储的是decimal(18,6))。如100.16元,直接存成100.16 这两种,其实我们都用过,而且现在也还都在用,因为他们都有各自的优缺点以及适用场景。 首先说BigDecimal,BigDecimal 是 Java 中用于精确计算的类,特别适合于需要高精度数值计算的场景,如金融、计量和工程等领域。其特点如下: 精确度高:BigDecimal 可以表示非常大或非常精确的小数,而不会出现浮点数那样的舍入误差。 灵活的数学运算:它提供各种方法进行精确的算术操作,包括加减乘除和四舍五入等。 控制舍入行为:在进行数学运算时,你可以指定舍入模式,这对于金融计算非常重要。 所以,BigDecimal的适用场景是需要高精度计算的金融应用,如货币计算、利率计算等。比如我们的结算系统、支付系统、账单系统等,都是用BigDecimal的。 其次,再说Long,long 是 Java 的一种基本数据类型,用于表示没有小数部分的整数。其特点如下: 性能高:作为基本数据类型,long 在处理速度上比 BigDecimal 快很多。 容量限制:long 可以表示的最大值为 (2^{63}-1),最小值为 (-2^{63})。这在大多数应用程序中已经足够,但在表示非常大的数或需要小数的计算中则不适用。 不适合精确的小数运算:long 无法处理小数,如果需要代表金额中的小数部分(如厘),则需要自行管理这一部分。 所以,Long的适用场景是适合于不涉及小数计算的大整数运算,如某些计数应用或者金额以整数形式表示。比如我们的额度系统、积分系统等。 很多人会有疑惑,什么情况下会出现需要比分还小的单位呢?其实就是在很多需要运算的场景,比如说金融的费率、利率、服务费的费率等等,这些都是很小的,一般都是万分之几或者千分之几。而一旦有一个单位为元的金额和一个"率"相乘的时候,就会出现小于分的单位。 那有人说,遇到分我就直接四舍五入不就行了么,反正结算也是按照分结算的。这样做会有问题,我举个例子。 我一笔账单,有两笔订单,金额都是1元,存储的时候按照分存储,即100分,然后我的服务费费率是0.004。 如果是以分为单位,long存储和表示的话,那么两笔订单分开算费率的话:100*0.004 = 0.4 ,四舍五入 0, 两笔加在一起,收入的费率就是0分。 但是如说是以元为单位,bigdecimal存储和表示的话,那么两笔订单分开算费率的话:1*0.004 = 0.004 , 两笔加在一起0.008,当我要结算的时候,再做四舍五入就是0.01元,即1分钱。 所以,因为long在计算和存储的过程中都会丢失掉小数部分,那就会导致每一次都被迫需要四舍五入。而decimal完全可以保留过程中的数据,再最终需要的时候做一次整体的四舍五入,这样结果就会更加精确! 所以,如果你的应用需要处理小数点后的精确计算(如金融计算中常见的多位小数),则应选择 BigDecimal。 如果你的应用对性能要求极高,并且没有乘除类运算,不需要很小的精度时,那么使用 long 可能更合适。 总结来说,对于绝大多数涉及货币计算的应用,推荐使用 <font style="background-color:#FBDE28;">BigDecimal</font>,因为它提供了必要的精度和灵活性,尽管牺牲了一些性能。如果确定不需要处理小数,并且对执行速度有极端要求,使用 <font style="background-color:#FBDE28;">long</font> 可能更适合。

March 22, 2026 · 1 min · santu

Java中的static都能用来修饰什么?

典型回答 在Java编程语言中,static关键字是非常重要的修饰符,可以用于多种不同的地方。可用来修饰变量、方法、代码块以及类。 ** 静态变量**: 定义:静态变量属于类本身,而不是类的任何特定实例(new出来的对象)。 特点: 所有实例共享同一静态变量。 在类加载到内存时就被初始化,而不是在创建对象的时候。 常用于管理类的全局状态或作为常量仓库(例如public static final修饰的常量)。 1 2 3 4 5 public class Counter { // 静态变量 public static int count = 0; public static final String ERROR_CODE = "SYSTEM_ERROR"; } 静态方法: 定义:静态方法同样属于类,而非类的实例。 特点: 可以在不创建类的实例的情况下调用。 不能访问类的实例变量或实例方法,它们只能访问其他的静态成员。 常用于工具类的方法,例如Math.sqrt()或Collections.sort()。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MathUtils { // 静态方法 public static double square(double number) { return number * number; } } // 使用示例 public class Main { public static void main(String[] args) { double result = MathUtils.square(3.0); System.out.println(result); // 输出9.0 } } ** 静态代码块**: 定义:用于初始化类的静态变量。 特点: 当类被Java虚拟机加载并初始化时执行。 通常用于执行静态变量的复杂初始化。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class DatabaseConfig { public static int timeout; public static String url; // 静态代码块 static { System.out.println("Initializing database settings"); timeout = 30; // 以秒为单位 url = "jdbc:mysql://localhost:3306/myDatabase"; } } // 使用示例 public class Main { public static void main(String[] args) { System.out.println("Database URL: " + DatabaseConfig.url); // 输出初始化的URL System.out.println("Timeout: " + DatabaseConfig.timeout); // 输出初始化的超时时间 } } 静态内部类: 定义:在一个类的内部定义的静态类。 特点: 可以不依赖于外部类的实例而独立存在。 可以访问外部类的所有静态成员,但不能直接访问外部类的实例成员。 常用于当内部类的行为不应依赖于外部类的实例时。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class OuterClass { // 静态内部类 public static class StaticNestedClass { private int value; public StaticNestedClass(int value) { this.value = value; } public void display() { System.out.println("Value: " + value); } } } // 使用示例 public class Main { public static void main(String[] args) { OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass(5); nestedObject.display(); // 输出值5 } } 使用static修饰符的好处包括减少内存使用(共享静态变量而不是为每个实例创建副本)、提供一个全局访问点(例如静态方法和变量)以及无需实例化类即可使用其中的方法和变量。 ...

March 22, 2026 · 2 min · santu

怎么修改一个类中的private修饰的String参数的值

典型回答 这个问题,要么面试官是想问你反射,要么就是在给你挖坑! 因为,在Java中,String 类型确实是不可变的。这意味着一旦一个 String 对象被创建,其内容就不能被改变。任何看似修改了 String 值的操作实际上都是创建了一个新的 String 对象。 ✅String为什么设计成不可变的? 当然,如果不考虑这个可不可变的问题,新建一个也算改了的话。那么就有以下几种方式: 1、在Java中,private 访问修饰符限制了只有类本身可以访问和修改其成员变量。如果需要在类的外部修改一个 private 修饰的 String 参数,通常有几种方法: 1. 使用 Setter 方法 这是最常用且最符合对象导向设计原则的方法。在类内部提供一个公开的 setter 方法来修改 private 变量的值。 1 2 3 4 5 6 7 8 9 10 11 public class MyClass { private String myString; public void setMyString(String value) { this.myString = value; } } // 使用 MyClass obj = new MyClass(); obj.setMyString("new value"); 2. 使用反射 如果没有 setter 方法可用,可以使用反射。这种方法可以突破正常的访问控制规则,但应谨慎使用,因为它破坏了封装性,增加了代码的复杂性和出错的可能性。并且性能并不好。 ...

March 22, 2026 · 1 min · santu

有了equals为啥需要hashCode方法?

典型回答 在Java中,equals()和hashCode()方法通常是成对的,它们在使用基于Hash机制的数据结构时非常重要,例如HashMap、HashSet和Hashtable等。 equals():用于判断两个对象是否相等 hashCode:生成对象的哈希码,返回值是一个整数,用于确定对象在哈希表中的位置。 为什么需要hashCode,主要是为了方便用在Hash结构的数据结构中,因为对于这种数据结构来说,想要把一个对象存进去,需要定位到他应该存放在哪个桶中,而这个桶的位置,就需要通过一个整数来获取,然后再对桶的长度取模(实际hashmap要比这复杂一些,可以看:https://www.yuque.com/hollis666/ec96i7/sz24zwwrdg92qizg)。 HashMap的数据结构详细的请参考: ✅HashMap的数据结构是怎样的? 那么,怎么能快速获取一个和这个对象有关的整数呢,那就是hashCode方法了。所以,hashCode的结果是和对象的内容息息相关的。那么也就意味着如果两个对象通过equals()方法比较是相等的,那么它们的hashCode()方法必须返回相同的整数值。 那么,在一个对象中,定义了equals方法之后,同时还需要定义hashCode方法, 因为这样在向hashMap、hashTable等中存放的时候,才能快速的定位到位置。 所以,基于两方面考虑,一方面是效率,hashCode() 方法提供了一种快速计算对象哈希值的方式,这些哈希值用于确定对象在哈希表中的位置。这意味着可以快速定位到对象应该存储在哪个位置或者从哪个位置检索,显著提高了查找效率。 **另外一方面是可以和equals做协同来保证数据的一致性和准确性。**根据 Java 的规范,如果两个对象通过 equals() 方法比较时是相等的,那么这两个对象的 hashCode() 方法必须返回相同的整数值。如果违反了这一规则,将导致哈希表等数据结构无法正确地处理对象,从而导致数据丢失和检索失败。

March 22, 2026 · 1 min · santu

JDK 9中对字符串的拼接做了什么优化?

典型回答 在JDK 9之前,字符串拼接通常使用+进行(也有其他的,我们不做展开了),+的实现其实是基于StringBuilder的。具体参考: ✅String、StringBuilder和StringBuffer的区别? 这个过程其实是比较低效的,因为整个过程包含了创建StringBuilder对象,通过调用append方法拼接字符串,最后通过toString方法转换成最终的字符串等多个操作。所以才有个规范说不要在 for 循环中用+来拼接字符串的。 但是,这个其实在 JDK 9中已经被修改了。JDK 9引入了StringConcatFactory。这玩意被推出的的主要目标是提供一种灵活且高效的方式来拼接字符串,代替之前的 StringBuilder 或 StringBuffer 的静态编译方法。 StringConcatFactory是基于invokedynamic指令实现的。 是 Java 7 中引入的一种动态类型指令,允许 JVM 在运行时动态解析和调用方法。 也就说,利用<font style="color:rgb(13, 13, 13);">invokedynamic</font>的特性,将字符串拼接的操作延迟到运行时,而不是在编译时固定使用StringBuilder。(前面的链接中我们做过反编译,可见+转成 StringBuilder是编译的时候就确定了的。) 这就使得,JVM可以在运行时根据实际的场景选择最优的拼接策略,可能是使用StringBuilder、StringBuffer、或者其他更高效的方法。 在 JDK 9中(后续版本会有所变化,1.9 看的比较清楚),支持的拼接策略有以下几个: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private enum Strategy { /** * 使用 StringBuilder 进行字符串拼接,但不预估所需的存储空间。 */ BC_SB, /** * 使用 StringBuilder 进行字符串拼接,同时尝试估计所需的存储空间,以优化性能。 */ BC_SB_SIZED, /** * 使用 StringBuilder 进行字符串拼接,且能够精确计算出所需的存储空间,以实现最高的效率。 */ BC_SB_SIZED_EXACT, /** * 基于 MethodHandle 技术,使用 StringBuilder 进行拼接,并尝试预估所需的存储空间。 */ MH_SB_SIZED, /** * 基于 MethodHandle 技术,使用 StringBuilder 进行拼接,并精确计算所需的存储空间。 */ MH_SB_SIZED_EXACT, /** * 通过 MethodHandle 技术,直接从输入参数构建一个字节数组,并准确计算出所需的存储空间,以实现高效的字符串拼接。 */ MH_INLINE_SIZED_EXACT } StringBuilder你不陌生,MethodHandle 是啥?他 Java 7 开始引入的特性,它提供了一种灵活且高效的方法来直接操作方法、构造函数和字段的调用。 ...

March 22, 2026 · 1 min · santu

Stream的并行流一定比串行流更快吗?

典型回答 不一定! Stream底层使用了ForkJoin进行并发处理,但是,并不代表着用了并发处理就一定比串行处理更快。有以下几个因素影响着并行流的性能: 线程管理的开销:并行流使用了多线程,而用了多线程就会带来线程管理和任务分配的开销。 任务分割:并行流的性能提升依赖于任务能够有效地分割和分配。如果任务分割不均衡,一些线程可能空闲或等待,从而影响性能。 线程争用:并行流使用公共的ForkJoinPool,如果系统中有其他并行任务,这些任务会争用线程资源,可能导致性能下降。 数据依赖性:并行流适用于没有数据依赖性的操作。如果操作之间存在依赖关系,并行流可能无法有效地提升性能,甚至可能导致错误。 环境配置:机器的硬件配置(例如CPU核心数)和当前系统负载也会影响并行流的性能。如果CPU核心数较少或负载较高,并行流的性能可能不如串行流。 在github(https://github.com/nickliuchao/stream/tree/master )上看到过有人做过测试,他测试的几个case是: 多核CPU服务器配置环境下,对比长度100的int数组的性能; 多核CPU服务器配置环境下,对比长度1.00E+8的int数组的性能; 多核CPU服务器配置环境下,对比长度1.00E+8对象数组过滤分组的性能; 单核CPU服务器配置环境下,对比长度1.00E+8对象数组过滤分组的性能。 主要区别就是CPU核数、任务的数量以及Stream中的元素的类型。得到的结果如下: 多核CPU服务器配置环境下,对比长度100的int数组 常规的迭代>Stream并行迭代>Stream串行迭代 多核CPU服务器配置环境下,对比长度1.00E+8的int数组 Stream并行迭代>常规的迭代>Stream串行迭代 多核CPU服务器配置环境下,对比长度1.00E+8对象数组过滤分组 Stream并行迭代>常规的迭代>Stream串行迭代 单核CPU服务器配置环境下,对比长度1.00E+8对象数组过滤分组 常规的迭代>Stream串行迭代>Stream并行迭代 所以,我们可以得到结论: 在单核CPU的情况下,Stream的串行迭代的效率是要高于Stream的并行迭代的效率的。 而在多核CPU的情况下,Stream的并行迭代速度要比Stream的串行迭代效率要高。但是,如果元素数量比较少的话,直接用常规迭代反而性能更好。

March 22, 2026 · 1 min · santu

反射与封装是否矛盾?如何解决反射破坏封装不安全的问题?

典型回答 封装、继承、多态是面向对象的基本原则,是 Java 的基础,封装目的是隐藏对象的内部实现细节,只暴露必要的接口,从而保护数据的完整性和安全性。 而反射是一种可以在运行期动态的访问、修改类的方法和属性的方式。而且反射是可以访问到一个类内部定义的私有的成员变量和方法的。所以,反射确实是可以破坏封装的保护机制的。 ✅什么是反射机制?为什么反射慢? 但是反射和封装达不到说矛盾的地步,可以认为基本原则就是要封装,而在一些特殊情况下,比如一些框架的底层处理上,是需要一种机制可以打破封装的桎梏的。 如何解决反射导致封装被破坏的问题,这个东西,只能从反射上入手,封装上面没啥能干的。而且能做的也就是通过一些使用规范来做约束。 比如,最小化反射原则,就是在非必要情况下,尽可能不要使用反射,尤其是在业务代码中,不要使用反射,只能在框架设计中使用。如果一定要使用反射,也只针对涉及到的字段和方法做反射。 还可以通过白名单机制,限制反射只能访问特定的类和成员。例如,在框架中设计时,明确允许反射访问的类和字段清单,避免任意访问不该暴露的成员。(比如我介绍过 fastjson 的 autotype 的反序列化的白名单机制,大致就是这个思想:https://www.yuque.com/hollis666/ec96i7/sexwwk) 还有就是依赖 CodeReview 了,在代码审查的时候严格的对反射进行审查。

March 22, 2026 · 1 min · santu

Java有协程吗?

典型回答 因为Go有协程,所以在一些Go的公司,如字节,腾讯等在面试的时候,或者是有些开发者Java和go都用过的话,面试官可能会问这个问题。 Java中是有协程的,叫虚拟线程: ✅JDK21 中的虚拟线程是怎么回事?

March 22, 2026 · 1 min · santu

留言给博主