那些惊到我的代码(持续更新...)

本文,我会先放出类,放出截图,图片下面会写出我自己的看法。
以后,我也可以以此为鉴。

方法名驼峰+下划线并用

为什么要这样呢?
红线驼峰,绿色下划线。

我理解,某种情况下,可以提高可读性。

在这里插入图片描述

多个方法内容重复

为什么要这样呢?
我唯一能想到的,就是不确定代码会不会变,不会变的抽成方法,会变的,流程可能会改动的,暂时不进行公用。

1
2
3
2022-04-28 13:58:14 补充

测试的时候这样写我可以理解,但是真正生产中我感觉这样不好,体现不到代码复用的原则。

在这里插入图片描述

catch块里写分号

org.slf4j.helpers.Util#safeGetSystemProperty
在这里插入图片描述
为什么要这样呢?
为了可读性,大可加一行注释即可。没必要加引号的。我也是第一次见到。

下面我演示了一下,实时证明,编译会进行去除。
在这里插入图片描述

case加大括号

2021-11-09 14:35:25
在这里插入图片描述

原来 case 也可以加大括号,学到了~

静态变量的描述

2021-11-30 11:47:11
org.assertj.core.presentation.StandardRepresentation
写的非常清楚明了,让不知道这个类是干什么的,也可以看懂这些变量做什么的。
在这里插入图片描述

继承接口(向上扩展)

2021-11-30 11:49:02
java.io.Closeable
在 1.5 只有 Closeable 接口。在 1.7 的时候新加了一个接口 AutoCloseable,让 Closeable 继承 AutoCloseable。
(原本 1.5 的时候,Closeable 接口是底层功能。在 1.7 的时候,把底层功能拓展了,加了 AutoCloseable 接口)
在这里插入图片描述
在这里插入图片描述

Function.identity()

package java.util.function;
虽然这个并没有什么,写 t -> t 也是可以的(我之前就是这么写的)。但毕竟写Function.identity()更好看一些。
这里体会到封装的特性。就感觉很舒服,很漂亮的code~
在这里插入图片描述

接口默认方法抛异常

2022-01-12 18:37:04

在这里插入图片描述
为什么要这么做呢?

假设一种情况,A、B、C、D、E都实现这个接口。

A、B、C 无需对这个接口进行实现。
D、E 需要对这个接口进行实现。

设置 remove() 默认方法的话,避免了 A、B、C 要对此方法进行空实现。

嵌套延迟任务

2022-01-17 12:03:35
嵌套延时任务 => 定时任务
关于:线程池之ScheduledThreadPoolExecutor 可参考 23. 线程池之ScheduledThreadPoolExecutor
在这里插入图片描述

初始化容量 1024

2022-01-17 15:17:06

ConcurrentHashMap<Service, Service> => 应该是想当 并发 Set 来使用的。
好家伙,直接初始化 1024,这个应该是注册中心的服务单例仓库,不知道为什么要搞得那么的大。
在这里插入图片描述

代码为什么没注释

2022-01-17 15:38:03

真正的代码,只有类和方法有注释。其他地方都是没有注释的。
因为好的代码,方法和变量,以及类、接口。它们就是最好的注释。如果不是,说明你定义的具有二义性。

(后来我在此篇文章中也提到了,我对“牛逼代码的思考”。)
关于 Nacos config 长轮询的源码

多Map嵌套的可读性

2022-01-17 15:55:33
在这里插入图片描述

雪花id i++

2022-01-18 15:22:14

它应该只是想表明:我的id也是不唯一的。(不是只有雪花算法生成的id才能称之为雪花id,只要不重复就是雪花id)
在这里插入图片描述

常量值重复,名不一致

2022-01-18 15:29:22

当然了,这样写,同样也是为了区分可读性。虽然值相同,根据常量的名称不同,而适用于不同的场景。
在这里插入图片描述

饿汉单例放内部类中

》》》第一次理解
2022-01-21 08:29:42
这个单例为什么不直接放外部类中呢,而要放在内部类中。这个我不太明白。

猜测:我才猜不出来。。。我只能说私有静态内部类会加载一次,然后它的静态成员变量也会加载一次。保证只有一次创建对象。
但是你放在外部也是只有一次初始化呀,即使外部的类不是 static。我不太理解。
在这里插入图片描述

》》》第二次理解
2022-01-22 18:14:46
我又发现了一个。但是我看源码的时候也发现有很多直接放在了外部类中private static final 修饰的啊。还是搞不懂为什么要这样写。
在这里插入图片描述

》》》第三次理解
2022-04-22 02:04:08
高兴!!!
我发现了这个原因的奥秘。其实我在第一次的时候就已经说了。
(现在是上海疫情期间,此时我在家里学习,真的是上头了,现在都夜里2点了。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 它利用了关于【类初始化的语言保证】,因此可以在所有符合 Java 的编译器和虚拟机中正常工作。
//
// 内部类的引用不早于调用 getInstance() 的那一刻(因此类加载器不早于加载)。
// => 因此,该解决方案是线程安全的,不需要特殊的语言结构(即 volatile 或 synchronized)。
// 2022-04-22 02:04:08
public class Singleton {
private Singleton() {
}

private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

迭代器使用 for

2022-01-22 21:25:34

迭代器使用 for 来写,而不是 while
这样可以我是知道的,只是因为我从来没这样用过,我都是用 while。
使用起来也是差不多的,只是我第一次见到这样写,仅此而已。
在这里插入图片描述

List封装为对象

2022-01-25 09:42:16

不直接用 List<Instance> 来进行传参,而是把这个集合封装为一个对象 Instances(多了一个s)。
而在这个对象里面提供一些方法,供外部使用。(非常好的面向对象思想,值得借鉴。)
在这里插入图片描述

Retransmitter,最大重试次数,ack,耗时

2022-01-25 15:19:39

Retransmitter 从翻译可以了解到:转播发射器,中继发射器。
(也就是重新进行发射的意思)。

下面是 Nacos udp 数据包发送的一个使用场景。

  • 最大重试次数,和 ack。
    在这里插入图片描述

  • 耗时
    在这里插入图片描述

List Map.Entry

2022-01-25 17:24:11

不使用 List<Map<String, Instance>>,却使用 List<Map.Entry<String, Instance>>,…

1
2
3
4
5
6
7
8
2022-02-09 11:26:39

我想通了。

List<Map>,里面放的是 Map 类型,而 Map 类型里面占用空间大。
例如:HashMap 里面有 Entry[] 数据,LinkedHashMap 里面有 head、tail,TreeMap...

List<Map.Entry>,里面放的只是 k,v 结构的 node 节点。(占用空间小)。

在这里插入图片描述

面向对象的思想

2022-02-07 08:47:35

案例1、使用这个类来继承 Date,就可以直接使用它的格式。不用 new Date 然后再转一次,这里直接 new 即可。
在这里插入图片描述

案例2:使用多级继承的结构。符合依赖倒置。这个如果加功能的话,更方便一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
面向对象的三大特性:封装、继承、多态

面向对象的七大基本原则:(或 五大原则,前五项)

1. 单一职责原则(Single Responsibility Principle)
每一个类应该专注于做一件事情。

2. 开闭原则(Open Close Principle)
面向扩展开放,面向修改关闭。

3. 里氏替换原则(Liskov Substitution Principle)
超类存在的地方,子类是可以替换的。

4. 依赖倒置原则(Dependence Inversion Principle)
实现尽量依赖抽象,不依赖具体实现。

5. 接口隔离原则(Interface Segregation Principle)
应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。

6. 迪米特法则(Law Of Demeter)
又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。

7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。

在这里插入图片描述

static 加 synchronized

2022-02-07 09:37:33

避免还未初始化完成 (init 未执行完成),就执行了 shutdown 方法。
为了使这两个不能同时进行,所以上锁。
在这里插入图片描述

特权线程工厂 Executors.privilegedThreadFactory

2022-02-07 10:55:20

只是说与父线程具有相同的权限。设置 ac(访问控制器) 与 cl(类加载器)。

(暂时还不知道这两个有什么特别用途,看这个方法也没有在其他地方用到过。)
在这里插入图片描述

在这里插入图片描述

委托的好处

2022-02-07 10:24:38

一开始,我一直没想懂 delegate 委托的好处。后来我总结了有两个地方。(依照下面图示,我称为 “外”、“内”)

1、增减方法(扩展功能、减少功能)
原本“内”有 10个方法,但是我在“外”中只定义 3个方法。把其他方法不对外提供。同理,也可以在 10个方法上,提供额外的方法。

2、扩展功能
例如在下面图示,原本“内”只拥有②级功能。现在我让“外”继承了③级功能,这样就变相的让“内”拥有了③级功能。(也就是为了扩展功能)。

3、代码易维护
例如在下面图示,我为了使用“内”的execute方法,我把“外”也定义了一个execute方法。我的程序中到处用的都是“外”.execute,即使“内”.execute 方法改名了,改为 “内”.run 了,我只需要改“外”.execute 方法一处即可,不用修改多处。

总结:委托的本质,就是为了扩展功能、和 减少功能。

在这里插入图片描述

其实,我当时看到上面图示这些代码瞬间惊了,为什么这样???

心想:“不行,我一定要搞清楚为什么。”

然后我百度搜索了一堆,其实都没看太懂。
直接看到了一篇:设计模式-代理模式(delegate)

而此时的 CharacterProxy 类,只能对外提供 toString() 方法。这样即使外部想使用 CharSequence 的其他方法,也使用不到了。
(于是,这里我想到了《大话设计模式》里面的一句话:大致意思是说 想扩展功能可以使用代理,不想对外提供一些功能也可以使用代理。个人理解:委托也算是代理的一种。)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CharacterProxy {
// delegate 被代理
private final CharSequence delegate;

public CharacterProxy(CharSequence delegate) {
this.delegate = delegate;
}

@Override
public String toString() {
return delegate.toString();
}
}

2022-02-07 11:15:04 再举个栗子:
是不是一眼就看出来为什么了???
让④变相的增加了③⑤功能。
在这里插入图片描述

Class#getPrimitiveClass

2022-02-10 09:28:16

八大基本类型算是八种。
但是原生这个方法获取,却是 9 种。多了一个 void。

在这里插入图片描述
在这里插入图片描述

枚举单例,枚举实现接口,监听器

2022-02-11 08:05:50

第一次见到单例这么来用的,直接把枚举当做 Object 来使用。

这才是我见过最好的枚举单例。即使它不算是规规矩矩的枚举。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

枚举是天生单例的,众所周知。而且枚举是不可以反射进行创建实例的,其他都可以调用反射来创建实例。
所以,真实的情况是,安全的单例:只有枚举一种。

这里使用枚举的属性来控制,线程是否继续执行。这里我还测试了一把,枚举是单例的,但它的属性可以修改吗???是可以修改的。

下面是:“测试枚举的成员变量是否可以修改???”
其实用枚举就是为了不可变,所以,一般建议枚举是不要用可变成员变量的。
而且 sonar (代码扫描工具)建议枚举不要提供 setter 方法。只需要提供 getter 来获取属性值即可。
(所以,上面的使用并不是规范的用法,一般可以采用其他的方式来使用单例。这里就不说了。)
在这里插入图片描述

关于 hutool 监听器:
下面会存在一个问题,一旦阻塞 sync=true,这个线程就一直处于 wait(0) 状态了。
在这里插入图片描述

方法返回值泛型问题

2022-02-11 11:49:06

这个是使用的时候,我们不加类型强转。

这个是编译器自己加的。
在这里插入图片描述

静态代码块可以写多个

2022-02-18 18:01:39

第一次见到,原来还有写多个的。实际上,它们还是会被编译到一起去(从上到下编译)。
在这里插入图片描述
在这里插入图片描述

接口定义 @override 重写接口方法

2022-02-24 11:34:22

实现方法上面写 @override 是经常见到的,接口上面重新我是第一次见到。

没想懂为什么,要重新写一遍这个方法。不写也是可以的啊。

可能是开发者内部的一种规范吗???
在这里插入图片描述

ConcurrentHashMap 上synchronized锁

2022-02-25 17:13:12

我不明白为什么,右边的 1、3 都是 ConcurrentHashMap,但是中间还要上synchronized锁(this是单例的)
在这里插入图片描述

线程中断异常,再中断

2022-02-25 17:19:28

wait 是忽略了中断异常,而 sleep 是又中断了一次。
在这里插入图片描述

内部类调用外部类

2022-03-24 18:53:20

以前我没注意过这个问题。
今天突然想到。

答案可参考:深入理解Java中为什么内部类可以访问外部类的成员

两个准则:
1、内部类对象的创建依赖于外部类对象。

2、内部类对象持有指向外部类对象的引用(参考下面编译后的图片)。

在这里插入图片描述
在这里插入图片描述

异常单例

2022-03-25 17:15:02

某些异常是可以实现单例的。例如下面的警告。
当然,还是传入指定场景信息最好,对吧。
在这里插入图片描述

do{…}while(true);

2022-03-25 17:27:49

这样写,适用于必须要走一次。(while true 在前面,好像也能表达这个意思。)
在这里插入图片描述

单例池

2022-04-30 06:41:24

原因是我看到一个方法被废弃了,我看了一下。雪花对象应为单例。
在这里插入图片描述

下面我又看了一下单例的实现,这个时候我发现了 函数表达式的一个用途。
之前没发现,现在突然悟到了,就是参数不下传,可以使用回调。
在这里插入图片描述

异常定义

2022-05-11 08:55:51

无论怎么样,在能复用的情况下,最好定义异常对象。
这样使代码更友好。例如:你的权限校验异常在哪些地方会用到。一眼便知。
在这里插入图片描述
在这里插入图片描述

SPI机制的数据提供者

2022-05-11 08:58:55

我的天呐,这是缘分吗。怎么会那么的巧呢。

前两天我在写预警中心,发送服务逻辑的时候。
正好我最近在看 datart 的取数逻辑。

虽然都是一样的,但是实现方式是完全不同的,而且我的逻辑相对来说比较单一,直接用类进行区分。这里使用SPI机制来设计,分module 来设计,完全的解耦了。可选择性依赖。插件式扩展服务。
(2022-06-18 21:18:19 其实网上大部分说的SPI都是jdbc,以及Dubbo)
在这里插入图片描述
在这里插入图片描述

Exceptions.throw

2022-05-23 18:50:42

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

package datart.core.base.exception;

import datart.core.common.MessageResolver;

import java.lang.reflect.Constructor;

public class Exceptions {

public static void msg(String msg, String... code) {
tr(BaseException.class, msg, code);
}

public static void base(String msg) {
throw new BaseException(msg);
}

public static void notFound(String... msg) {
tr(NotFoundException.class, "base.not.exists", msg);
}

public static void exists(String... msg) {
tr(ParamException.class, "base.not.exists", msg);
}

public static void e(Exception e) {
throw new BaseException(e);
}

public static void tr(Class<? extends BaseException> clz, String messageCode, String... codes) throws RuntimeException {
BaseException throwable;
try {
String message = MessageResolver.getMessages(messageCode, (Object[]) codes);// codes 是填充参数的
Constructor<? extends BaseException> constructor = clz.getConstructor(String.class);
throwable = constructor.newInstance(message);
} catch (Exception e) {
throwable = new BaseException(messageCode);
}
throw throwable;
}


}
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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
public class MessageResolver {

private static MessageSource messageSource;

public MessageResolver() {
}

@Autowired
public void setMessageSource(MessageSource messageSource) {
MessageResolver.messageSource = messageSource;
}

public static String getMessage(Object code) {
return messageSource.getMessage(code.toString(), null, code.toString(), LocaleContextHolder.getLocale());
}

// public static String getMessage(String code, Object... args) {
// return messageSource.getMessage(code, args, code, LocaleContextHolder.getLocale());
// }

public static String getMessages(Object code, Object... messageCodes) {
Object[] objs = Arrays.stream(messageCodes).map(MessageResolver::getMessage).toArray();// 参数补充 {0} {1} 这种
return messageSource.getMessage(code.toString(), objs, code.toString(), LocaleContextHolder.getLocale());
// return getMessage(code, objs);
}
}

在这里插入图片描述

枚举$数字开头

2022-06-08 07:59:19
在这里插入图片描述

插入、更新数据

2022-06-12 18:15:08
在这里插入图片描述