0%

Java系列之零碎笔记

基础

基本类型的零值

自动类型转换顺序

char>int>long>float>double

关键字

所有成员变量的调用附带上this,防止代码产生歧义或冲突。

synchronized

以某个代码片段为例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void fizz(Runnable printFizz) throws InterruptedException {
while (i <= n) {
synchronized (this) {
if (i % 3 == 0 && i % 5 != 0) {
printFizz.run();
i++;
this.notifyAll();
} else {
this.wait();
}
}
}
}

使用javap -v命令查看其class文件,得到如下的字节码命令:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public void fizz(java.lang.Runnable) throws java.lang.InterruptedException;
descriptor: (Ljava/lang/Runnable;)V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=2
0: aload_0
1: getfield #2 // Field i:I
4: aload_0
5: getfield #3 // Field n:I
8: if_icmpgt 73
11: aload_0
12: dup
13: astore_2
14: monitorenter // 在这里锁住
15: aload_0
16: getfield #2 // Field i:I
19: iconst_3
20: irem
21: ifne 56
24: aload_0
25: getfield #2 // Field i:I
28: iconst_5
29: irem
30: ifeq 56
33: aload_1
34: invokeinterface #4, 1 // InterfaceMethod java/lang/Runnable.run:()V
39: aload_0
40: dup
41: getfield #2 // Field i:I
44: iconst_1
45: iadd
46: putfield #2 // Field i:I
49: aload_0
50: invokevirtual #5 // Method java/lang/Object.notifyAll:()V
53: goto 60
56: aload_0
57: invokevirtual #6 // Method java/lang/Object.wait:()V
60: aload_2
61: monitorexit // 在这里第一次释放锁
62: goto 70
65: astore_3
66: aload_2
67: monitorexit // 这里会再次释放锁
68: aload_3
69: athrow
70: goto 0
73: return
Exception table:
from to target type
15 62 65 any
65 68 65 any
LineNumberTable:
line 47: 0
line 48: 11
line 49: 15
line 50: 33
line 51: 39
line 52: 49
line 54: 56
line 56: 60
line 58: 73
LocalVariableTable:
Start Length Slot Name Signature
0 74 0 this Lme/fengorz/leetcode/concurrency/fizz_buzz_multithreaded/FizzBuzz;
0 74 1 printFizz Ljava/lang/Runnable;
StackMapTable: number_of_entries = 6
frame_type = 0 /* same */
frame_type = 252 /* append */
offset_delta = 55
locals = [ class java/lang/Object ]
frame_type = 3 /* same */
frame_type = 68 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
frame_type = 2 /* same */
Exceptions:
throws java.lang.InterruptedException

发现上面有两条关键字节码monitorentermonitorexit

  • 每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权。
  • monitorexit会释放锁

上面的字节码文件中出现了一次monitorenter和二次monitorexit

两次monitorexit指令的原因是为了保证抛异常的情况下也能释放锁,所以javac为同步代码块添加了一个隐式的try-finally,在finally中会调用monitorexit命令释放锁。

transient

https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc

Class

1
void.class // 罕见又骚气的用法

泛型

1
2
3
4
public <T> T readObjectData(ByteBuffer buffer, Class<T> type) {
...
T retVal = (T) summaries;
return retVal;

我在stackoverflow发现一个很好的解析:

1
2
3
4
public <T> T readObjectData(...
^ ^
| + Return type
+ Generic type argument

集合

Unimagined

1
2
// 一个集合并入到另一个集合的安全写法
Collections.addAll(Collection<? super T> c, T... elements);
1
Collections.singletonMap(...);

快速失败机制(fast-fail)

集合常见的快速失败检查都是通过如下代码:

1
2
3
4
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

来自网上的Java集合体系图


异常

一些自定义常用异常可以抽成公用的静态final异常,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class PrematureCloseException extends IOException {

public static final PrematureCloseException BEFORE_RESPONSE_SENDING_REQUEST =
new PrematureCloseException("Connection has been closed BEFORE response, while sending request body");

public static final PrematureCloseException BEFORE_RESPONSE =
new PrematureCloseException("Connection prematurely closed BEFORE response");

public static final PrematureCloseException DURING_RESPONSE =
new PrematureCloseException("Connection prematurely closed DURING response");

PrematureCloseException(String message) {
super(message);
}

@Override
public synchronized Throwable fillInStackTrace() {
// omit stacktrace for this exception
return this;
}
}

一些运行时异常可以用Spring内置的断言API比较高逼格

1
Assert.notNull etc.

非null异常的优雅写法

1
Objects.requireNonNull(obj);

try资源句式

如果代码调用的类是实现java.lang.AutoCloseable的,用try资源句式来释放关闭资源这样代码就优雅多了。

PropertyDescriptor

1
2
3
PropertyDescriptor descriptor = new PropertyDescriptor("propertyName", param.getClass());
Method getPropertyMethod = descriptor.getReadMethod();
property = (Integer) getPropertyMethod.invoke(param);

String

String.format

https://blog.csdn.net/u010137760/article/details/82869637

String.intern

https://www.cnblogs.com/Yintianhao/p/12273714.html

n次方写法

在Java中一个数的N次方不可以写成:a^0这种形式,算得的数不正确;
正确的写法为Math.pow(a,0);

废弃对象手动赋值为null有助于GC即将生效

1
2
3
Object o = new Object();
o = null;
o = new Object(); // 这里将触发上面null的GC,又或者调用System.gc(); 但是System.gc()是Full GC

Integer.valueOf(x)缓存

如果x的范围在-128~127之间,那么会直接返回缓存中的对象,也就是只要数值被调用过一次,以后返回的都是同个对象实例了。


避坑

代码格式

一个团队你的代码格式一定要统一,不然代码冲突时,比对代码非常麻烦。

Service与Controller的交互

Service最终返回给Controller层的数据如果比较复杂,尽量不要用Map,可以封装成DTO,而且这样一来还可以结合JSR303或者其各种扩展,对DTO进行优雅的数据校验。

减少箭头型代码

https://blog.csdn.net/weixin_33858485/article/details/88768985
但是个人认为,一味的追求箭头代码也会有弊端,比如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Task task = monTask.getTask();
if (task == null) {
return;
}
MonAssignTaskExample monAssignTaskExample = new MonAssignTaskExample();
monAssignTaskExample.createCriteria().andTaskIdEqualTo(task.getTaskId()).andDealObjectIdEqualTo(task.getDealObjectId())
.andIsFinishEqualTo("N");
List<MonAssignTask> monAssignTasks = this.baseMonAssignTaskBO.selectByExample(monAssignTaskExample);
if (ObjectUtils.isEmpty(monAssignTasks)) {
return;
}
for (MonAssignTask monAssignTask : monAssignTasks) {
// do something
}

这样写固然是可以减少箭头代码,但是如若这段代码下面今后要添加一些额外的业务逻辑,这些业务逻辑与task是否为null没有必要的联系,
那么可能会因为task==null退出了整个函数,导致额外的业务逻辑不被执行。

Utils 工具类的命名

每次在使用工具类的时候,总是会出现一个类名出现N个工具类比如:
image
而且自己也会有自定义的Utils类,为了更好地区分,我喜欢在Utils类价格前缀Enhanced,这样找自己的工具类就方便多了,比如:

1
EnhancedBeanUtils

@Deprecated

有些被封装的业务方法或者工具类方法如果有做修改,增加了方法的升级版,老旧的方法可以用@Deprecated标记为过时,防止新业务逻辑用到废弃的API。

魔法值应该尽量避免

阿里巴巴说java规范里面说杜绝一切魔法值,但是我觉得也不用可以完全追求零魔法值,我认为需要用常量代替魔法值的有以下这些:

  1. 使用频率高的,或者被频繁调用的逻辑块中的魔法值。
  2. 可读性很差的魔法值和表达式,应该禁止,最好都声明成可读性较好的常量或者变量。

多余的super();

在看公司的上古代码发现这种写法:

1
2
3
4
5
public CreateRepairBillThread(SaInterfaceAccept accept) {
super();
this.accept = accept;
this.timeout = timeout * 60L * 1000L;
}

这里面super();是一句多余的代码,因为子类的构造器会默认先调用父类无参构造器。

尽量避免大对象

大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组。
比遇到大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对象”,我们写程序的时候应注意避免。
在Java虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们,而当复制对象时,大对象就意味着高额的内存复制开销。HotSpot虚拟机提供了-XX: PretenureSizeThreshold参数, 指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区之间来回复制,产生大量的内存复制操作。

接口与方法的实现注意形参尽量用可传送的DTO对象(实现Serializable)

这个将来方便各种动态扩展参数。

自动装箱和拆箱的坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 128;
Integer f = 128;
Integer e1 = 127;
Integer f1 = 127;
Long g = 3L;
System.out.println(c.equals(d));
System.out.println(e == f);// 不在-128~127范围内不相等
System.out.println(e.equals(f));// 替换成equals才会相等
System.out.println(e1 == f1);// 只有在-128~127之间,读取缓存中的值才会相等
System.out.println(g == (a + b));// == 会处理自动类型转换
System.out.println(g.equals(a + b));// equals方法不处理自动类型转换
}

涉及到日志输出注意避免无意义日志大量产生

对于某些重复性很高的业务逻辑,日志的输出要注意在可控的范围内,千万不能引起日志巨量产生而导致磁盘爆满。

读取IO流要注意尾部数据失真问题

1
2
3
4
5
6
7
8
9
temps = response.getOutputStream();
in = new DataInputStream(inputStream);
// 这个方法写入音频流时有个致命问题,如果是音频流会出现尾部有杂音,因为byte数组如果尾部空流在音频当还是会被当做声音处理
// 如果采用下面注释掉的这种写法的话
byte[] b = new byte[2048];
while ((in.read(b)) != -1) {
temps.write(b);
temps.flush();
}

可以采用HuTool工具API,或者使用其内部的实现机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
byte[] b = new byte[length];
int readLength;
try {
readLength = in.read(b);
} catch (IOException e) {
throw new IORuntimeException(e);
}
if (readLength > 0 && readLength < length) {
byte[] b2 = new byte[length];
System.arraycopy(b, 0, b2, 0, readLength);
return b2;
} else {
return b;
}
1
2
3
4
5
6
byte[] b = IoUtil.readBytes(in, 1024);
while (b != null && b.length > 0) {
temps.write(b);
temps.flush();
b = IoUtil.readBytes(in, 1024);
}

集合在new的时候通过构造器传入初始数据时,要避免是null值

1
2
List list = new LinkedLIst(...);
// ...里面要避免是null值

Combo

jar

Mac中jar运行之后窗口无法粘贴

有些情况下需要使用windows的复制粘贴快捷键才能生效

jar指定目录的所有classes去打包

1
jar cvf test.jar -C unwoven-classes .

Jackson Convert Error: Cannot construct instance of java.time.LocalDateTime

LocalDateTime属性加上注解
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)

Thread.sleep 使用 TimeUnit.XXX.sleep(x) 代替(可读性更好)

https://stackoverflow.com/questions/9587673/thread-sleep-vs-timeunit-seconds-sleep

音频格式转换

使用JAVE 音频转换包

Mac下JDK目录

可以通过/usr/libexec目录下的java_home命令找到Mac下安装过的JDK所在目录

1
2
{/usr/libexec} # ./java_home
/Library/Java/JavaVirtualMachines/jdk-11.0.8.jdk/Contents/Home