Java基础

代理模式

代理模式是什么?

我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常少,日常开发几乎看不到使用静态代理的场景。

上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件

静态代理实现步骤:

  1. 定义一个接口及其实现类
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情

代码示例:

1.定义发送短信的接口

1
2
3
public interface SmsService {
String send(String message);
}

2.实现发送短信的接口

1
2
3
4
5
6
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

3.创建代理类并同样实现发送短信的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SmsProxy implements SmsService {

private final SmsService smsService;

public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}

@Override
public String send(String message) {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method send()");
smsService.send(message);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method send()");
return null;
}
}

4.实际使用

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}

运行上述代码之后,控制台打印出:

1
2
3
before method send()
send message:java
after method send()

可以输出结果看出,我们已经增加了 SmsServiceImpl 的send()方法。

动态代理

相比于静态代理,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

JDK 动态代理机制

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心****。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象

1
2
3
4
5
6
7
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的一些接口;
  3. h : 实现了 InvocationHandler 接口的对象;

要实现动态代理,还必须需要实现InvocationHandler 来自定义处理逻辑(核心)。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用

1
2
3
4
5
6
7
8
public interface InvocationHandler {

/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

invoke() 方法有下面三个参数:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法****。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

JDK 动态代理类使用步骤

  • 定义一个接口及其实现类
  • 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法),并在被代理类的方法前后自定义一些处理逻辑
  • 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象

代码示例:

1.定义发送短信的接口

1
2
3
public interface SmsService {
String send(String message);
}

2.实现发送短信的接口

1
2
3
4
5
6
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

3.定义一个 JDK 动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;

public DebugInvocationHandler(Object target) {
this.target = target;
}


public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}

invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。

4.获取代理对象的工厂类

1
2
3
4
5
6
7
8
9
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}

getProxy():主要通过Proxy.newProxyInstance()方法获取某个类的代理对象

5.实际使用

1
2
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

运行上述代码之后,控制台打印出:

1
2
3
before method send
send message:java
after method send

CGLIB 动态代理机制

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,可以用 CGLIB 动态代理机制来避免****。

CGLIBopen in new window(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIBopen in new window, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心****。

你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

1
2
3
4
public interface MethodInterceptor extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
  1. obj : 被代理的对象(需要增强的对象)
  2. method : 被拦截的方法(需要增强的方法)
  3. args : 方法入参
  4. proxy : 用于调用原始方法

你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

CGLIB 动态代理类使用步骤

  • 定义一个类
  • 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  • 通过 Enhancer 类的 create()创建代理类

代码示例

不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

1.实现一个使用阿里云发送短信的类

1
2
3
4
5
6
7
8
package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

2.自定义 MethodInterceptor(方法拦截器)

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
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {


/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}

}

3.获取代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}

4.实际使用

1
2
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

运行上述代码之后,控制台打印出:

1
2
3
before method send
send message:java
after method send

JDK 动态代理和 CGLIB 动态代理对比

  1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类****。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法
  2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

静态代理和动态代理的对比

  1. 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  2. JVM 层面静态代理编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

Java多态机制

什么是多态机制?Java语言是如何实现多态的?

多态分为编译时多态运行时多态。如果在编译时能够确定执行多态方法中的哪一个,称为编译时多态,否则称为运行时多态(核心)

其中编译时多态是静态的主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

运行时多态就是指程序中定义的引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定****。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

编译时多态实践

方法重载实现的编译时多态:

方法重载就是在同一个类中,出现了多个同名的方法,他们的参数列表(方法签名)不同 (参数列表的个数不同,参数列表的数据类型不同,参数列表的顺序不同)根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。

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
public class test1 {
public static void main(String[] args) {
// 下面是针对求和方法的调用
int sum1 = add(1, 2);
int sum2 = add(1, 2, 3);
double sum3 = add(1.2, 2.3);
// 下面的代码是打印求和的结果
System.out.println("sum1=" + sum1);
System.out.println("sum2=" + sum2);
System.out.println("sum3=" + sum3);
}

// 下面的方法实现了两个整数相加
public static int add(int x, int y) {
return x + y;
}
// 下面的方法实现了三个整数相加
public static int add(int x, int y, int z) {
return x + y + z;
}
// 下面的方法实现了两个小数相加
public static double add(double x, double y) {
return x + y;
}
}
1
2
3
sum1=3
sum2=6
sum3=3.5

方法重写实现的编译时多态:

当一个对象的引用指向的是当前对象所属类的对象时,为编译时多态,其他则为运行时多态。sun的引用指向的就是Sun类的对象,故在编译时期就可以确定要执行sun类中的say()方法,故属于编译时多态。

1
2
3
4
5
public class Father {
public void say() {
System.out.println("我是父类");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Sun extends Father{

public void say() {
System.out.println("我是子类");
}

public static void main(String[] args) {
Sun sun = new Sun();
Father father = new Father();
sun.say();
father.say();
}
}
1
2
我是子类
我是父类

运行时多态实践

编译时期,首先会去查看父类中是否有这个方法,如果没有的话向上继续查找,直到找到Object类如果还没有的话就报错,如果有的话,到运行阶段查看子类中有没有覆盖该方法,如果覆盖了,则执行子类覆盖的方法,如果没有则执行父类中原本的方法

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
public class MultiTest {

public static void main(String[] args) {
System.out.println("------------以 Cat 对象调用 show 方法---------------");
show(new Cat()); // 以 Cat 对象调用 show 方法
System.out.println("------------以 Cat 对象调用 show 方法---------------");
System.out.println("------------以 Dog 对象调用 show 方法---------------");
show(new Dog()); // 以 Dog 对象调用 show 方法
System.out.println("------------以 Dog 对象调用 show 方法---------------");

System.out.println("------------Cat向上转型调用 show 方法---------------");
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
System.out.println("------------Cat向上转型调用 show 方法---------------");
System.out.println("------------Animal引用指向的Cat对象向下转型调用 work 方法---------------");
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
System.out.println("------------Animal引用指向的Cat对象向下转型调用 work 方法---------------");
System.out.println("------------Cat向上转型查看变量name---------------");
System.out.println(a.name);
System.out.println("------------Cat向上转型查看变量name---------------");
}

public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}

}

abstract class Animal {
String name = "动物名";
abstract void eat();
}

class Cat extends Animal {
String name = "猫名";
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
public void addFunction() {
System.out.println("我是子类猫新增的方法");
}
}

class Dog extends Animal {
String name = "狗";
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
------------以 Cat 对象调用 show 方法---------------
吃鱼
抓老鼠
------------以 Cat 对象调用 show 方法---------------
------------以 Dog 对象调用 show 方法---------------
吃骨头
看家
------------以 Dog 对象调用 show 方法---------------
------------Cat向上转型调用 show 方法---------------
吃鱼
------------Cat向上转型调用 show 方法---------------
------------Animal引用指向的Cat对象向下转型调用 work 方法---------------
抓老鼠
------------Animal引用指向的Cat对象向下转型调用 work 方法---------------
------------Cat向上转型查看变量name---------------
动物名
------------Cat向上转型查看变量name---------------

子类新增加的方法通过多态可以执行吗?

不可以。在编译

1
a.addFunction();

这行时,编译时报错。因此,当父类引用指向子类对象时候,父类只能执行那些在父类中声明、被子类覆盖了的子类方法,如上述中的eat(),而不能执行子类增加的成员方法,如addFunction()。

如果父类中的属性被子类覆盖,会显示哪个属性的值呢?

当子类和父类有相同属性时,父类还是会执行自己所拥有的属性若父类中没有的属性子类中有,当父类对象指向子类引用时(向上转型),在编译时期就会报错

如果父类中的static方法被子类覆盖呢?会执行哪个?

对于static方法还是会执行父类中的方法。这是由于在运行时,虚拟机已经认定static方法属于哪个类“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承

子类会将父类静态方法隐藏(hide),但子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法。

重载和重写

重载****:发生在同一个类中方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理

重载的实践请查看多态中编译时多态实践小节中的重载方法实现多态的实践代码。

重写****:重写是子类对父类的允许访问的方法的实现过程进行重新编写

  1. 方法名、参数列表必须相同(核心)子类方法返回值类型应比父类方法返回值类型更小或相等抛出的异常范围小于等于父类,访问修饰符范围大于等于父类
  2. 如果父类方法访问修饰符为 private/final/static子类不能重写该方法,但是被 static 修饰的方法能够被再次声明
  3. 构造方法无法被重写

static方法不能被重写但是能重定义详解:

编译出错的情况:

1
2
3
4
5
6
7
8
9
10
11
class Animal {
public static void walk() { System.out.println("父类Animal走呀走~"); }
}
public class Horse extends Animal {
public void walk() {
System.out.println("子类Horse走呀走~");
}
public static void main(String [] args) {
new Horse().walk();
}
}

编译通过的情况:

1
2
3
4
5
6
7
8
9
10
11
class Animal {
public static void walk() { System.out.println("父类Animal走呀走~"); }
}
public class Horse extends Animal {
public static void walk() {
System.out.println("子类Horse走呀走~");
}
public static void main(String [] args) {
new Horse().walk();
}
}
1
子类Horse走呀走~

**走的实际是子类Horse的静态方法walk(),即隐藏了父类的同名静态方法walk()子类运行的是自己的walk()**。

面向对象五大基本原则

单一职责原则SRP(Single Responsibility Principle)

类的功能要单一,不能将错综复杂的功能都放到一个类中。一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。类的职责主要包括两个方面:数据职责行为职责数据职责通过其字段来体现,而行为职责通过其方法来体现

接口分离原则ISP(the Interface Segregation Principle ISP)

设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,将这些功能作为方法定义在一个接口中,所有想造手机的人想要实现造手机的目的,就相当于需要将这个接口的方法全部实现才可以造一部手机,万一有的手机制造商不想给自己造的手机加玩游戏这个功能呢?所以把这几个功能拆分成不同的接口,手机制造商可以根据自己的需求选择性实现哪些功能,也就是每个实现类选择性实现哪些接口,反正一个类可以实现多个接口,比把所有功能放在一个接口里要好的多。

开放封闭原则OCP(Open-Close Principle)

  • ,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的
  • ,是指对于原有代码的修改是封闭的,即不应该修改原有的代码

当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。用抽象构建框架,用实现扩展细节

实践实例:

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
class Rectangle{
void draw(){ System.out.println("长方形"); }
}

class Circle {
void draw(){ System.out.println("圆形"); }
}

class Triangle { // 新增绘制三角形
void draw(){ System.out.println("三角形"); }

}
class GraphicDraw {
void drawgraph(int type){
if(type==1) {
Rectangle rec=new Rectangle();
rec.draw();
} else if(type==2) {
Circle c=new Circle();
c.draw();
} else if(type==3){ // 新增绘制三角形就需要在此处再加一个else if
// 每新加一个需要绘制的图形就需要在这里改动
Triangle t=new Triangle();
t.draw();}
}
}

public class graphDraw {
public static void main(String[] args) {
GraphicDraw graphicdraw=new GraphicDraw();
graphicdraw.drawgraph(3);
}
}
1
三角形

优缺点:

优点是比较好理解,简单易操作

缺点是违反了开闭原则;需要给类增加新功能的时候,尽量不要修改代码,或者尽可能少修改代码。

改进思路:

抽取共用的部分:发现长方形Rectangle和圆形Circle有共同的画图draw方法,创建抽象Shape类做成基类,并提供抽象的draw方法,让子类去实现,当需要新的图形时,只需要让新的图形继承Shape,并实现draw方法。用于测试的画图类GraphicDraw中不出现具体的类,用抽象类Shape。这样使用方的代码就不需要修改,满足开闭原则。

1
2
3
4
//Shape类,基类
abstract class Shape {
public abstract void draw(); //抽象方法
}
1
2
3
class Rectangle extends Shape {
public void draw(){ System.out.println("长方形"); }
}
1
2
3
class Circle extends Shape {
public void draw(){ System.out.println("圆形"); }
}
1
2
3
class Triangle extends Shape { //新增三角形
public void draw(){ System.out.println("三角形"); }
}
1
2
3
class GraphicDraw { // 新增绘制图形不需修改此类
void drawgraph(Shape s){ s.draw(); }
}
1
2
3
4
5
6
7
8
public class graphDraw {
public static void main(String[] args) {
GraphicDraw graphicdraw=new GraphicDraw();
graphicdraw.drawgraph(new Circle());
graphicdraw.drawgraph(new Rectangle());
graphicdraw.drawgraph(new Triangle());
}
}
1
2
3
圆形
长方形
三角形

依赖倒置原则DIP(the Dependency Inversion Principle DIP)

高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象抽象不应该依赖于具体实现,具体实现应该依赖于抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并且能够降低修改程序所造成的风险

反例:

张三现在正在学习《Java基础》和《SpringCloud》的课程,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test1 {
public void studyJava() {
System.out.println("张三正在学习Java基础");
}
public void studySpringCloud() {
System.out.println("张三正在学习SpringCloud");
}

public static void main(String[] args) {
Test1 test1 = new Test1();
test1.studyJava();
test1.studySpringCloud();
}
}
1
2
张三正在学习Java基础
张三正在学习SpringCloud

原因一:有效控制影响范围

张三同学可能会继续学习 AI的课程。这个时候,因为业务的扩展,要从底层实现到高层调用依次地修改代码****。我们需要在Test 类中新增 studyAICourse() 方法,也需要在高层调用中增加调用,这样一来,系统发布后,其实是非常不稳定的。显然在这个简单的例子中,我们还可以自信地认为,我们能 Hold 住这一次的修改带来的影响,因为都是新增的代码,我们回归的时候也可以很好地 cover 住,但实际的情况和实际的软件环境要复杂得多。

最理想的情况就是,我们已经编写好的代码可以 “万年不变”,这就意味着已经覆盖的单元测试可以不用修改,已经存在的行为可以保证保持不变,这就意味着「稳定」。任何代码上的修改带来的影响都是有未知风险的,不论看上去多么简单****。

原因二:增强代码可读性和可维护性

其实加上新增的 AI 课程的学习,他们三节课本质上行为都是一样的,如果我们任由这样行为近乎一样的代码在我们的类里面肆意扩展的话,很快我们的类就会变得臃肿不堪,等到我们意识到不得不重构这个类以缓解这样的情况的时候,或许成本已经变得高得可怕了

原因三:降低耦合

解决耦合度高的办法就是都依赖于抽象。例如我用自己的养的猪去换一台电视,卖电视的商人用我给的猪和手机店老板换他想要的手机,这样耦合度很高,不如我们都依赖于现金,我用猪换现金,给卖电视的商人,商人用现金去手机店老板那里买他想要的手机。

如何实现依赖倒置?

我们现在的代码是上层直接依赖低层实现,现在我们需要定义一个抽象的 ICourse 接口,来对这种强依赖进行解耦:

接下来我们可以参考一下伪代码,先定一个课程的抽象 ICourse 接口:

1
2
3
public interface ICourse {
void study();
}

然后编写分别为 JavaCourse 和 SpringCloudCourse 编写一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JavaCourse implements ICourse {

@Override
public void study() {
System.out.println("张三正在学习Java基础");
}
}

public class SpringCloudCourse implements ICourse {

@Override
public void study() {
System.out.println("张三正在学习SpringCloud");
}
}

然后把 Test 类改造成如下的样子:

1
2
3
4
5
6
7
8
9
10
11
public class Test {

public static void main(String[] args) {
Test test = new Test();
test.study(new JavaCourse());
test.study(new SpringCloudCourse());
}
public void study(ICourse course) {
course.study();
}
}
1
2
张三正在学习Java基础
张三正在学习SpringCloud

这时候我们再来看代码,无论张三的兴趣怎么暴涨,对于新的课程,都只需要新建一个类,通过参数传递的方式告诉它而不需要修改底层的代码****。实际上这有点像大家熟悉的依赖注入的方式了。

总之,切记:以抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此在拿到需求后,要面相接口编程,先顶层设计再细节地设计代码结构。

里式替换原则LSP(the Liskov Substitution Principle LSP)

子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。

里斯替换原则内在含义:

含义一:子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法(核心)

含义二:子类中可以增加自己特有的方法

含义三:前置条件放大, 子类在重载父类的已实现方法时,方法的前置条件(形参)范围应该比父类更加宽松

含义四:后置条件缩小,子类在实现父类的抽象方法(重写)时,方法的后置条件(返回值)范围应该比父类更加严格或相同

理解一:针对1,子类不要修改或覆盖父类已经实现好的方法。

问题:为什么子类不要修改或覆盖父类已经实现好的方法?

违反里氏替换原则定义

违反了里氏替换原则对”子类就是一个父类,父类能够出现的地方子类一定能够出现“的定义,当子类和父类的已实现方法功能不同时,子类就不一定能够完全的替换掉父类(功能不一致)

达不到抽取继承体系中重复代码的目的

我们使用继承的很大部分原因是为了抽取可重用的重复代码,减少同一“类型”中代码的重复定义。如果在一个继承体系中大部分子类都需要覆盖重写父类的方法,那么便达不到减少重复代码的作用

造成方法调用的混乱

父类中已经实现好的方法,相当于是在定义一系列的具体的行为规则(与接口不同,接口是一套行为契约的抽象定义,并且实现类必须实现),虽然父类不要求所有的子类必须遵守这一规则(子类可以修改),但是如果子类随意修改父类的这一”规则”,则造会成整个继承体系的混乱

如果父类中已经实现好的方法被若干个子类随意地进行覆盖重写,那么在该继承体系中就会产生同一方法对应着若干个不同的实现。即,当使用不同子类的多态类型时就会面临着同一方法声明却又不同实现(功能)的场景,这样就会造成整个继承体系地混乱和模糊,尤其是在频繁使用到该继承体系的多态时,就会增大出错的可能性。例如:

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
// 父类
public class Parent {

// 父类中已经实现好的方法: 求和功能
public int function(int a, int b) {
return (a + b);
}
}
// 子类1
public class Son1 extends Parent {

// 覆盖重写父类中的getSum()方法: 相减功能
@Override
public int function(int a, int b) {
return (a - b);
}

}
// 子类2
public class Son2 extends Parent {

// 覆盖重写父类中的getSum()方法: 相除功能
@Override
public int function(int a, int b) {
return (a / b);
}

}
// 测试类:使用父类的原有程序
public class Test1 {

public static void main(String[] args) {

// 1: 使用父类
Parent parent = new Parent();

// 2: 求和
int num = son1.function(1, 2);

}

}
// 测试类:使用子类替换掉父类的新程序
public class Test2 {
public static void main(String[] args) {
// 1: 使用子类替换掉父类
Son1 son1 = new Son1();
// 2: 原程序需要的是求和功能,此时同一方法却变成了相减功能。
int num = son1.function(1, 2);
}
}

综上所述,如果一个继承体系中父类中的部分方法需要频繁的被子类实现,那么还不如在各个子类中分别定义相应的方法。因为,它们不属于重复代码,同时在相应的子类中定义也可以为不同功能的代码定义“见名知意”方法签名。

理解二:针对2,子类可以有自己的“特色”

子类可以在父类的基础之上,增加自己特有的功能。这样子类可以完成更多的功能,并且还可以完全透明的替换掉父类

理解三:针对3,方法重载的规则

1.什么是方法的前置条件和后置条件?

方法,即对具有某一行为或功能的代码封装。前置条件,即方法的形参,是为方法功能的实现传递必须的数据后置条件,即方法的返回值,得到功能方法的最终结果

2.为什么将方法的形参和返回值称为“条件”?

方法的参数和返回值就是在定义该方法的”实现规则”,即实现方法的功能所必须的前提条件以及最终得到什么类型的结果。具体如何实现不管,只要满足方法的前提和结果约束就行。通俗的将就是”完成某一件事,你必须先具有什么,最后必须得到什么”。

3.为什么子类方法抛出的异常必须与父类声明的异常相同或是其子类异常

如果子类方法声明抛出的异常大于父类,那么子类就可能无法完全透明的替换掉父类。比如父类声明抛出的是一个空指针异常,而子类实现却抛出了个Exception异常,那么在使用子类替换父类时,原有程序的异常捕获机制可能就无法控制子类方法抛出的异常。

4.为什么子类实现的方法返回值类型必须与父类相同或缩小

如果子类实现的返回值大于父类,那么子类可能就无法完全透明的替换掉父类。比如父类声明返回值为List,子类却返回一个Collection(返回值范围大于List),那么原程序可能就无法使用子类的返回值。此时子类便有可能不能完全透明的替换掉父类,即子类替换掉父类后可能会影响到原程序的运行状况,并且打破了原有方法的后置条件约束。

值传递和引用传递

值传递(pass by value):在调用函数时,将实际参数复制一份传递到函数中,这样在函数中对参数进行修改,就不会影响到原来的实际参数

引用传递(pass by reference):在调用函数时,将实际参数的地址直接传递到函数中。这样在函数中对参数进行的修改,就会影响到实际参数

Java中只有值传递,只不过对于引用类型参数,值传递的是对象的引用

值传递

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

public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
System.out.println("swap()函数交换完毕之前 num1 = " + num1);
System.out.println("swap()函数交换完毕之前 num2 = " + num2);
swap(num1, num2);
System.out.println("swap()函数交换完毕之后 num1 = " + num1);
System.out.println("swap()函数交换完毕之后 num2 = " + num2);
}

public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;

System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
1
2
3
4
5
6
swap()函数交换完毕之前 num1 = 10
swap()函数交换完毕之前 num2 = 20
a = 20
b = 10
swap()函数交换完毕之后 num1 = 10
swap()函数交换完毕之后 num2 = 20

可以看出,虽然在swap()方法中a,b的值做了交换,但是主方法中num1,num2的值并未改变。

引用类型传递

数组类型实践

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ValueTransport2 {

public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
System.out.println("调用change前arr数组第一个元素为:" + arr[0]);
change(arr);
System.out.println("调用change后arr数组第一个元素为:" + arr[0]);
}

public static void change(int[] array) {
array[0] = 0;
}
}
1
2
调用change前arr数组第一个元素为:1
调用change后arr数组第一个元素为:0

可以看出,在change()方法中将数组的第一个元素改为0,主方法中数组的第一个元素也跟着变为0。

String类型实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ValueTransport3 {
public static void main(String[] args) {
String str = new String("B站:不吃辣的Chris");
System.out.println("更新字符串str前str为:" + str);
System.out.println("更新字符串str前哈希码值为:" + System.identityHashCode(str));
changeString(str);
System.out.println("更新字符串str后str为:" + str);
System.out.println("更新字符串str后哈希码值为:" + System.identityHashCode(str));
}

public static void changeString(String string) {
string = "抖音:不吃辣的Chris";
System.out.println("changeString()方法中更新字符串str后哈希码值为:" + System.identityHashCode(string));
}
}
1
2
3
4
5
更新字符串str前str为:B站:不吃辣的Chris
更新字符串str前哈希码值为:1300109446
changeString()方法中更新字符串str后哈希码值为:1020371697
更新字符串str后str为:B站:不吃辣的Chris
更新字符串str后哈希码值为:1300109446

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ValueTransport3 {
public static void main(String[] args) {
String str = new String("B站:不吃辣的Chris");
System.out.println("更新字符串str前str为:" + str);
System.out.println("更新字符串str前哈希码值为:" + System.identityHashCode(str));
changeString(str);
System.out.println("更新字符串str后str为:" + str);
System.out.println("更新字符串str后哈希码值为:" + System.identityHashCode(str));
}

public static void changeString(String string) {
// string = "抖音:不吃辣的Chris";
// System.out.println("changeString()方法中更新字符串str后哈希码值为:" + System.identityHashCode(string));
string = new String("抖音:不吃辣的Chris");
System.out.println("changeString()方法中更新字符串str后哈希码值为:" + System.identityHashCode(string));
}
}
1
2
3
4
5
更新字符串str前str为:B站:不吃辣的Chris
更新字符串str前哈希码值为:1300109446
changeString()方法中更新字符串str后哈希码值为:1020371697
更新字符串str后str为:B站:不吃辣的Chris
更新字符串str后哈希码值为:1300109446

System.identityHashCode()函数返回指定对象的Hash码值,这个值在对象的生命周期中不变。可以实践得知:传入的入参变量指针指向了新的对象的地址,但主函数中原来的变量str指向的地址没有变化。

自定义类型实践:

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
package ObjectOrientedTest;
public class People {

public int age;

public String name;

public People(int age, String name) {
this.age = age;
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ObjectOrientedTest;

public class PassByValue {
public static void main(String[] args) {
People p1 = new People(12,"Chris");
System.out.println("更新年龄前" + p1.getName() + "的年龄为:" + p1.getAge());
updateAge(p1);
System.out.println("更新年龄后" + p1.getName() + "的年龄为:" + p1.getAge());
}

public static void updateAge(People p) {
p.setAge(16);
}
}
1
2
更新年龄前B站:不吃辣的Chris的年龄为:12
更新年龄后B站:不吃辣的Chris的年龄为:16
1
2
3
4
5
6
7
8
9
10
11
12
13
public class PassByValue {
public static void main(String[] args) {
People p1 = new People(12,"B站:不吃辣的Chris");
System.out.println("更新年龄前" + p1.getName() + "的年龄为:" + p1.getAge());
updateAge(p1);
System.out.println("更新年龄后" + p1.getName() + "的年龄为:" + p1.getAge());
}

public static void updateAge(People p) {
p = new People(15,"B站:不吃辣的Chris");
p.setAge(16);
}
}
1
2
更新年龄前B站:不吃辣的Chris的年龄为:12
更新年龄后B站:不吃辣的Chris的年龄为:12

接口和抽象类

接口的意义

接口简单来说就是一组方法的声明这些方法没有方法体,所有实现这个接口的类都必须要实现这些方法接口不关心具体实现细节,只关心类的行为是否符合约定。因此,接口可以作为一个标准,定义了类应该遵循的规则和约定

Java中面向对象编程的一个重要概念就是“接口隔离原则”,即一个类只需依赖它需要的接口,而不需要依赖其它接口。这使得程序更具有可扩展性可维护性降低代码的耦合度

实践:

1
2
3
4
5
public interface Animal {
void eat(); // 吃饭
void sleep(); // 睡觉
void run(); // 奔跑
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("猫吃饭");
}

@Override
public void sleep() {
System.out.println("猫睡觉");
}

@Override
public void run() {
System.out.println("猫奔跑");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("狗吃饭");
}

@Override
public void sleep() {
System.out.println("狗睡觉");
}

@Override
public void run() {
System.out.println("狗奔跑");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
cat.sleep();
cat.run();

Dog dog = new Dog();
dog.eat();
dog.sleep();
dog.run();
}
}
1
2
3
4
5
6
猫吃饭
猫睡觉
猫奔跑
狗吃饭
狗睡觉
狗奔跑

抽象类的意义

简单概括:对代码的维护重用

抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。具体分析如下:

1.因为抽象类不能实例化对象,所以必须要有子类来继承它之后才能使用。这样就可以把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护****。

比如猫和狗可以抽象成动物,他们有相同的属性和方法,例如都有耳朵,都有睡觉的行为。这样当你对其中某个类进行修改时会受到父类的限制,这样就会提醒开发人员有些东西不能进行随意修改,这样可以对比较重要的东西进行统一的限制,也算是一种保护,对维护会有很大的帮助。
2.当又有一个具有相似的组件产生时,只需要实现该抽象类就可以获得该抽象类的那些属性和方法。
比如又出现了松鼠这类动物,那么松鼠直接继承动物,然后对自己特有的属性和方法进行补充即可。这样对于代码的重用也是很好的体现。

所以,Java中抽象类对于代码的维护和重用有很好的帮助,也是Java面向对象的一个重要体现。

实践:

1
2
3
4
5
6
7
public abstract class Animal {
public void sleep(){
System.out.println("休息~");
}

public abstract void eat();
}
1
2
3
4
5
6
7
8
public class Dog extends Animal{

@Override
public void eat() {
System.out.println("狗吃骨头~");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼~");
}
public static void main(String[] args) {
Cat cat = new Cat();
cat.sleep();
cat.eat();
Dog dog = new Dog();
dog.sleep();
dog.eat();
}
}
1
2
3
4
休息~
猫吃鱼~
休息~
狗吃骨头~

接口和抽象类的区别

比较 抽象类 接口
默认方法 抽象类可以有默认的方法实现 Java 8之前接口中不存在方法的实现
实现方式 子类使用extends关键字来继承抽象类,如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 实现类使用implements来实现接口,需要提供接口中所有声明的方法实现
构造器 抽象类中可以有构造器 接口中不能
访问修饰符 抽象方法可以由public,protected和default等修饰 接口默认是public,不能使用其他修饰符
多继承 一个子类只能存在一个父类 一个子类可以实现多个接口
访问新方法 抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 如果往接口中添加新方法,则子类中需要实现该方法

可变对象与不可变对象

什么是不可变对象

不可变对象指对象一旦被创建,它的状态(对象的数据,也即对象属性值)就不能再改变。任何修改都会创建一个新的对象,如 String、Integer、BigInteger和BigDecimal及其它包装类。反之即为可变对象(Mutable Objects),如Date。

不可变对象优点

1.构造、使用和测试都很简单

2.线程安全且没有同步问题,不需要担心数据会被其它线程修改

3.当用作类的属性时不需要保护性拷贝

4.可以很好的用作Map键值和Set元素

不可变对象的缺点

创建对象的开销大,因为每一步操作都会产生一个新的对象。

实践

反例

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
import lombok.AllArgsConstructor;
import java.io.IOException;
import java.util.Date;

public class CovertyTest {

public static void main(String[] args) throws IOException {
Emplee emplee = new Emplee(new Date(),1,new Integer(3));
Date d = emplee.getHireDate();
int i = emplee.getI();
Integer i2 = emplee.getI2();
System.out.println("emplee的i值为:" + emplee.getI());
System.out.println("emplee的i2值为:" + emplee.getI2());
System.out.println("emplee的hireDate值为:" + emplee.getHireDate());
System.out.println("i2为" + i2);
System.out.println("d为" + d);
System.out.println("=============================================");
double ten = 10 * 365 * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() - (long) ten);
i = 10;
i2 = new Integer(6);
System.out.println("emplee的i值为:" + emplee.getI());
System.out.println("emplee的i2值为:" + emplee.getI2());
System.out.println("emplee的hireDate值为:" + emplee.getHireDate());
System.out.println("i2为" + i2);
System.out.println("d为" + d);
}
}

@AllArgsConstructor
class Emplee {
public Emplee() {
hireDate = new Date();
}
private Date hireDate;
private int i;
private Integer i2;

public int getI() {
return i;
}

public Integer getI2() {
return i2;
}

public Date getHireDate() {
return hireDate;
}
}
1
2
3
4
5
6
7
8
9
10
11
emplee的i值为:1
emplee的i2值为:3
emplee的hireDate值为:Sun Jan 21 09:54:55 CST 2024
i2为3
d为Sun Jan 21 09:54:55 CST 2024
=============================================
emplee的i值为:1
emplee的i2值为:3
emplee的hireDate值为:Sun Dec 31 06:18:28 CST 2023
i2为6
d为Sun Dec 31 06:18:28 CST 2023

正例

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
import lombok.AllArgsConstructor;

import java.io.IOException;
import java.util.Date;

public class CovertyTest {

public static void main(String[] args) throws IOException {
Emplee emplee = new Emplee(new Date(),1,new Integer(3));
Date d = emplee.getHireDate();
int i = emplee.getI();
Integer i2 = emplee.getI2();
System.out.println("emplee的i值为:" + emplee.getI());
System.out.println("emplee的i2值为:" + emplee.getI2());
System.out.println("emplee的hireDate值为:" + emplee.getHireDate());
System.out.println("i2为" + i2);
System.out.println("d为" + d);
System.out.println("=============================================");
double ten = 10 * 365 * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() - (long) ten);
i = 10;
i2 = new Integer(6);
System.out.println("emplee的i值为:" + emplee.getI());
System.out.println("emplee的i2值为:" + emplee.getI2());
System.out.println("emplee的hireDate值为:" + emplee.getHireDate());
System.out.println("i2为" + i2);
System.out.println("d为" + d);
}
}

@AllArgsConstructor
class Emplee {
public Emplee() {
hireDate = new Date();
}
private Date hireDate;
private int i;
private Integer i2;

public int getI() {
return i;
}

public Integer getI2() {
return i2;
}

public Date getHireDate() {
return (Date)hireDate.clone();
}

}
1
2
3
4
5
6
7
8
9
10
11
emplee的i值为:1
emplee的i2值为:3
emplee的hireDate值为:Sun Jan 21 09:58:09 CST 2024
i2为3
d为Sun Jan 21 09:58:09 CST 2024
=============================================
emplee的i值为:1
emplee的i2值为:3
emplee的hireDate值为:Sun Jan 21 09:58:09 CST 2024
i2为6
d为Sun Dec 31 06:21:41 CST 2023

深拷贝、浅拷贝和引用拷贝是什么?

  • 引用拷贝两个不同的引用指向****同一个对象
  • 浅拷贝:浅拷贝会在堆上创建一个****新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制****内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象
1
2
3
4
5
6
7
8
9
10
11
12
@AllArgsConstructor
public class Address implements Cloneable{
private String name;
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@AllArgsConstructor
@Data
public class Person implements Cloneable {
private Address address;
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}

public static void main(String[] args) {
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
System.out.println(person1.getAddress() == person1Copy.getAddress());
}
}
1
true

深拷贝:这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@AllArgsConstructor
@Data
public class Person implements Cloneable {
private Address address;
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
person.setAddress(person.getAddress().clone());
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}

public static void main(String[] args) {
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
System.out.println(person1.getAddress() == person1Copy.getAddress());
}
}
1
false

从输出结构就可以看出,显然 person1 的克隆对象和 person1 包含的 Address 对象已经是不同的了。

== 和 equals() 的区别

== 对于基本类型和引用类型的作用效果是不同的

  • 对于基本数据类型来说,**== 比较的是值**。
  • 对于引用数据类型来说,**== 比较的是对象的内存地址**。

因为** Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是**引用类型变量存的值是对象的地址

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法,Object 类中的equals() 方法:

1
2
3
4
public boolean equals(Object obj) {
// 比较的是地址
return (this == obj);
}

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法
  • 类重写了 equals()方法:一般我们都重写 equals()方法来比较两个对象中的属性是否相等,若它们的属性相等,则返回 true(即,认为这两个对象相等)。

为什么重写 equals() 时必须重写 hashCode() 方法?

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等

实践:

重写 equals() 时没有重写 hashCode() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Objects;

@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
}
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
import java.util.HashSet;

public class EqualsMain {

public static void main(String[] args) {
// 创建 Person 对象
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Bob", 30);
Person person3 = new Person("Alice", 25);

// 重写的 equals() 方法会判断对象内容是否相等
System.out.println("person1.equals(person2): " + person1.equals(person2)); // false
System.out.println("person1.equals(person3): " + person1.equals(person3)); // true

// 未重写的 hashCode() 方法会生成不相同的哈希码
System.out.println("person1.hashCode(): " + person1.hashCode());
System.out.println("person3.hashCode(): " + person3.hashCode());

// 使用 HashSet 存储 Person 对象
HashSet<Person> set = new HashSet<>();
set.add(person1);
set.add(person2);
set.add(person3);

// HashSet 会根据 equals() 和 hashCode() 方法来判断对象是否重复
System.out.println("HashSet size: " + set.size()); // 3
System.out.println("HashSet contains person1: " + set.contains(person1)); // true
System.out.println("HashSet contains person2: " + set.contains(person2)); // true
System.out.println("HashSet contains person3: " + set.contains(person3)); // true
}
}
1
2
3
4
5
6
7
8
person1.equals(person2): false
person1.equals(person3): true
person1.hashCode(): 789451787
person3.hashCode(): 1950409828
HashSet size: 3
HashSet contains person1: true
HashSet contains person2: true
HashSet contains person3: true

重写 equals() 时 且 重写了 hashCode() 方法

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
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Objects;

@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
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
import java.util.HashSet;

public class EqualsMain {

public static void main(String[] args) {
// 创建 Person 对象
Person person1 = new Person("Alice", 25);
Person person2 = new Person("Bob", 30);
Person person3 = new Person("Alice", 25);

// 重写的 equals() 方法会判断对象内容是否相等
System.out.println("person1.equals(person2): " + person1.equals(person2)); // false
System.out.println("person1.equals(person3): " + person1.equals(person3)); // true

// 重写的 hashCode() 方法会根据对象内容生成相同的哈希码
System.out.println("person1.hashCode(): " + person1.hashCode());
System.out.println("person3.hashCode(): " + person3.hashCode());

// 使用 HashSet 存储 Person 对象
HashSet<Person> set = new HashSet<>();
set.add(person1);
set.add(person2);
set.add(person3);

// HashSet 会根据 equals() 和 hashCode() 方法来判断对象是否重复
System.out.println("HashSet size: " + set.size()); // 2
System.out.println("HashSet contains person1: " + set.contains(person1)); // true
System.out.println("HashSet contains person2: " + set.contains(person2)); // true
System.out.println("HashSet contains person3: " + set.contains(person3)); // true
}
}
1
2
3
4
5
6
7
8
person1.equals(person2): false
person1.equals(person3): true
person1.hashCode(): 1963862394
person3.hashCode(): 1963862394
HashSet size: 2
HashSet contains person1: true
HashSet contains person2: true
HashSet contains person3: true

总结

  • equals 方法判断两个对象是相等的,那开发者也要必须保证这两个对象的 hashCode 值相等
  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)

String、StringBuffer、StringBuilder 的区别?

区别

可变性

String 是不可变的StringBuilder 与 StringBuffer 可变,且都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。

线程安全性

String** 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer**** 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的**。

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将****指针指向新的 String 对象StringBuffer 和 StringBuilder 每次都会****对对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder** 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险**。

对于三者使用的总结:

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

字符串拼接用“+” 还是 StringBuilder?

字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StringTestMain {

public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
long startTime = System.currentTimeMillis();
String s = "";
for (int j = 0;j < 10000;j++) {
for (int i = 0; i < arr.length; i++) {
s += arr[i];
}
}
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
System.out.println("代码执行耗时:" + costTime + "毫秒");
// System.out.println(s);
}
}

1
代码执行耗时:1223毫秒

StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。如果****直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题了

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
public class StringTestMain {
public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
s.append(value);
}
System.out.println(s);
}
}

public class StringTestMain {
public static void main(String[] args) {
String[] arr = {"he", "llo", "world"};
long startTime = System.currentTimeMillis();
StringBuilder s = new StringBuilder();
for (int i = 0;i < 10000000;i++) {
for (String value : arr) {
s.append(value);
}
}
long endTime = System.currentTimeMillis();
long costTime = endTime - startTime;
System.out.println("代码执行耗时:" + costTime + "毫秒");
// System.out.println(s);
}
}
1
代码执行耗时:0毫秒

String s1 = new String(“abc”);这句话创建了几个字符串对象?

会创建 1 或 2 个字符串对象。

  • 如果字符串常量池中不存在字符串对象“abc”的引用,那么它将首先在字符串常量池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象
  • 如果字符串常量池中已存在字符串对象“abc”的引用则直接引用已有的对象,所以只会在堆中创建 1 个字符串对象“abc”

final finally finalize区别

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承修饰方法表示该方法不能被重写、****修饰变量表示该变量是一个常量不能被重新赋值
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码
  • finalize是一个方法,并且是属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,对象在被垃圾回收之前垃圾回收器会调用finalize()方法(如果重写了该方法),****可以在 finalize() 方法中编写一些清理资源的代码

final示例:

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
public final class FinalClass {
// ...
}

public class SubClass extends FinalClass { // 编译错误,无法继承 final 类

}

public class Parent {
public final void finalMethod() {
// ...
}
}

public class Child extends Parent {
// 编译错误,无法重写 final 方法
// public void finalMethod() {
// // ...
// }
}

public class Example {
public static final int MAX_VALUE = 100;

public void method() {
final int localVar = 10;
// localVar = 20; // 编译错误,无法修改 final 变量的值
// ...
// final 修饰的基本类型变量,引用类型变量,
// 可以修改引用变量所引用的对象的内容,但是不能对引用的对象重新赋值操作;
//正确
final int[] arr={5,6,7,8};
arr[2]=-8;
//错误
arr=null;
//错误
final Person p=new Person();
p=null;
}
}

finally示例:

一般在finally中执行一些数据库关闭、文件流关闭、释放线程等操作。

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class finallyTest {

public static void main(String[] args) {
finallyTest finallyTest = new finallyTest();
System.out.println("==========finallyFunction1()==================");
finallyTest.finallyFunction1();
System.out.println("==========finallyFunction2()==================");
finallyTest.finallyFunction2();
System.out.println("==========finallyFunction3()==================");
// try语句中有返回值不影响finally正常执行
String s = finallyTest.finallyFunction3();
System.out.println("s:" + s);
System.out.println("==========finallyFunction4()==================");
// catch语句中有返回值不影响finally的正常运行
String s1 = finallyTest.finallyFunction4();
System.out.println("s1:" + s1);
System.out.println("==========finallyFunction5()==================");
// finallu语句中有返回值影响finally的正常运行
String s2 = finallyTest.finallyFunction5();
System.out.println("s2:" + s2);

System.out.println("==========finallyFunction6(1)==================");
// 遇到System.exit(0)会跳过fianlly语句结束方法 Sytstem.exit();
// 实际上调用了Runtime的方法,执行时可以等同于Runtime.getRuntime().exit() 用来终止当前运行的虚拟机
finallyTest.finallyFunction6(1);
System.out.println("==========finallyFunction6(0)==================");
// 遇到System.exit(0)会跳过fianlly语句结束方法 语句在try代码块还是catch代码块中被执行,都越过finally结束了
finallyTest.finallyFunction6(0);
}


public void finallyFunction1() {
try {
System.out.println("这是try代码块");
throw new Exception();
} catch (Exception e) {
System.out.println("这是catch代码块");
} finally {
System.out.println("这是finally代码块");
}
}

public void finallyFunction2() {
try {
System.out.println("这是try代码块");
} finally {
System.out.println("这是finally代码块");
}
}

public String finallyFunction3() {
try {
System.out.println("这是try代码块");
return "这是try中的返回值";
} catch (Exception e) {
System.out.println("这是catch代码块");
} finally {
System.out.println("这是finally代码块");
}
return "这是try外侧的返回值";
}

public String finallyFunction4() {
try {
System.out.println("这是try代码块");
throw new Exception();
} catch (Exception e) {
System.out.println("这是catch代码块");
return "这是catch中的返回值";
} finally {
System.out.println("这是finally代码块");
}
}

public String finallyFunction5() {
try {
System.out.println("这是try代码块");
return "这是try中的返回值";
} catch (Exception e) {
System.out.println("这是catch代码块");
} finally {
System.out.println("这是finally代码块");
return "这是finally中的返回值";
}
}

private void finallyFunction6(int n) {
try {
if (n == 1) {
System.out.println("这是try代码块");
throw new Exception();
}
System.exit(0);
} catch (Exception e) {
System.out.println("这是catch代码块");
System.exit(0);
} finally {
System.out.println("这是finally代码块");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
==========finallyFunction1()==================
这是try代码块
这是catch代码块
这是finally代码块
==========finallyFunction2()==================
这是try代码块
这是finally代码块
==========finallyFunction3()==================
这是try代码块
这是finally代码块
s:这是try中的返回值
==========finallyFunction4()==================
这是try代码块
这是catch代码块
这是finally代码块
s1:这是catch中的返回值
==========finallyFunction5()==================
这是try代码块
这是finally代码块
s2:这是finally中的返回值
==========finallyFunction6(1)==================
这是try代码块
这是catch代码块
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
public class finallyTest02 {

public static void main(String[] args) {
finallyTest02 finallyTest = new finallyTest02();
System.out.println("==========finallyFunction6(0)==================");
// 遇到System.exit(0)会跳过fianlly语句结束方法
// 语句在try代码块还是catch代码块中被执行,都越过finally结束了
finallyTest.finallyFunction6(0);
}

private void finallyFunction6(int n) {
try {
if (n == 1) {
System.out.println("这是try代码块");
throw new Exception();
}
System.exit(0);
} catch (Exception e) {
System.out.println("这是catch代码块");
System.exit(0);
} finally {
System.out.println("这是finally代码块");
}
}
}
1
==========finallyFunction6(0)==================

finalize示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyObject {
@Override
protected void finalize() throws Throwable {
try {
// 清理资源的操作
} finally {
super.finalize();
}
}
}

public class Example {
public static void main(String[] args) {
MyObject obj = new MyObject();
// 让对象成为垃圾,触发垃圾回收
obj = null;
// 请求启动垃圾回收
System.gc();
}
}

注意,在实际开发中,不建议过多地依赖 finalize() 方法来进行资源清理,因为它的调用时机是不确定的,并且可能导致性能问题。应该使用 try-finally 或 try-with-resources 语句块来确保资源的正确释放。try-with-resources 语句块是在 Java 7 中引入的,用于自动管理资源(如文件或数据库连接),无需手动关闭资源。使用 try-with-resources 语句块的格式如下:

1
2
3
4
5
6
7
8
try (Resource1 res1 = new Resource1();
Resource2 res2 = new Resource2();
// ...
ResourceN resN = new ResourceN()) {
// 使用资源的代码
} catch (Exception e) {
// 处理异常
}

其中,Resource1、Resource2、…、ResourceN 均为实现了 AutoCloseable 接口的资源对象,这些对象在 try 语句块结束后会自动调用它们的 close() 方法释放资源。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.BufferedReader;
import java.io.FileReader;

public class Example {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

包装类的缓存机制

Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。Byte,Short,Integer,Long** 这 4 种包装类默认创建了数值**** [-128,127] *的相应类型的缓存数据*Character ****创建了数值在 ****[0,127] *范围的缓存数据,Boolean 直接返回 True or False。如果*超出对应范围仍然会去创建新的对象**,缓存的范围区间的大小只是在性能和资源之间的权衡。两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。

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
79
80
81
82
83
84
85
86
87
88
89
90
public class packageTest {

public static void main(String[] args) {
System.out.println("==============Byte缓存机制实践================================");
Byte b11 = Byte.valueOf((byte) 127);
Byte b21 = Byte.valueOf((byte) 127);
System.out.println("valueOf方式创建对象");
System.out.println(b11 == b21);

Byte b12 = 127; // 自动装箱
Byte b22 = 127;
System.out.println("自动装箱方式创建对象");
System.out.println(b12 == b22);

Byte b13 = new Byte((byte) 128); // 超出127则无法利用缓存机制
Byte b23 = new Byte((byte) 128);
System.out.println("无法利用缓存机制情景");
System.out.println(b13 == b23);

System.out.println("==============Short缓存机制实践================================");
Short s11 = Short.valueOf((short) 127);
Short s21 = Short.valueOf((short) 127);
System.out.println("valueOf方式创建对象");
System.out.println(s11 == s21);

Short s12 = 127; // 自动装箱
Short s22 = 127;
System.out.println("自动装箱方式创建对象");
System.out.println(s12 == s22);

Short s13 = new Short((short) 128); // 超出127则无法利用缓存机制
Short s23 = new Short((short) 128);
System.out.println("无法利用缓存机制情景");
System.out.println(s13 == s23);

System.out.println("==============Integer缓存机制实践================================");
Integer i11 = Integer.valueOf(127);
Integer i21 = Integer.valueOf(127);
System.out.println("valueOf方式创建对象");
System.out.println(i11 == i21);

Integer i12 = 127; // 自动装箱
Integer i22 = 127;
System.out.println("自动装箱方式创建对象");
System.out.println(i12 == i22);

Integer i13 = new Integer(128); // 超出127则无法利用缓存机制
Integer i23 = new Integer(128);
System.out.println("无法利用缓存机制情景");
System.out.println(i13 == i23);

System.out.println("==============Long缓存机制实践================================");
Long l11 = Long.valueOf(127);
Long l21 = Long.valueOf(127);
System.out.println("valueOf方式创建对象");
System.out.println(l11 == l21);

Long l12 = 127L; // 自动装箱
Long l22 = 127L;
System.out.println("自动装箱方式创建对象");
System.out.println(l12 == l22);

Long l13 = new Long(128); // 超出127则无法利用缓存机制
Long l23 = new Long(128);
System.out.println("无法利用缓存机制情景");
System.out.println(l13 == l23);

System.out.println("==============Character缓存机制实践================================");
Character c11 = Character.valueOf((char) 127);
Character c21 = Character.valueOf((char) 127);
System.out.println("valueOf方式创建对象");
System.out.println(c11 == c21);

Character c12 = 127; // 自动装箱
Character c22 = 127;
System.out.println("自动装箱方式创建对象");
System.out.println(c12 == c22);

Character c13 = new Character((char) 128); // 超出127则无法利用缓存机制
Character c23 = new Character((char) 128);
System.out.println("无法利用缓存机制情景");
System.out.println(c13 == c23);

System.out.println("==============Boolean缓存机制实践================================");
Boolean b1 = true; // 自动装箱
Boolean b2 = true;
System.out.println("自动装箱方式创建对象");
System.out.println(b1 == b2);
}
}
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
==============Byte缓存机制实践================================
valueOf方式创建对象
true
自动装箱方式创建对象
true
无法利用缓存机制情景
false
==============Short缓存机制实践================================
valueOf方式创建对象
true
自动装箱方式创建对象
true
无法利用缓存机制情景
false
==============Integer缓存机制实践================================
valueOf方式创建对象
true
自动装箱方式创建对象
true
无法利用缓存机制情景
false
==============Long缓存机制实践================================
valueOf方式创建对象
true
自动装箱方式创建对象
true
无法利用缓存机制情景
false
==============Character缓存机制实践================================
valueOf方式创建对象
true
自动装箱方式创建对象
true
无法利用缓存机制情景
false
==============Boolean缓存机制实践================================
自动装箱方式创建对象
true

线程进程

进程是什么?

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

线程是什么?

线程与进程相似,但线程是一个比进程更小的执行单位,一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的****程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

实验验证:一个 Java 程序的运行是 main 线程和多个其他线程同时运行

1
2
3
4
5
6
7
8
9
10
11
12
public class ThreadTest {
public static void main(String[] args) {
// 获取 Java 线程管理 MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程 ID 和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
}
}
}
1
2
3
4
5
6
[6]Monitor Ctrl-Break
[5]Attach Listener
[4]Signal Dispatcher
[3]Finalizer
[2]Reference Handler
[1]main

[6] Monitor Ctrl-Break:用于接收来自键盘上的 Ctrl-Break 组合键的信号。当用户按下 Ctrl-Break 组合键时,该线程会被激活并执行相应的操作。

[5] Attach Listener:这是 JVM 中的一个内部线程,用于监听和处理与虚拟机的附加操作相关的事件。它主要用于支持 Java 调试工具和监控工具等。

[4] Signal Dispatcher:这也是 JVM 中的一个内部线程,用于接收操作系统发送的信号,并将其转发给 JVM 的其他线程进行处理。它主要用于处理例如 SIGTERM、SIGINT 等信号。

[3] Finalizer负责执行对象的 finalize 方法finalize 方法在对象被垃圾回收之前会被调用一次,用于释放对象的资源或执行一些清理操作

[2] Reference Handler用于处理引用队列中的引用对象。当对象被垃圾回收器标记为即将回收时,引用对象会被放入引用队列中,Reference Handler 线程会从队列中取出引用对象并执行相应的操作。

[1] main:是 Java 程序的主线程,从 main 方法开始执行,并且是程序中的入口点。在这个线程中,程序会按照顺序执行 main 方法中的代码,创建其他线程,或者执行其他需要执行的操作。

这些线程大多数都是 JVM 内部使用的特殊线程,对于一般的 Java 应用程序来说,主要关注的是主线程(main)以及根据应用程序需求创建的其他自定义线程。

进程与线程的关系

一个进程中可以有多个线程,多个线程共享进程方法区 (JDK1.8 之后的元空间)资源****,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

总结:线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反

程序计数器为什么是私有的?

程序计数器主要有下面两个作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了

需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

虚拟机栈和本地方法栈为什么是私有的?

  • 虚拟机栈****: 每个 Java 方法在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
  • 本地方法栈****: 和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务****。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

堆和方法区

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

并发与并行的区别

  • 并发:两个及两个以上的作业在同一 时间段 内执行。
  • 并行:两个及两个以上的作业在同一 时刻 执行。

最关键的点是:是否是 同时 执行。

同步和异步的区别

  • 同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待
  • 异步:调用在发出之后,不用等待返回结果,该调用直接返回

为什么要使用多线程?

  • 单核时代:在单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率。 假设只运行了一个 Java 进程的情况,当我们请求 IO 的时候,如果 Java 进程中只有一个线程,此线程被 IO 阻塞则整个进程被阻塞。CPU 和 IO 设备只有一个在运行,那么可以简单地说系统整体效率只有 50%。当使用多线程的时候,一个线程被 IO 阻塞,其他线程还可以继续使用 CPU。从而提高了 Java 进程利用系统资源的整体效率。
  • 多核时代: 多核时代多线程主要是为了提高进程利用多核 CPU 的能力。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,不论系统有几个 CPU 核心,都只会有一个 CPU 核心被利用到。而创建多个线程,这些线程可以被映射到底层多个 CPU 上执行,在任务中的多个线程没有资源竞争的情况下,任务执行的效率会有显著性的提高,约等于(单核时执行时间/CPU 核心数)。

使用多线程可能带来什么问题?

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。

如何理解线程安全和不安全?

  • 线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。
  • 线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失

Comparable接口和Comparator接口实现排序的使用方式

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
import java.util.*;

/**
* @author 不吃辣的Chris
* @create 2024-03-26-22:43
*/
public class ComparatorTest {

public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(12);
arrayList.add(5);
arrayList.add(100);
arrayList.add(40);
arrayList.add(66);
arrayList.add(30);
arrayList.add(21);
arrayList.add(80);

List<User> userList = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setAge(random.nextInt(100));
user.setScore(random.nextInt(100));
userList.add(user);
}

// TreeMap中key有序
TreeMap<Student, String> studentStringTreeMap = new TreeMap<Student, String>();
for (int i = 0; i < 10; i++) {
studentStringTreeMap.put(new Student(random.nextInt(100)), i+"");
}


System.out.println("原数组:");
System.out.println(arrayList);
System.out.println("原用户列表");
userList.forEach(c -> {
System.out.println(c);
});


// 自定义排序规则
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});

// 自定义排序规则
Collections.sort(userList, new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o2.getAge().compareTo(o1.getAge());
}
});

System.out.println("排序后:");
System.out.println(arrayList);
userList.forEach(c -> {
System.out.println(c);
});
// 得到key的值的同时得到key所对应的值
Set<Student> keys = studentStringTreeMap.keySet();
for (Student key : keys) {
System.out.println(key);
}
}

}
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
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @author 不吃辣的Chris
* @create 2024-03-26-22:51
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Comparable<Student>{

private Integer score;

/**
* 重写compareTo方法实现按年龄来排序
*/
@Override
public int compareTo(Student student) {
if (this.score > student.getScore()) {
return 1;
}
if (this.score < student.getScore()) {
return -1;
}
return 0;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.Data;

/**
* @author 不吃辣的Chris
* @create 2024-03-26-22:45
*/
@Data
public class User {

private Integer age;

private Integer score;

}
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
原数组:
[12, 5, 100, 40, 66, 30, 21, 80]
原用户列表
User(age=43, score=71)
User(age=12, score=34)
User(age=10, score=98)
User(age=77, score=63)
User(age=46, score=42)
User(age=50, score=22)
User(age=26, score=53)
User(age=48, score=16)
User(age=82, score=96)
User(age=40, score=1)
排序后:
[100, 80, 66, 40, 30, 21, 12, 5]
User(age=82, score=96)
User(age=77, score=63)
User(age=50, score=22)
User(age=48, score=16)
User(age=46, score=42)
User(age=43, score=71)
User(age=40, score=1)
User(age=26, score=53)
User(age=12, score=34)
User(age=10, score=98)
Student(score=10)
Student(score=12)
Student(score=24)
Student(score=40)
Student(score=42)
Student(score=48)
Student(score=49)
Student(score=62)
Student(score=86)
Student(score=99)

Optional使用方式实践

在项目实践的过程中,我们经常需要处理多层if-else判空的问题,防止运行时出现空指针异常,这样做保证代码稳健性的出发点是十分值得肯定的,但当if-else层级过多时,代码不是很优雅,可读性也略差,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author 不吃辣的Chris
* @create 2024-03-31-21:47
*/
class Person {
private HomeAddress homeAddress;

public Person(HomeAddress homeAddress) {
this.homeAddress = homeAddress;
}

public HomeAddress getHomeAddress() {
return homeAddress;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author 不吃辣的Chris
* @create 2024-03-31-21:47
*/
class HomeAddress {
private Country country;

public HomeAddress(Country country) {
this.country = country;
}

public Country getCountry() {
return country;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author 不吃辣的Chris
* @create 2024-03-31-21:47
*/
class Country {
private String name;

public Country(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
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
/**
* @author 不吃辣的Chris
* @create 2024-03-31-21:46
*/
import java.util.Objects;
import java.util.Optional;

public class PersonHomeAddressExample {
public static void main(String[] args) {
// 示例的 Person 对象
Person person = getPerson();

if (Objects.nonNull(person)) {
HomeAddress homeAddress = person.getHomeAddress();
if (Objects.nonNull(homeAddress)) {
Country country = homeAddress.getCountry();
if (Objects.nonNull(country)) {
String name = country.getName();
if (Objects.nonNull(name)) {
System.out.println("if-else方式-字母转为大写后的城市名为: " + name.toUpperCase());
}
}
}
}
}
1
if-else方式-字母转为大写后的城市名为: BEIJING

最终是可以正常返回结果,但代码嵌套层数过多。

Optional 用于处理一个值可能存在或不存在的问题,避免运行时出现空指针异常问题,提高程序健壮性

常用方法

  • isPresent()检查值是否存在
  • get()获取值,如果值不存在会抛出 NoSuchElementException 异常
  • orElse(T other)获取值,如果值不存在则返回指定的默认值
  • orElseGet(Supplier<? extends T> other)获取值,如果值不存在则返回由 Supplier 提供的默认值
  • orElseThrow(Supplier<? extends X> exceptionSupplier)获取值,如果值不存在则抛出由 Supplier 提供的异常

isPresent()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Optional;

public class OptionalIsPresentExample {
public static void main(String[] args) {
Optional<String> optionalString = Optional.of("Hello~Chris!");

// 检查值是否存在
if (optionalString.isPresent()) {
System.out.println("值存在: " + optionalString.get());
} else {
System.out.println("值不存在");
}
}
}
1
值存在: Hello~Chris!

get()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Optional;

public class OptionalGetExample {
public static void main(String[] args) {
Optional<String> optionalString = Optional.of("Hello~Chris!");

// 获取值,如果值不存在会抛出 NoSuchElementException 异常
String value = optionalString.get();
System.out.println("获取到的值为: " + value);

Optional<String> optionalStringNull = Optional.empty();

String valueNull = optionalStringNull.get();
System.out.println("获取到的值为: " + valueNull);

}
}
1
2
3
4
获取到的值为: Hello~Chris!
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at JavaKnowledge.OptionalTest.OptionalGetExample.main(OptionalGetExample.java:19)

orElse()

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
import java.util.Optional;

public class OptionalOrElseExample {
public static void main(String[] args) {
Optional<String> optionalString = Optional.empty();

// 获取值,如果值不存在则返回指定的默认值
String value1 = optionalString.orElse("这是个默认值哦~");
System.out.println("获取到的值value1为: " + value1);

// 获取值,如果值不存在则返回指定函数的返回值
String value2 = optionalString.orElse(function1());
System.out.println("获取到的值value2为: " + value2);


Optional<String> optionalChris = Optional.of("Chris~");
// 获取值,如果值不存在则返回指定函数的返回值
// 注意:即便存在,也会执行函数中的逻辑 只不过不将函数返回值作为get到的最终结果
String value3 = optionalChris.orElse(function1());
System.out.println("获取到的值value3为: " + value3);

}

private static String function1() {
System.out.println("这是方法1");
return "方法一返回值";
}
}
1
2
3
4
5
获取到的值value1为: 这是个默认值哦~
这是方法1
获取到的值value2为: 方法一返回值
这是方法1
获取到的值value3为: Chris~

orElseGet()

1
2
3
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
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
import java.util.Optional;

public class OptionalOrElseGetExample {
public static void main(String[] args) {
Optional<String> optionalString = Optional.empty();

// 获取值,如果值不存在则返回由 Supplier 提供的默认值
String value1 = optionalString.orElseGet(() -> "这是由 Supplier 提供的默认值");
System.out.println("获取到的值value1为: " + value1);

// 获取值,如果值不存在则执行函数中逻辑 并返回指定函数的返回值
String value2 = optionalString.orElseGet(() -> {
System.out.println("值不存在的情况下的方法2");
return "方法二返回值";
});
System.out.println("获取到的值value2为: " + value2);

Optional<String> optionalChris = Optional.of("Chris~");
// 获取值,如果值不存在则执行函数中逻辑 并返回指定函数的返回值
// 注意:如果存在,就不会执行函数中的逻辑 函数返回值更不可能作为get到的最终结果
String value3 = optionalChris.orElseGet(() -> {
System.out.println("值存在的情况下的方法3");
return "方法三返回值";
});
System.out.println("获取到的值value3为: " + value3);
}
}
1
2
3
4
获取到的值value1为: 这是由 Supplier 提供的默认值
值不存在的情况下的方法2
获取到的值value2为: 方法二返回值
获取到的值value3为: Chris~

使用 orElse(T other) 时无论 Optional 是否 null 都会执行传入的函数获取结果值,在一些高并发的场景会造成额外的性能浪费,应尽可能选择使用 orElseGet(Supplier<? extends T> other)。

orElseThrow()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Optional;

public class OptionalOrElseThrowExample {
public static void main(String[] args) {
Optional<String> optionalString = Optional.empty();

// 获取值,如果值不存在则抛出由 Supplier 提供的异常
try {
String value = optionalString.orElseThrow(() -> new RuntimeException("值不存在哦~"));
System.out.println("获取到的值为: " + value);
} catch (RuntimeException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
}
1
捕获到异常: 值不存在哦~

map() 和 flatMap()

map 和 flatMap 可以将当前值传入到参数函数中,并返回一个 Optional 对象,两者唯一的区别在于 flatMap 不会再次包装,即传入函数返回值为 Optional 类型,即当使用 flatMap 方法时,如果传入的参数函数返回一个 Optional 对象,flatMap 方法会将这个返回的 Optional 对象中的值取出来,并将其作为最终结果的值,而不会再次对其进行包装成一个新的 Optional 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Optional;

public class FlatMapExample2 {
public static Optional<String> toUpperCaseOptional(String s) {
if (s == null) {
return Optional.empty();
}
return Optional.of(s.toUpperCase());
}

public static void main(String[] args) {
Optional<String> optionalString = Optional.of("Hello~Chris!");

// 使用 map 方法
Optional<Optional<String>> result1 = optionalString.map(s -> toUpperCaseOptional(s));
System.out.println("使用 map 方法结果:" + result1);

// 使用 flatMap 方法
Optional<String> result2 = optionalString.flatMap(s -> toUpperCaseOptional(s));
System.out.println("使用 flatMap 方法结果:" + result2);
}
}
1
2
使用 map 方法结果:Optional[Optional[HELLO~CHRIS!]]
使用 flatMap 方法结果:Optional[HELLO~CHRIS!]

filter()

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
import java.util.Optional;

public class FilterExample {
public static void main(String[] args) {
// 创建一个包含值的 Optional 对象
Optional<String> optionalString = Optional.of("Hello~Chris!");

// 使用 filter 方法按条件过滤值
Optional<String> filteredOptional = optionalString.filter(s -> s.startsWith("H"));

// 输出结果
if (filteredOptional.isPresent()) {
System.out.println("过滤后的值为: " + filteredOptional.get());
} else {
System.out.println("值不存在或不符合条件");
}

// 使用 filter 方法按条件过滤值
Optional<String> filteredOptional2 = optionalString.filter(s -> s.startsWith("Ch"));

// 输出结果
if (filteredOptional2.isPresent()) {
System.out.println("过滤后的值为: " + filteredOptional.get());
} else {
System.out.println("值不存在或不符合条件");
}

// 创建一个空的 Optional 对象
Optional<String> emptyOptional = Optional.empty();

// 使用 filter 方法按条件过滤值
Optional<String> filteredEmptyOptional = emptyOptional.filter(s -> s.startsWith("H"));

// 输出结果
if (filteredEmptyOptional.isPresent()) {
System.out.println("过滤后的值为: " + filteredEmptyOptional.get());
} else {
System.out.println("值不存在或不符合条件");
}
}
}
1
2
3
过滤后的值为: Hello~Chris!
值不存在或不符合条件
值不存在或不符合条件

在这个实践中,我们首先创建了一个包含值 “HelloChris!” 的 Optional 对象 optionalString。然后我们使用 filter 方法,传入一个 Predicate 参数,该参数用于指定过滤条件,即以 “H” 开头的字符串。因为 “HelloChris!” 符合条件,所以 filter 方法返回一个包含值 “Hello~Chris!” 的 Optional 对象。同理,以”Ch”开头的字符串这个条件不满足,所以返回”值不存在或不符合条件”。创建一个空的 Optional 对象 emptyOptional,并再次使用 filter 方法进行过滤。由于空的 Optional 对象中并不存在值,所以无论条件如何,filter 方法都会返回一个空的 Optional 对象,即输出结果将根据是否存在符合条件的值而有所不同。

现在我们优化一下本小节开头的示例:

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
/**
* @author 不吃辣的Chris
* @create 2024-03-31-21:46
*/
import java.util.Objects;
import java.util.Optional;

public class PersonHomeAddressExample {
public static void main(String[] args) {
// 示例的 Person 对象
Person person = getPerson();

if (Objects.nonNull(person)) {
HomeAddress homeAddress = person.getHomeAddress();
if (Objects.nonNull(homeAddress)) {
Country country = homeAddress.getCountry();
if (Objects.nonNull(country)) {
String name = country.getName();
if (Objects.nonNull(name)) {
System.out.println("if-else方式-字母转为大写后的城市名为: " + name.toUpperCase());
}
}
}
}


// 使用 Optional 优化原始代码
String countryName = Optional.ofNullable(person)
.map(Person::getHomeAddress)
.map(HomeAddress::getCountry)
.map(Country::getName)
.map(String::toUpperCase)
.orElse(null);

// 输出结果
System.out.println("Optional 优化原始代码-字母转为大写后的城市名为: " + countryName);
}

// 模拟获取 Person 对象的方法
private static Person getPerson() {
// 此处可以根据实际情况返回 Person 对象
return new Person(new HomeAddress(new Country("BeiJing")));
}
}
1
2
if-else方式-字母转为大写后的城市名为: BEIJING
Optional 优化原始代码-字母转为大写后的城市名为: BEIJING

实践可知:通过Optional可以很大程度上减少if-else嵌套的层级,同时保证代码的健壮性

MyBatis的一级缓存

MyBatis中如果多次执行完全相同的SQL语句时,MyBatis提供了一级缓存机制用于提高查询效率。虽然初衷是提高查询效率,但在使用时如果不知道这个点就会出现一些很玄幻的bug,代码就那么几行,为什么莫名其妙就不走数据库查询了?

一级缓存是默认开启的,如果想要手动配置,需要修改MyBatis配置文件:

1
2
3
4
5
mybatis:
mapper-locations: classpath:/mapper/*.xml
configuration:
local-cache-scope: statement #一级缓存指定为statement级别或者session
cache-enabled: false #禁用二级缓存

statement:一级缓存仅针对当前执行的SQL语句生效。当前执行的SQL语句执行完毕后,对应的一级缓存会被清空。

session:一级缓存在一个会话中生效,****MyBatis的一级缓存默认开启,且默认作用范围为session。即在一个会话中的所有查询语句,均会共享同一份一级缓存,不同会话中的一级缓存不共享。

在一个会话中,执行操作会使本会话中的一级缓存失效。

实践验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
datasource:
url: jdbc:mysql://localhost:3306/mysql?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:/mapper/*.xml
configuration:
cache-enabled: false #禁用二级缓存
local-cache-scope: session #一级缓存指定为session级别
server:
port: 8000
logging:
level:
com.example.testproject: debug #开启日志
1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `task` (
`id` bigint NOT NULL,
`task_name` varchar(255) DEFAULT NULL,
`task_type` varchar(255) DEFAULT NULL,
`task_executor_id` bigint DEFAULT NULL,
`task_executor_name` varchar(255) DEFAULT NULL,
`creat_time` datetime DEFAULT NULL,
`creator_id` bigint DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`updator_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;

/**
* (Task)实体类
*
* @author makejava
* @since 2024-03-24 21:39:40
*/
@Component
public class Task implements Serializable {
private static final long serialVersionUID = -73634142730056691L;

private Long id;

private String taskName;

private String taskType;

private Long taskExecutorId;

private String taskExecutorName;

private Date creatTime;

private Long creatorId;

private Date updateTime;

private Long updatorId;


public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getTaskName() {
return taskName;
}

public void setTaskName(String taskName) {
this.taskName = taskName;
}

public String getTaskType() {
return taskType;
}

public void setTaskType(String taskType) {
this.taskType = taskType;
}

public Long getTaskExecutorId() {
return taskExecutorId;
}

public void setTaskExecutorId(Long taskExecutorId) {
this.taskExecutorId = taskExecutorId;
}

public String getTaskExecutorName() {
return taskExecutorName;
}

public void setTaskExecutorName(String taskExecutorName) {
this.taskExecutorName = taskExecutorName;
}

public Date getCreatTime() {
return creatTime;
}

public void setCreatTime(Date creatTime) {
this.creatTime = creatTime;
}

public Long getCreatorId() {
return creatorId;
}

public void setCreatorId(Long creatorId) {
this.creatorId = creatorId;
}

public Date getUpdateTime() {
return updateTime;
}

public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}

public Long getUpdatorId() {
return updatorId;
}

public void setUpdatorId(Long updatorId) {
this.updatorId = updatorId;
}

}
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
import com.example.testproject.entity.Task;
import com.example.testproject.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
* (Task)表控制层
*
* @author makejava
* @since 2024-03-24 21:39:39
*/
@RestController
@RequestMapping("task")
public class TaskController {
/**
* 服务对象
*/
@Autowired
private TaskService taskService;

/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("{id}")
public ResponseEntity<Task> queryById(@PathVariable("id") Long id) {
return ResponseEntity.ok(this.taskService.queryById(id));
}

/**
* 新增数据
*
* @param task 实体
* @return 新增结果
*/
@PostMapping
public ResponseEntity<Task> add(Task task) {
return ResponseEntity.ok(this.taskService.insert(task));
}

/**
* 编辑数据
*
* @param task 实体
* @return 编辑结果
*/
@PutMapping
public ResponseEntity<Task> edit(Task task) {
return ResponseEntity.ok(this.taskService.update(task));
}

/**
* 删除数据
*
* @param id 主键
* @return 删除是否成功
*/
@DeleteMapping
public ResponseEntity<Boolean> deleteById(Long id) {
return ResponseEntity.ok(this.taskService.deleteById(id));
}

}
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
import com.example.testproject.entity.Task;
import org.springframework.stereotype.Service;

/**
* (Task)表服务接口
*
* @author makejava
* @since 2024-03-24 21:39:40
*/
@Service
public interface TaskService {

/**
* 通过ID查询单条数据
*
* @param id 主键
* @return 实例对象
*/
Task queryById(Long id);

/**
* 新增数据
*
* @param task 实例对象
* @return 实例对象
*/
Task insert(Task task);

/**
* 修改数据
*
* @param task 实例对象
* @return 实例对象
*/
Task update(Task task);

/**
* 通过主键删除数据
*
* @param id 主键
* @return 是否成功
*/
boolean deleteById(Long id);

}
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
79
80
81
82
83
84
85
86
import com.example.testproject.entity.Task;

import com.example.testproject.mapper.TaskMapper;
import com.example.testproject.service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Random;

/**
* (Task)表服务实现类
*
* @author makejava
* @since 2024-03-24 21:39:40
*/
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private TaskMapper taskMapper;

/**
* 通过ID查询单条数据
*
* @param id 主键
* @return 实例对象
*/
@Override
@Transactional
public Task queryById(Long id) {
for (int i = 0; i < 10; i++) {
List<Task> taskList = taskMapper.queryAllTask();
taskList.remove(0);
taskList.remove(1);
Task task = new Task();
task.setId(1L);
task.setTaskName("任务名称修改1");
taskMapper.update(task);
Random random = new Random();
task.setId((long) (1000000+ i));
taskMapper.insert(task);
taskMapper.deleteById((long) random.nextInt(100));
System.out.println();
}

return taskMapper.queryById(id);

}


/**
* 新增数据
*
* @param task 实例对象
* @return 实例对象
*/
@Override
public Task insert(Task task) {
this.taskMapper.insert(task);
return task;
}

/**
* 修改数据
*
* @param task 实例对象
* @return 实例对象
*/
@Override
public Task update(Task task) {
this.taskMapper.update(task);
return this.queryById(task.getId());
}

/**
* 通过主键删除数据
*
* @param id 主键
* @return 是否成功
*/
@Override
public boolean deleteById(Long id) {
return this.taskMapper.deleteById(id) > 0;
}
}
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
import com.example.testproject.entity.Task;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
* (Task)表数据库访问层
*
* @author makejava
* @since 2024-03-24 21:39:39
*/
@Mapper
public interface TaskMapper {

/**
* 通过ID查询单条数据
*
* @param id 主键
* @return 实例对象
*/
Task queryById(Long id);

/**
* 统计总行数
*
* @param task 查询条件
* @return 总行数
*/
long count(Task task);

/**
* 新增数据
*
* @param task 实例对象
* @return 影响行数
*/
int insert(Task task);

/**
* 批量新增数据(MyBatis原生foreach方法)
*
* @param entities List<Task> 实例对象列表
* @return 影响行数
*/
int insertBatch(@Param("entities") List<Task> entities);

/**
* 批量新增或按主键更新数据(MyBatis原生foreach方法)
*
* @param entities List<Task> 实例对象列表
* @return 影响行数
*/
int insertOrUpdateBatch(@Param("entities") List<Task> entities);

/**
* 修改数据
*
* @param task 实例对象
* @return 影响行数
*/
int update(Task task);

/**
* 通过主键删除数据
*
* @param id 主键
* @return 影响行数
*/
int deleteById(Long id);

List<Task> queryAllTask();
}
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.testproject.mapper.TaskMapper">

<resultMap type="com.example.testproject.entity.Task" id="TaskMap">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="taskName" column="task_name" jdbcType="VARCHAR"/>
<result property="taskType" column="task_type" jdbcType="VARCHAR"/>
<result property="taskExecutorId" column="task_executor_id" jdbcType="INTEGER"/>
<result property="taskExecutorName" column="task_executor_name" jdbcType="VARCHAR"/>
<result property="creatTime" column="creat_time" jdbcType="TIMESTAMP"/>
<result property="creatorId" column="creator_id" jdbcType="INTEGER"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="updatorId" column="updator_id" jdbcType="INTEGER"/>
</resultMap>

<!--查询单个-->
<select id="queryById" resultMap="TaskMap">
select id,
task_name,
task_type,
task_executor_id,
task_executor_name,
creat_time,
creator_id,
update_time,
updator_id
from task
where id = #{id}
</select>

<!--查询指定行数据-->
<select id="queryAllByLimit" resultMap="TaskMap">
select
id, task_name, task_type, task_executor_id, task_executor_name, creat_time, creator_id, update_time, updator_id
from task
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="taskName != null and taskName != ''">
and task_name = #{taskName}
</if>
<if test="taskType != null and taskType != ''">
and task_type = #{taskType}
</if>
<if test="taskExecutorId != null">
and task_executor_id = #{taskExecutorId}
</if>
<if test="taskExecutorName != null and taskExecutorName != ''">
and task_executor_name = #{taskExecutorName}
</if>
<if test="creatTime != null">
and creat_time = #{creatTime}
</if>
<if test="creatorId != null">
and creator_id = #{creatorId}
</if>
<if test="updateTime != null">
and update_time = #{updateTime}
</if>
<if test="updatorId != null">
and updator_id = #{updatorId}
</if>
</where>
limit #{pageable.offset}, #{pageable.pageSize}
</select>

<!--统计总行数-->
<select id="count" resultType="java.lang.Long">
select count(1)
from task
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="taskName != null and taskName != ''">
and task_name = #{taskName}
</if>
<if test="taskType != null and taskType != ''">
and task_type = #{taskType}
</if>
<if test="taskExecutorId != null">
and task_executor_id = #{taskExecutorId}
</if>
<if test="taskExecutorName != null and taskExecutorName != ''">
and task_executor_name = #{taskExecutorName}
</if>
<if test="creatTime != null">
and creat_time = #{creatTime}
</if>
<if test="creatorId != null">
and creator_id = #{creatorId}
</if>
<if test="updateTime != null">
and update_time = #{updateTime}
</if>
<if test="updatorId != null">
and updator_id = #{updatorId}
</if>
</where>
</select>

<!--新增所有列-->
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into task(id, task_name, task_type, task_executor_id, task_executor_name, creat_time, creator_id,
update_time, updator_id)
values (#{id}, #{taskName}, #{taskType}, #{taskExecutorId}, #{taskExecutorName}, #{creatTime}, #{creatorId},
#{updateTime}, #{updatorId})
</insert>

<insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">
insert into task(task_name, task_type, task_executor_id, task_executor_name, creat_time, creator_id,
update_time, updator_id)
values
<foreach collection="entities" item="entity" separator=",">
(#{entity.taskName}, #{entity.taskType}, #{entity.taskExecutorId}, #{entity.taskExecutorName},
#{entity.creatTime}, #{entity.creatorId}, #{entity.updateTime}, #{entity.updatorId})
</foreach>
</insert>

<insert id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true">
insert into task(task_name, task_type, task_executor_id, task_executor_name, creat_time, creator_id,
update_time, updator_id)
values
<foreach collection="entities" item="entity" separator=",">
(#{entity.taskName}, #{entity.taskType}, #{entity.taskExecutorId}, #{entity.taskExecutorName},
#{entity.creatTime}, #{entity.creatorId}, #{entity.updateTime}, #{entity.updatorId})
</foreach>
on duplicate key update
task_name = values(task_name),
task_type = values(task_type),
task_executor_id = values(task_executor_id),
task_executor_name = values(task_executor_name),
creat_time = values(creat_time),
creator_id = values(creator_id),
update_time = values(update_time),
updator_id = values(updator_id)
</insert>

<!--通过主键修改数据-->
<update id="update">
update task
<set>
<if test="taskName != null and taskName != ''">
task_name = #{taskName},
</if>
<if test="taskType != null and taskType != ''">
task_type = #{taskType},
</if>
<if test="taskExecutorId != null">
task_executor_id = #{taskExecutorId},
</if>
<if test="taskExecutorName != null and taskExecutorName != ''">
task_executor_name = #{taskExecutorName},
</if>
<if test="creatTime != null">
creat_time = #{creatTime},
</if>
<if test="creatorId != null">
creator_id = #{creatorId},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="updatorId != null">
updator_id = #{updatorId},
</if>
</set>
where id = #{id}
</update>

<!--通过主键删除-->
<delete id="deleteById">
delete
from task
where id = #{id}
</delete>

<!-- flushCache="true"-->
<select id="queryAllTask" resultMap="TaskMap" flushCache="true">
select id,
task_name,
task_type,
task_executor_id,
task_executor_name,
creat_time,
creator_id,
update_time,
updator_id
from task
</select>

</mapper>

SpringBoot整合SwaggerUI流程

SwaggerUI是我们与前端沟通接口信息的一个工具,可以展示所有接口及接口的请求方式和请求体格式

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
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>interfaceTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>interfaceTest</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--@Valid用到的注解-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>

<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->

</project>
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
package com.example.interfacetest.config;

import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//为当前包路径
.apis(RequestHandlerSelectors.basePackage("com.example.interfacetest"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot 使用 Swagger2 构建RESTful API")
.version("1.0")
.description("API 描述")
.build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.interfacetest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
public class InterfaceTestApplication {

public static void main(String[] args) {
SpringApplication.run(InterfaceTestApplication.class, args);
}

}
1
2
3
4
5
6
7
8
9
swagger:
enabled: true
title: API文档
#接口包扫描路径
base-package: com.example.interfacetest.controller
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher

访问http://localhost:8080/swagger-ui/index.html#/则可以进入SwaggerUI的界面。

SpringBoot接口的几种入参请求和接收方式

@PathVariable

@PathVariable 注解用于从 URI 中提取参数并将其绑定到方法的参数上

@RequestParam

@RequestParam 注解将 Http 请求中的参数值映射到方法的参数上

@RequestBody

@RequestBody 注解用于处理 Http 请求体的内容映射到方法的参数上,可以将请求体的内容转化为相应的 Java 对象,以便在方法中处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class StudentReq {

@ApiModelProperty(value = "姓名", required = true)
private String name;

@ApiModelProperty(value = "年龄", required = true)
private Integer age;

@ApiModelProperty(value = "年级", required = true)
private String gender;

@ApiModelProperty(value = "分数")
private Double score;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileInfo {


private String fileName;

private MultipartFile file;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;


@Data
@ApiModel(value = "枚举类Vo", description = "")
public class EnumVo implements Serializable {

private static final Long serialVersionUID = 8233942199L;

@ApiModelProperty(value = "code")
private String code;

@ApiModelProperty(value = "name")
private String name;
}
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
public enum StateEnum {

START(0, "开始"),
END(1, "结束"),
SUSPEND(2, "暂停");

/**
* 状态码
*/
private Integer code;
/**
* 状态名称
*/
private String name;

StateEnum() {
}

StateEnum(Integer code, String name) {
this.code = code;
this.name = name;
}

/**
* 根据code获取name
*/
public static String getNameByCode(Integer code) {
StateEnum[] values = values();
for (StateEnum value : values) {
if(value.code.compareTo(code) == 0){
return value.name;
}
}
return null;
}

/**
* 根据name获取code
*/
public static Integer getCodeByName(String name) {
StateEnum[] values = values();
for (StateEnum value : values) {
if(value.name.equals(name)){
return value.code;
}
}
return null;
}

public Integer getStateCode() {
return code;
}

public String getStateName() {
return name;
}
}
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.example.interfacetest.controller;

/**
* @author 不吃辣的Chris
* @create 2024-04-08-22:40
*/
import com.example.interfacetest.enumPackage.StateEnum;
import com.example.interfacetest.req.FileInfoReq;
import com.example.interfacetest.req.StudentReq;
import com.example.interfacetest.vo.EnumVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;


@RestController
@Api(value = "/interfaceTest", tags = {"接口入参测试"})
public class interfaceTestController {


/**
* @PathVariable测试
*/
@ApiOperation(value = "查询任务id1", httpMethod = "GET")
@GetMapping("/pathTest1/{taskId}")
public String getTaskId1(@PathVariable Integer taskId) {
return "taskId为" + taskId;
}


@ApiOperation(value = "查询任务id1", httpMethod = "GET")
@GetMapping("/pathTest2/{id}")
public String getTaskId2(@PathVariable("id") Integer taskId) {
return "taskId为" + taskId;
}


/**
* @RequestParam测试
*/
@ApiOperation(value = "查询名称1", httpMethod = "GET")
@GetMapping("/paramTest1")
public String getName1(@RequestParam String name) {
return name;
}

@ApiOperation(value = "查询名称2", httpMethod = "GET")
@GetMapping("/paramTest2")
public String getName2(@RequestParam("userName") String name) {
return name;
}

@ApiOperation(value = "查询名称3", httpMethod = "GET")
@GetMapping("/paramTest3")
public String getName3(@ApiParam(name = "name", value = "你的姓名", required = true) @RequestParam(value = "name", required = true) String name) {
return name;
}

/**
* @RequestBody测试
*/
@ApiOperation(value = "查询学生信息1", httpMethod = "POST")
@PostMapping("/bodyTest1")
public String getStudent1(@RequestBody @Valid StudentReq studentReq) {
return studentReq.getName();
}

@ApiOperation(value = "查询学生信息2", httpMethod = "POST")
@PostMapping("/bodyTest2")
public int[] getStudent2(@RequestBody int[] studentIdList) {
return studentIdList;
}

@ApiOperation(value = "查询学生信息3", httpMethod = "POST")
@PostMapping("/bodyTest3")
public Integer[] getStudent3(@RequestBody Integer[] studentIdList) {
return studentIdList;
}


/**
* 批量删除记录
* 批量删除接口修改请求方式为Put 原因在于如果Delete和RequestBody配合使用时Postman是可以请求成功
* 但前端请求会失败,因为Express 默认情况下不会解析 DELETE 请求的请求体
*/
@ApiOperation(value = "批量删除记录", httpMethod = "PUT")
@PutMapping(path = "/deleteStudentByIds")
public String deleteStudentByIds(@RequestBody List<Long> studentIdList) {
List<Long> studentIdList1 = studentIdList;
return studentIdList.toString();
}

/**
* 请求体带文件
*/
@ApiOperation(value = "请求体带文件测试", httpMethod = "PUT")
@PutMapping(path = "/fileUploadTest")
public String fileUploadTest(FileInfoReq fileInfoReq) {
return fileInfoReq.getFile().getOriginalFilename();
}

/**
* 查询枚举信息
*/
@ApiOperation(value = "查询枚举信息", httpMethod = "GET")
@GetMapping("/getEnumInfo")
public List<EnumVo> getEnumInfo() {
return Arrays.stream(StateEnum.values()).map(c -> {
EnumVo enumVo = new EnumVo();
enumVo.setCode(c.getStateCode().toString());
enumVo.setName(c.getStateName());
return enumVo;
}).collect(Collectors.toList());
}

}

http://localhost:8080/swagger-ui/index.html#/

代码解耦小技巧

在一个接口有很多实现类,需要根据不同的条件判断何时调用哪个实现类的方法时,常见的做法是通过if-else判断,new一个对应的实现类对象,调用该对象的方法。但如果某个实现类需要被删除,所有涉及到这个实现类的地方都会受到影响,需要手动检查代码,从业务逻辑代码中删除相关的代码,代码耦合度较高。为了解决这一问题,可以通过遍历接口实现类的方式,查找在特定情况下需要调用哪个实现类的某一方法,实现代码的解耦,无论是新增还是删除这个接口的实现类,在业务代码中均不会出现具体的实现类,具有较好的扩展性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.stereotype.Component;

/**
* @author 不吃辣的Chris
* @create 2024-04-11-20:06
*/
@Component
public interface Handler {

String handle(String s);

boolean support(String s);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.example.interfacetest.service.Handler;
import org.springframework.stereotype.Component;

/**
* @author 不吃辣的Chris
* @create 2024-04-11-20:05
*/
@Component
public class TaskHandler implements Handler {

private static String STRING_ZERO = "0";

@Override
public String handle(String s) {
return "开始处理任务,任务信息是:" + s;
}

@Override
public boolean support(String s) {
return STRING_ZERO.equals(s);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.example.interfacetest.service.Handler;
import org.springframework.stereotype.Component;

/**
* @author 不吃辣的Chris
* @create 2024-04-11-20:05
*/
@Component
public class ProjectHandler implements Handler {

private static String STRING_ONE = "1";

@Override
public String handle(String s) {
return "开始处理项目,项目信息是:" + s;
}

@Override
public boolean support(String s) {
return STRING_ONE.equals(s);
}
}
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
import com.example.interfacetest.service.Handler;
import com.example.interfacetest.util.ProjectHandler;
import com.example.interfacetest.util.TaskHandler;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @author 不吃辣的Chris
* @create 2024-04-11-20:11
*/
@RestController
@Api(value = "/codeOptimize", tags = {"代码优化测试"})
public class CodeOptimizeController {

@Autowired
List<Handler> handlerList;

private static String STRING_ZERO = "0";

private static String STRING_ONE = "1";


@ApiOperation(value = "处理任务/项目信息(if-else) 0-任务 1-项目", httpMethod = "GET")
@GetMapping("/test1")
public String test1(@RequestParam("code") String code, @RequestParam("message") String message) {
String result = "";
if ("0".equals(code)) {
TaskHandler taskHandler = new TaskHandler();
result = taskHandler.handle(message);
}else if ("1".equals(code)) {
ProjectHandler projectHandler = new ProjectHandler();
result = projectHandler.handle(message);
}
return result;
}

@ApiOperation(value = "处理任务/项目信息(for) 0-任务 1-项目", httpMethod = "GET")
@GetMapping("/test2")
public String test2(@RequestParam("code") String code, @RequestParam("message") String message) {
String result = "";
for (Handler handler : handlerList) {
if (handler.support(code)) {
result = handler.handle(message);
break;
}
}
return result;
}

}

枚举类结合泛型降低代码耦合小技巧

在上述实现方法中,如果Handler类很多,并且每种Handler类中的方法很少,那建立很多个Handler类会使项目代码中类的规模十分庞大。在这种情况下可以考虑使用枚举类结合泛型优化代码结构。

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
import org.example.ideaspringboottest.JavaTest.FunctionTest.interfaceTest.FunctionStrategy;

import java.util.function.Function;
import java.util.function.Predicate;

public enum FunctionHandlerEnum implements FunctionStrategy<String, String, String> {

TASK(code -> "0".equals(code), message -> {
return "开始处理任务信息" + message;
} ),
PROJECT(code -> "1".equals(code), message -> {
return "开始处理项目信息" + message;
} );

FunctionHandlerEnum(Predicate<String> predicate, Function<String, String> function) {
this.predicate = predicate;
this.function = function;
}

private final Predicate<String> predicate;

private final Function<String, String> function;

public Predicate<String> getPredicate() {
return predicate;
}

public Function<String, String> getFunction() {
return function;
}

@Override
public Predicate<String> predicate() {
return predicate;
}

@Override
public Function<String, String> function() {
return function;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.function.Function;
import java.util.function.Predicate;

public interface FunctionStrategy <P, T, R>{

/**
* Predicate是指定类型,返回boolean值的函数接口。内部提供默认实现的方法,可以组合复杂的逻辑判断(and、or、negate)
* @return
*/
Predicate<P> predicate();

/**
* Function 接口定义了一个操作,它接受一个输入参数并产生一个结果。它的泛型参数表示输入类型和输出类型。即 Function 接口有两个泛型类型参数:输入类型(T)和输出类(R)
* Function 接口中的抽象方法是 apply(),它接受一个输入参数类型为 T 的对象,并返回一个输出类型为 R 的结果。
* @return
*/
Function<T, R> function();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

import org.example.ideaspringboottest.JavaTest.FunctionTest.enumTest.FunctionHandlerEnum;

public class FunctionTest {

public static void main(String[] args) {

String s = function1("1", "Chris");
System.out.println(s);
}

public static String function1(String code, String message) {
String result = "";
for (FunctionHandlerEnum value : FunctionHandlerEnum.values()) {
if (value.predicate().test(code)) {
result = value.function().apply(message);
}
}
return result;
}

}
1
开始处理项目信息Chris

通过枚举类和泛型优化责任链模式

常规的责任链模式通过new出责任链上的所有对象,每次使用时手动组装其顺序,当责任链中对象较多时,手动组装较麻烦,因此可以借助枚举类和泛型抽取组装责任链的过程,不过下述方案使用了反射,性能不如手动组装,可以根据实际情况选择作为备用方案。

传统责任链模式使用实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import lombok.Data;

@Data
public abstract class OperationHandler {

protected OperationHandler nextHandler;

public void setNextHandler(OperationHandler nextHandler) {
this.nextHandler = nextHandler;
}

public abstract void operation();

}
1
2
3
4
5
6
7
8
9
10
public class PowerOnHandler extends OperationHandler {

@Override
public void operation() {
System.out.println("执行开机操作");
if (this.nextHandler != null) {
nextHandler.operation();
}
}
}
1
2
3
4
5
6
7
8
9
10
public class PlayHandler extends OperationHandler {

@Override
public void operation() {
System.out.println("执行播放视频操作");
if (this.nextHandler != null) {
nextHandler.operation();
}
}
}
1
2
3
4
5
6
7
8
9
10
public class ShutDownHandler extends OperationHandler {

@Override
public void operation() {
System.out.println("执行关机操作");
if (this.nextHandler != null) {
nextHandler.operation();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.PlayHandler;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.PowerOnHandler;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.ShutDownHandler;

public class TraditionTest {


public static void main(String[] args) {

PowerOnHandler powerOnHandler = new PowerOnHandler();
PlayHandler playHandler = new PlayHandler();
ShutDownHandler shutDownHandler = new ShutDownHandler();

powerOnHandler.setNextHandler(playHandler);
playHandler.setNextHandler(shutDownHandler);

powerOnHandler.operation();

}

}
1
2
3
执行开机操作
执行播放视频操作
执行关机操作

通过枚举类和泛型优化责任链模式实践

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
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperationBean {

/**
* 序号
*/
Integer orderId;

/**
* 操作内容
*/
String operationContent;

/**
* 全类名
*/
String pathName;

/**
* 上一个序号
*/
Integer preOrderId;

/**
* 下一个序号
*/
Integer nextOrderId;

}
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
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.bean.OperationBean;

public enum OperationEnum {


POWERON_HANDLER(new OperationBean(1,"执行开机操作",
"org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.PowerOnHandler",null,2)),
PLAY_HANDLER(new OperationBean(2,"执行播放视频操作",
"org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.PlayHandler",1,3)),
SHUT_DOWN_HANDLER(new OperationBean(3,"执行关机操作",
"org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.ShutDownHandler",2,null));


OperationBean operationBean;


public OperationBean getOperationBean() {
return operationBean;
}


OperationEnum(OperationBean operationBean) {
this.operationBean = operationBean;
}

}
1
2
3
4
5
6
7
8
9
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.bean.OperationBean;

public interface ComputerOperation {

OperationBean getOperationBeanById(Integer orderId);

OperationBean getFirstOperationBean();

}
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.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.bean.OperationBean;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.enumTest.OperationEnum;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.myInterface.ComputerOperation;

import java.util.HashMap;
import java.util.Map;

public class ComputerOperationImp implements ComputerOperation {


private static Map<Integer, OperationBean> operationBeanMap = new HashMap<>();

static {
for (OperationEnum value : OperationEnum.values()) {
OperationBean operationBean = value.getOperationBean();
operationBeanMap.put(operationBean.getOrderId(), operationBean);
}
}


@Override
public OperationBean getOperationBeanById(Integer orderId) {
return operationBeanMap.get(orderId);
}

@Override
public OperationBean getFirstOperationBean() {
for (Map.Entry<Integer, OperationBean> entry : operationBeanMap.entrySet()) {
if (entry.getValue().getPreOrderId() == null) {
return entry.getValue();
}
}
return null;
}
}
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
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.OperationHandler;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.bean.OperationBean;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.myInterface.imp.ComputerOperationImp;

public class ComputerHandlerEnumFactory {

private static ComputerOperationImp computerOperation = new ComputerOperationImp();

public static OperationHandler getOperationHandlerByBean(OperationBean operationBean) {
// 获取Handler实现类的全路径名
String pathName = operationBean.getPathName();
try {
// 通过反射的方式根据全路径名获得对象
Class<?> classObject = Class.forName(pathName);
return (OperationHandler) classObject.newInstance();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}


public static OperationHandler getFirstHandler() {

OperationBean firstOperationBean = computerOperation.getFirstOperationBean();
OperationHandler firstOperationHandler = getOperationHandlerByBean(firstOperationBean);

if (firstOperationHandler == null) {
return null;
}

OperationBean tempOperationBean = firstOperationBean;
OperationHandler tempOperationHandler = firstOperationHandler;
Integer nextOrderId = null;
// 遍历枚举类 将Handler按照顺序进行排序组装
while ((nextOrderId = tempOperationBean.getNextOrderId()) != null) {
OperationBean operationBean = computerOperation.getOperationBeanById(nextOrderId);
OperationHandler operationHandler = getOperationHandlerByBean(operationBean);
tempOperationHandler.setNextHandler(operationHandler);
tempOperationBean = operationBean;
tempOperationHandler = operationHandler;
}
return firstOperationHandler;
}

}
1
2
3
4
5
6
7
8
9
10
11
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.factory.ComputerHandlerEnumFactory;
import org.example.ideaspringboottest.JavaTest.ResponsibilityChainTest.handler.OperationHandler;

public class ChainEnumTest {

public static void main(String[] args) {
OperationHandler firstHandler = ComputerHandlerEnumFactory.getFirstHandler();
firstHandler.operation();
}

}
1
2
3
执行开机操作
执行播放视频操作
执行关机操作

Map结合函数式接口优化策略模式

在使用策略模式时,如果策略较多,需要很多switch case或者if-else进行判断,当一个类中有多处需要用到switch case或者if-else进行判断则代码比较冗余,我们可以借助Map以code为key,以策略中的函数为value,对策略中的函数进行存储,需要时按需根据code从中获取对应的函数。

传统方法

1
2
3
4
5
public interface OperationStrategy {

public String operation(String s);

}
1
2
3
4
5
6
7
8
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.OperationStrategy;

public class TaskOperationStrategy implements OperationStrategy {
@Override
public String operation(String s) {
return "处理任务信息:" + s;
}
}
1
2
3
4
5
6
7
8
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.OperationStrategy;

public class ProjectOperationStrategy implements OperationStrategy {
@Override
public String operation(String s) {
return "处理项目信息:" + s;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.OperationStrategy;

public class OperationBean {

private OperationStrategy operationStrategy;

public OperationBean(OperationStrategy operationStrategy) {
this.operationStrategy = operationStrategy;
}

public String operation(String s) {
return this.operationStrategy.operation(s);
}

}
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
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.bean.OperationBean;
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.impl.ProjectOperationStrategy;
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.impl.TaskOperationStrategy;

public class TraditionTest {

private static final String STRING_ZERO = "0";
private static final String STRING_ONE = "1";

public static void main(String[] args) {

String s1 = operationMessage("0", "Chris");
String s2 = operationMessage("1", "John");
System.out.println(s1);
System.out.println(s2);
}

private static String operationMessage(String code, String message) {
String result = "";
switch (code) {
case STRING_ZERO -> {
result = new OperationBean(new TaskOperationStrategy()).operation(message);
}
case STRING_ONE -> {
result = new OperationBean(new ProjectOperationStrategy()).operation(message);
}
}
return result;
}
}
1
2
处理任务信息:Chris
处理项目信息:John

Map结合函数式接口优化策略模式

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
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.impl.ProjectOperationStrategy;
import org.example.ideaspringboottest.JavaTest.StrategyFunction.Strategy.impl.TaskOperationStrategy;

import java.util.HashMap;
import java.util.function.Function;

public class StrategyFunctionTest {

private static HashMap<String, Function<String, String>> functionHashMap = new HashMap<>();
private static final String STRING_ZERO = "0";
private static final String STRING_ONE = "1";

static {
functionHashMap.put(STRING_ZERO, message -> new TaskOperationStrategy().operation(message));
functionHashMap.put(STRING_ONE, message -> new ProjectOperationStrategy().operation(message));
}

private static String operationMessage(String code, String message) {
Function<String, String> operationFunction = functionHashMap.get(code);
return operationFunction.apply(message);
}

public static void main(String[] args) {
String s1 = operationMessage("0", "Chris");
String s2 = operationMessage("1","John");
System.out.println(s1);
System.out.println(s2);
}

}
1
2
处理任务信息:Chris
处理项目信息:John

责任链模式优化请求参数校验

用户请求后端接口时,如果存在参数校验,并且很多接口的参数校验逻辑相同,那么在每个接口中都写重复的校验逻辑将使代码十分冗余,通过责任链模式结合策略模式优化参数校验过程,对校验逻辑进行抽取,减少重复代码的出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser {

String userName;

String passWord;

String RoleCode;

}
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
import org.example.ideaspringboottest.JavaTest.ChainVerification.Bean.LoginUser;

public abstract class Handler<T> {

protected Handler next;

private void next(Handler next) {
this.next = next;
}

public abstract void doHandler(LoginUser loginUser);

public static class Builder<T> {
private Handler<T> head;
private Handler<T> tail;

public Builder<T> addHandler(Handler handler) {
if (this.head == null) {
this.tail = handler;
this.head = tail;
return this;
}
this.tail.next(handler);
this.tail = handler;
return this;
}

public Handler<T> build() {
return this.head;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.example.ideaspringboottest.JavaTest.ChainVerification.Bean.LoginUser;
import org.example.ideaspringboottest.JavaTest.ChainVerification.Handler.Handler;

public class AuthHandler extends Handler {
@Override
public void doHandler(LoginUser loginUser) {
if (!"1".equals(loginUser.getRoleCode())) {
System.out.println("非管理员无操作权限");
return;
}
if (null != next) {
next.doHandler(loginUser);
}
}
}
1
2
3
4
5
6
7
8
9
10
import org.example.ideaspringboottest.JavaTest.ChainVerification.Bean.LoginUser;
import org.example.ideaspringboottest.JavaTest.ChainVerification.Handler.Handler;

public class BusinessLogicHandler extends Handler {

@Override
public void doHandler(LoginUser loginUser) {
System.out.println("用户:" + loginUser.getUserName() + "登陆成功!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.example.ideaspringboottest.JavaTest.ChainVerification.Bean.LoginUser;
import org.example.ideaspringboottest.JavaTest.ChainVerification.Handler.Handler;

public class LoginHandler extends Handler {

@Override
public void doHandler(LoginUser loginUser) {
if (!"Chris".equals(loginUser.getUserName()) || !"123456".equals(loginUser.getPassWord())) {
System.out.println("用户名密码不正确");
return;
}
if (null != next) {
next.doHandler(loginUser);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import io.micrometer.common.util.StringUtils;
import org.example.ideaspringboottest.JavaTest.ChainVerification.Bean.LoginUser;
import org.example.ideaspringboottest.JavaTest.ChainVerification.Handler.Handler;

public class ValidateHandler extends Handler {
@Override
public void doHandler(LoginUser loginUser) {
if (StringUtils.isEmpty(loginUser.getUserName()) || StringUtils.isEmpty(loginUser.getPassWord())) {
System.out.println("用户名和密码不能为空");
return;
}
if (null != next) {
next.doHandler(loginUser);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ChainVerificationTest {

public static void main(String[] args) {
Handler.Builder builder = new Handler.Builder();
// 责任链模式的链式调用
builder.addHandler(new ValidateHandler())
.addHandler(new LoginHandler())
.addHandler(new AuthHandler())
.addHandler(new BusinessLogicHandler());
LoginUser loginUser = new LoginUser();
loginUser.setUserName("Chris");
loginUser.setPassWord("123456");
loginUser.setRoleCode("1");
builder.build().doHandler(loginUser);
}

}
1
用户:Chris登陆成功!

链式规则执行器优化条件判断

在开发接口逻辑的过程中经常会需要对入参进行判断,如果涉及 && 或 || 的逻辑判断,并且相同的判断逻辑在多处需要使用,可以对判断规则进行抽取,即使用规则执行器优化条件判断。优点是每个规则可以独立存在,在使用时自行根据需要进行组装使用,无需写重复的判断逻辑,缺点是依赖相同的入参类,当规则过多时,创建的规则类较多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import lombok.Data;

/**
* 待校验的类
*/
@Data
public class Task {

private Long id;

private String taskName;

private String taskTypeCode;

private String executorName;
}
1
2
3
4
5
6
7
8
9
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;

/**
* 规则定义接口
*/
public interface BaseRule {

boolean execute(Task task);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;

public class TaskExectorNameRule implements BaseRule {

@Override
public boolean execute(Task task) {
System.out.println("开始校验任务执行人名称~");
if (task.getExecutorName().equals("Chris")) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;

public class TaskIdRule implements BaseRule{

@Override
public boolean execute(Task task) {
System.out.println("开始校验任务id~");
if (task.getId() > 2) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;

public class TaskNameRule implements BaseRule {

@Override
public boolean execute(Task task) {
System.out.println("开始校验任务名称~");
if (task.getTaskName().equals("select")) {
return true;
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;

public class TaskTypeCodeRule implements BaseRule{

@Override
public boolean execute(Task task) {
System.out.println("开始校验任务类型码~");
if (task.getTaskTypeCode().equals("5")) {
return true;
}
return false;
}
}
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
79
80
package org.example.ideaspringboottest.JavaTest.RuleExecutor.Service;

import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Utils.BaseRule;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RuleService {

private Map<Integer, List<BaseRule>> hashMap = new HashMap<>();

private static final int OR = 0;
private static final int AND = 1;

public static RuleService create() {
return new RuleService();
}


public RuleService and(List<BaseRule> ruleList) {
hashMap.put(AND, ruleList);
return this;
}

public RuleService or(List<BaseRule> ruleList) {
hashMap.put(OR, ruleList);
return this;
}

public boolean execute(Task task) {
for (Map.Entry<Integer, List<BaseRule>> item : hashMap.entrySet()) {
List<BaseRule> ruleList = item.getValue();
switch (item.getKey()) {
case OR:
// 如果是 or 关系,并行执行
System.out.println("判断类型为or");
if (!or(task, ruleList)) {
return false;
}
break;
case AND:
// 如果是 and 关系,同步执行
System.out.println("判断类型为and");
if (!and(task, ruleList)) {
return false;
}
break;
default:
break;
}
}
return true;
}

private boolean and(Task task, List<BaseRule> ruleList) {
for (BaseRule rule : ruleList) {
boolean execute = rule.execute(task);
if (!execute) {
// and 判断方式中只要有不符合规则的情况则返回false
return false;
}
}
// and 判断方式中全部规则校验均通过 返回true
return true;
}

private boolean or(Task task, List<BaseRule> ruleList) {
for (BaseRule rule : ruleList) {
boolean execute = rule.execute(task);
if (execute) {
// or 判断方式中只要有符合规则的情况则返回true
return true;
}
}
// or 判断方式中全部规则校验均不通过 返回false
return false;
}
}
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
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Bean.Task;
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Service.RuleService;
import org.example.ideaspringboottest.JavaTest.RuleExecutor.Utils.*;

import java.util.Arrays;

public class RuleServiceTest {


public static void main(String[] args) {
execute();
}

public static void execute() {

// 定义单个规则
BaseRule taskIdRule = new TaskIdRule();
BaseRule taskNameRule = new TaskNameRule();
TaskTypeCodeRule taskTypeCodeRule = new TaskTypeCodeRule();
TaskExectorNameRule taskExectorNameRule = new TaskExectorNameRule();

// 设置待校验入参对象
Task task = new Task();
task.setId(5L);
task.setTaskName("select");
task.setTaskTypeCode("3");
task.setExecutorName("Chris");

// 以链式调用构建和执行规则校验
boolean ruleResult = RuleService
.create()
.and(Arrays.asList(taskIdRule, taskNameRule))
.or(Arrays.asList(taskTypeCodeRule, taskExectorNameRule))
.execute(task);
System.out.println("任务执行结果为 :" + ruleResult);
}
}
1
2
3
4
5
6
7
判断类型为or
开始校验任务类型码~
开始校验任务执行人名称~
判断类型为and
开始校验任务id~
开始校验任务名称~
任务执行结果为 :true

自定义注解记录日志

在项目开发的过程中经常会用到记录日志的操作,如果在每个方法中都添加记录日志的逻辑,将使代码十分冗余,对于有共性的一些日志信息记录的过程我们可以进行抽取,通过注解的方式记录日志,减少重复代码的出现,同时与业务逻辑解耦,不影响主流程的逻辑处理。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
import lombok.Data;

@Data
public class TaskInsertReq {

private Long id;

private String taskName;

private String executor;

}
1
2
3
4
5
6
7
8
9
10
11
12
import lombok.Data;

@Data
public class TaskUpdateReq {

private Long taskId;

private String taskName;

private String executor;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.example.ideaspringboottest.JavaTest.TaskService.Annotation.OperateRecord;
import org.example.ideaspringboottest.JavaTest.TaskService.Annotation.TaskInsertConvert;
import org.example.ideaspringboottest.JavaTest.TaskService.Annotation.TaskUpdateConvert;
import org.example.ideaspringboottest.JavaTest.TaskService.Req.TaskInsertReq;
import org.example.ideaspringboottest.JavaTest.TaskService.Req.TaskUpdateReq;
import org.springframework.stereotype.Service;

@Service
public class TaskService {

@OperateRecord(operateContent = "新增任务操作", convert = TaskInsertConvert.class)
public String insert(TaskInsertReq taskInsertReq) {
System.out.println("插入一条任务,任务id为:" + taskInsertReq.getId() + ", 任务名称为:" + taskInsertReq.getTaskName());
return taskInsertReq.getTaskName();
}

@OperateRecord(operateContent = "更新任务操作", convert = TaskUpdateConvert.class)
public String update(TaskUpdateReq taskUpdateReq) {
System.out.println("更新一条任务,任务id为:" + taskUpdateReq.getTaskId() + ",任务名称为:" + taskUpdateReq.getTaskName());
return taskUpdateReq.getTaskName();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
import lombok.Data;

@Data
public class OperateLog {

private Long id;

private String operateContent;

private String result;

}
1
2
3
4
5
6
7
import org.example.ideaspringboottest.JavaTest.TaskService.Bean.OperateLog;

public interface Convert <T> {

OperateLog convert(T param);

}
1
2
3
4
5
6
7
8
9
10
11
import org.example.ideaspringboottest.JavaTest.TaskService.Req.TaskInsertReq;
import org.example.ideaspringboottest.JavaTest.TaskService.Bean.OperateLog;

public class TaskInsertConvert implements Convert<TaskInsertReq>{
@Override
public OperateLog convert(TaskInsertReq taskInsertReq) {
OperateLog operateLog = new OperateLog();
operateLog.setId(taskInsertReq.getId());
return operateLog;
}
}
1
2
3
4
5
6
7
8
9
10
11
import org.example.ideaspringboottest.JavaTest.TaskService.Req.TaskUpdateReq;
import org.example.ideaspringboottest.JavaTest.TaskService.Bean.OperateLog;

public class TaskUpdateConvert implements Convert<TaskUpdateReq> {
@Override
public OperateLog convert(TaskUpdateReq taskUpdateReq) {
OperateLog operateLog = new OperateLog();
operateLog.setId(taskUpdateReq.getTaskId());
return operateLog;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateRecord {

String operateContent() default "";

Class<? extends Convert> convert();

}
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
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.ideaspringboottest.JavaTest.TaskService.Bean.OperateLog;
import org.springframework.stereotype.Component;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
public class OperateAspect {


@Pointcut(value = "@annotation(org.example.ideaspringboottest.JavaTest.TaskService.Annotation.OperateRecord)")
public void pointcut(){}


@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

Object result = proceedingJoinPoint.proceed();
threadPoolExecutor.execute(() -> {

try {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
OperateRecord annotation = methodSignature.getMethod().getAnnotation(OperateRecord.class);
Class<? extends Convert> convert = annotation.convert();
Convert convertInstance = convert.newInstance();
OperateLog operateLog = convertInstance.convert(proceedingJoinPoint.getArgs()[0]);
operateLog.setOperateContent(annotation.operateContent());
operateLog.setResult(result.toString());
System.out.println("在此处将日志记录至数据库:" + operateLog.toString());
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
return result;
}

public ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 1,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(60));


}
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
import org.example.ideaspringboottest.JavaTest.TaskService.Req.TaskInsertReq;
import org.example.ideaspringboottest.JavaTest.TaskService.Req.TaskUpdateReq;
import org.example.ideaspringboottest.JavaTest.TaskService.Service.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync(proxyTargetClass = true)
@ComponentScan({"org.example.ideaspringboottest.*"})
public class IdeaSpringBootTestApplication implements CommandLineRunner {

@Autowired
private TaskService taskService;

public static void main(String[] args) {
new SpringApplication(IdeaSpringBootTestApplication.class).run(args);
}

@Override
public void run(String... args) throws Exception {
// TaskService taskService = new TaskService();
TaskInsertReq taskInsertReq = new TaskInsertReq();
taskInsertReq.setId(1L);
taskInsertReq.setTaskName("任务1");

TaskUpdateReq taskUpdateReq = new TaskUpdateReq();
taskUpdateReq.setTaskId(2L);
taskUpdateReq.setTaskName("任务2");

String s1 = taskService.insert(taskInsertReq);
String s2 = taskService.update(taskUpdateReq);

System.out.println(s1);
System.out.println(s2);
}

}
1
2
3
4
5
6
插入一条任务,任务id为:1, 任务名称为:任务1
更新一条任务,任务id为:2,任务名称为:任务2
任务1
任务2
在此处将日志记录至数据库:OperateLog(id=1, operateContent=新增任务操作, result=任务1)
在此处将日志记录至数据库:OperateLog(id=2, operateContent=更新任务操作, result=任务2)

自定义注解实现数据脱敏

在项目开发的过程中,经常会遇到一些需要脱敏处理后返回给用户的数据,例如身份证号、手机号和用户真实姓名等,现在实践一下:

1
2
3
4
5
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
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
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {
/**
* 脱敏数据类型
*/
DesensitizationTypeEnum type() default DesensitizationTypeEnum.CUSTOMER;

/**
* 开始位置(包含)
*/
int startIndex() default 0;

/**
* 结束位置(不包含)
*/
int endIndex() default 0;
}
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
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/desensitizationTest")
public class DesensitizationController {

@GetMapping("/simple")
public UserPhone simple(){
UserPhone userPhone = new UserPhone();
userPhone.setPhone("12345678910");
return userPhone;
}

@GetMapping("/diverse")
public UserInfo diverse(){
UserInfo userInfo = new UserInfo();
userInfo.setPhone("12345678910");
userInfo.setMyStr("hahahahahaha");
userInfo.setEmail("925853191@qq.com");
userInfo.setIdCard("111000111000111000");
return userInfo;
}

}
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Objects;

@NoArgsConstructor
@AllArgsConstructor
public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {

private DesensitizationTypeEnum type;
private Integer startIndex;
private Integer endIndex;

@Override
public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
switch (type) {
// 自定义类型脱敏
case CUSTOMER:
jsonGenerator.writeString(CharSequenceUtil.hide(str, startIndex, endIndex));
break;
// userId脱敏
case USER_ID:
jsonGenerator.writeString(String.valueOf(DesensitizedUtil.userId()));
break;
// 中文姓名脱敏
case CHINESE_NAME:
jsonGenerator.writeString(DesensitizedUtil.chineseName(String.valueOf(str)));
break;
// 身份证脱敏
case ID_CARD:
jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2));
break;
// 固定电话脱敏
case FIXED_PHONE:
jsonGenerator.writeString(DesensitizedUtil.fixedPhone(String.valueOf(str)));
break;
// 手机号脱敏
case MOBILE_PHONE:
jsonGenerator.writeString(DesensitizedUtil.mobilePhone(String.valueOf(str)));
break;
// 地址脱敏
case ADDRESS:
jsonGenerator.writeString(DesensitizedUtil.address(String.valueOf(str), 8));
break;
// 邮箱脱敏
case EMAIL:
jsonGenerator.writeString(DesensitizedUtil.email(String.valueOf(str)));
break;
// 密码脱敏
case PASSWORD:
jsonGenerator.writeString(DesensitizedUtil.password(String.valueOf(str)));
break;
// 中国车牌脱敏
case CAR_LICENSE:
jsonGenerator.writeString(DesensitizedUtil.carLicense(String.valueOf(str)));
break;
// 银行卡脱敏
case BANK_CARD:
jsonGenerator.writeString(DesensitizedUtil.bankCard(String.valueOf(str)));
break;
default:
}

}

@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
// 判断数据类型是否为String类型
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
// 获取定义的注解 这个方法是直接从当前属性(beanProperty)上获取指定类型的注解(Desensitization.class)。
//如果当前属性上没有直接标注 Desensitization 注解,那么这个方法会返回 null
Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
// 为null
if (desensitization == null) {
// 这个方法是从属性的上下文中获取指定类型的注解(Desensitization.class)。
// 属性的上下文可以是包含该属性的类、方法等,而不仅仅是该属性本身。
// 这个方法会向上遍历属性的上下文,直到找到第一个标注了指定注解的位置,或者到达了根节点。
// 如果找到了指定类型的注解,就会返回该注解;如果没有找到,就会返回 null
desensitization = beanProperty.getContextAnnotation(Desensitization.class);
}
// 不为null
if (desensitization != null) {
// 创建定义的序列化类的实例并且返回,入参为注解定义的type,开始位置,结束位置。
return new DesensitizationSerialize(desensitization.type(), desensitization.startIndex(),
desensitization.endIndex());
}
}
// 根据给定的 Java 类型(可能是一个类、接口、泛型等),找到可以将该类型的对象序列化为 JSON 的序列化器
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
// 查找用于序列化空值(null)的序列化器
return serializerProvider.findNullValueSerializer(null);
}
}
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
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

public enum DesensitizationTypeEnum {
/**
* 自定义
*/
CUSTOMER,

/**
* 用户id
*/
USER_ID,

/**
* 中文名
*/
CHINESE_NAME,

/**
* 身份证号
*/
ID_CARD,

/**
* 座机号
*/
FIXED_PHONE,

/**
* 手机号
*/
MOBILE_PHONE,

/**
* 地址
*/
ADDRESS,

/**
* 电子邮件
*/
EMAIL,

/**
* 密码
*/
PASSWORD,

/**
* 中国大陆车牌 包含普通车辆、新能源车辆
*/
CAR_LICENSE,

/**
* 银行卡
*/
BANK_CARD
}
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
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;

public class MySimpleJacksonSerialize extends JsonSerializer<String> {


/**
* 将输入的手机号进行脱敏处理,然后将脱敏后的手机号序列化成 JSON 格式。在方法内部,调用了 DesensitizedUtil.fixedPhone() 方法来对手机号进行脱敏操作,
* 然后使用 jsonGenerator.writeString() 将脱敏后的手机号写入到 JSON 中。这样,在使用 Jackson 库进行 JSON 序列化时,就会自动调用该方法来处理字符串类型的手机号。
* @param str:表示要序列化的字符串对象,这里是手机号码。
* @param jsonGenerator:用于生成 JSON 内容的生成器对象。在这个方法中,jsonGenerator.writeString() 会将脱敏后的手机号写入到 JSON 中。
* @param serializerProvider:序列化过程中的上下文提供者,包含了一些序列化时可能需要的配置信息和对象信息。
*/
@Override
@SneakyThrows
public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
// 使用我们的hutool工具类进行手机号脱敏
jsonGenerator.writeString(DesensitizedUtil.fixedPhone(String.valueOf(str)));
}
}
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
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

import lombok.Data;
import java.io.Serializable;

@Data
public class UserInfo implements Serializable {
/**
* 自定义
*/
@Desensitization(type = DesensitizationTypeEnum.CUSTOMER, startIndex = 5, endIndex = 10)
private String myStr;
/**
* 手机号
*/
@Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE)
private String phone;
/**
* 邮箱
*/
@Desensitization(type = DesensitizationTypeEnum.EMAIL)
private String email;
/**
* 身份证
*/
@Desensitization(type = DesensitizationTypeEnum.ID_CARD)
private String idCard;


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example.ideaspringboottest.JavaTest.DesensitizationTest;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import java.io.Serializable;

@Data
public class UserPhone implements Serializable {
/**
* 手机号
*/
@JsonSerialize(using = MySimpleJacksonSerialize.class)
private String phone;
}

使用Comparator排序实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Task {

private Long id;

private String taskName;

private Integer serialNumber;

private String priorityNumber;

}
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
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class Test {

public static void main(String[] args) {
Task task1 = new Task(1L, "任务1", 8, "9");
Task task2 = new Task(2L, "任务2", 9, "8");
Task task3 = new Task(3L, "任务3", 1, "8");
Task task4 = new Task(4L, "任务4", 2, "2");
Task task5 = new Task(5L, "任务5", 3, "1");
Task task6 = new Task(6L, "任务6", 3, "3");
Task task7 = new Task(7L, "任务7", 6, "4");
Task task8 = new Task(8L, "任务8", 7, "5");
Task task9 = new Task(9L, "任务9", 10, "7");
Task task10 = new Task(10L, "任务10", 5, "6");
ArrayList<Task> taskList = new ArrayList<>();
taskList.add(task1);
taskList.add(task2);
taskList.add(task3);
taskList.add(task4);
taskList.add(task5);
taskList.add(task6);
taskList.add(task7);
taskList.add(task8);
taskList.add(task9);
taskList.add(task10);
Comparator<Task> comparator = Comparator.comparingInt(task -> Integer.parseInt(task.getPriorityNumber()));
List<Task> result = taskList.stream().sorted(comparator).collect(Collectors.toList());
// comparator.reversed();
comparator = comparator.reversed();
result = taskList.stream().sorted(comparator).collect(Collectors.toList());
// comparator.thenComparing(task -> task.getSerialNumber());
comparator = comparator.thenComparing(task -> task.getSerialNumber());
result = taskList.stream().sorted(comparator).collect(Collectors.toList());
System.out.println(result);
}
}