Java小技巧
如何编写更健壮更好的java代码?这里面整理收集一些在java开发过程中我们需要注意的一些点或者说是技巧,从而提高代码的质量以及我们的工作效率。
Springboot
Ignore Null Fields with Jackson
springboot中默认序列化Json的框架就是jackson
了,这里面介绍在springboot项目中如何忽略null字段
- 全局
springboot配置和java代码都可以实现全局忽略null的功能
spring:
jackson:
default-property-inclusion: non_null
var mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
- 类级别
@JsonInclude(Include.NON_NULL)
public class Person { ... }
- 字段级别
public class MyPersonDto {
private String name;
@JsonInclude(Include.NON_NULL)
private String address;
}
Java record类不能用作JPA实体
A JPA entity class must:
🔘 be non-final
🔘 have a no-arg constructor
🔘 have non-final fields
A Record:
❌ is final
❌ has one constructor with all fields
❌ has final fields
Docker
docker远程Debug
- 使用docker部署项目
# 9094: 应用端口, 5005: 远程debug的端口,需要远程的服务器防火墙打开此接口
docker run -d -p 9094:9094 -p 5005:5005 \
-e "JAVA_TOOL_OPTIONS=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"" \
--name demo_app demo_app:0.0.1-SNAPSHOT
- 检查远程接口是否打开
nc -vz 47.111.12.18 5005
输出的日志中带有Connection to .... successed
就代表远程的5005接口是可以连通的。
- intellj配置
Remote Jvm Debug
# import thing is the JVM arguments
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

本地和远程的源代码要保持一致,要不然本地的debug会不能工作。
- 运行测试
本地代码打上断点,在idea上面点击server debug
以debug
模式运行。访问远程的api,就会走到本地相应的断点。
Standard
Boolean的短路评估
&&
: If the left operand is false, the right one isn’t called.||
: If the left operand is true, the right one isn’t called.
利用这个特性的话,在编写代码就可以通过变换表达式的位置或者提取方法来优化性能,比如
public boolean shouldSkip {
var hasOutput = CollectionUtils.isNotEmpty(response.getSpec().getOutputs());
var hasSecret = provider.callRemoteApi("string");
return hasSecret || hasOutput;
}
public boolean shouldSkip {
var hasOutput = CollectionUtils.isNotEmpty(response.getSpec().getOutputs());
return hasOutput || hasSecret("string");
}
private boolean hasSecret(String string) {
return provider.callRemoteApi(string);
}
总的原则就是把耗时的表达式滞后,如果表达式比较长的话,可以提取成函数。
静态方法的调用
当通过 Java 中的空引用调用静态方法时,不会引发异常并且代码会运行。
public class Main {
public static void main(String[] args) {
Greeting greeting = null;
greeting.hello(); // Hello
}
}
class Greeting {
public static void hello() {
System.out.println("Hello");
}
}
静态方法属于类而不是实例,在编译时greeting.hello()变成Greeting.hello()。调用静态方法时始终使用类名,而不是实例。
枚举类的比较
对于枚举判断相等,我们可以使用==
或者equals
方法
- 使用
==
boolean isEnabled = accountStatus == Status.ENABLED; // true or false
boolean isEnabled = "string" == Status.ENABLED; // compiler error
如果accountStatus为null,上述可以工作。如果accountStatus不是一个枚举,那编译报错。
- 使用
equals
将枚举常量放在左边,这样就可以避免空指针异常,但是没有类型的编译时检查。
boolean isEnabled = Status.ENABLED.equals(accountStatus); // true or false
boolean isEnabled = Status.ENABLED.equals("null") // false,正常编译
Tips
对于枚举判断相等,推荐使用==
,因为它提供类型的编译时检查并且保证了null
安全
Map的get方法为null的情况
Map的get
方法返回结果为null
是有两种情况:
- The map does not contain the provided key
- The map does contain the key but its value is null
所以说如果想要判断key在map中是否存在,建议使用contains(Object key)
方法。另外Java 8中提供了getOrDefault(Object key, V defaultValue),如果相应的key对应的值是null,那么返回defaultValue。
Stream中检查存在性
Java 8中引入的Stream,方便了我们对Collection的操作。在检查Java流中是否存在时,使用anyMatch()
而不是count() > 0
// Stream has 10k objects
// 2ms
result = employees.stream().anyMatch(e -> e.isActive());
// 20ms
result = employees.stream().fliter(e -> e.isActive()).count() > 0;
Tips
对比于count() > 0
,使用anyMatch()
更安全,性能也更好。因为anyMatch
在检索过程中,如果发现有一个满足条件,就直接返回true了。而count() > 0
撘配filter
会检索整个Stream。
null的instanceof操作符
如果对象为 null,instanceof
运算符将返回false。
// before
if (object != null && object instanceof MyClass) {}
// after: better
if (object instanceof MyClass) {}
谨慎使用BigDecimal(double)构造
BigDecimal(double)
存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。比如下面实际存储的值就是:0.100000001490116119384765625
BigDecimal value = new BigDecimal (0.1f)
优先推荐BigDecimal(String)
的构造方法或使用BigDecimal.valueOf
方法,此方法内部其实执行了Double的toString方法,而Double的toString按double的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
当涉及到金钱、对精度有要求的计算时,最好是使用BigDecimal
,而不是使用Double
或者Float
。当然对比于运算速度,Bigdecimal
是要慢些的。
0.1d * 0.2d; // 0.020000000000000004
BigDecimal.valueOf(0.1d).multiply(BigDecimal.valueOf(0.2d)); // 0.02
整型包装类对象之间值的比较
对于Integer.valueOf(i)
在-128至127
之间的赋值,Integer对象是在IntegerCache.cache
产生,会复用已有对象,这个区间内的Integer值可以直接使用==
进行判断。
但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑。
Info
所有整型包装类对象之间值的比较,全部使用 equals
方法比较。
System.out.println(Integer.valueOf(12) == Integer.valueOf(12)); // true
System.out.println(new Integer(12) == new Integer(12)); // false
System.out.println(Integer.valueOf(258) == Integer.valueOf(258)); // false
System.out.println(Integer.valueOf(12).equals(Integer.valueOf(12))); // true
System.out.println(new Integer(12).equals(new Integer(12))); // true
System.out.println(Objects.equals(Integer.valueOf(258), Integer.valueOf(258))); // true
new Integer(i)
这个方法在java 9已经被标记为废弃,被推荐使用Integer.valueOf
方法。这个方法的代码如下:
@IntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
@IntrinsicCandidate
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}