免责声明:本人设计模式学得一坨,因此本文没有详细的设计模式教程,只有针对几个问题的解答。教程?等老子重新学完再说😋

设计模式

说到底设计模式就是软件编程中的最佳实践,是前人面对具体问题,不断试错不断积累,总结出来的解决方案,的是为了更好的组织代码,提高代码的扩展性,维护性和复用的能力,是一种思想和方法论,没有严格的固定模板,可以根据场景灵活调整,也可以组合使用

谈谈你知道的设计模式?

参考链接:https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Java%e6%a0%b8%e5%bf%83%e6%8a%80%e6%9c%af%e9%9d%a2%e8%af%95%e7%b2%be%e8%ae%b2/14%20%e8%b0%88%e8%b0%88%e4%bd%a0%e7%9f%a5%e9%81%93%e7%9a%84%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%ef%bc%9f.md

大致按照模式的应用目标分类,设计模式可以分为创建型模式、结构型模式和行为型模式。

  • 创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式(Factory、Abstract Factory)、单例模式(Singleton)、构建器模式(Builder)、原型模式(ProtoType)。
  • 结构型模式,是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。常见的结构型模式,包括桥接模式(Bridge)、适配器模式(Adapter)、装饰者模式(Decorator)、代理模式(Proxy)、组合模式(Composite)、外观模式(Facade)、享元模式(Flyweight)等。
  • 行为型模式,是从类或对象之间交互、职责划分等角度总结的模式。比较常见的行为型模式有策略模式(Strategy)、解释器模式(Interpreter)、命令模式(Command)、观察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(Template Method)、访问者模式(Visitor)。

手撕单例模式?双检锁单例模式?

什么是单例模式?

想象一下你在管理一个公司。在整个公司中,只能有一个总经理,不能同时存在两个总经理,这就类似于单例模式的概念。

单例模式的核心思想就是:确保一个类在整个应用程序中只能创建一个对象实例,并提供一个全局访问点。

举一个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CompanyManager {
// 1. 私有的静态实例,用于保存单例对象
private static CompanyManager instance;
// 2. 私有构造方法,防止外部直接创建实例
private CompanyManager() {
// 初始化工作
}
// 3. 公共的静态方法,用于获取实例
public static CompanyManager getInstance() {
// 如果实例不存在,才创建新实例
if (instance == null) {
instance = new CompanyManager();
}
return instance;
}
// 4. 业务方法
public void manage() {
System.out.println("管理公司业务...");
}
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
// 获取公司经理实例
CompanyManager manager1 = CompanyManager.getInstance();
CompanyManager manager2 = CompanyManager.getInstance();

// 验证是否是同一个实例
System.out.println(manager1 == manager2); // 输出: true

// 使用实例
manager1.manage();
}
}

这个例子的关键点:

  1. 构造方法是私有的,这样其他类就不能直接创建新的实例
  2. 提供一个静态方法来获取实例,确保只创建一个对象
  3. 使用静态变量来保存这个唯一的实例

单例模式的常见应用场景:

  • 配置管理器:整个应用只需要一个配置管理器
  • 数据库连接池:统一管理数据库连接资源
  • 日志记录器:集中管理日志记录

不过需要注意,上面的示例是最简单的单例实现,在多线程环境下可能会有问题。在实际开发中,我们通常会使用双重检查锁定或者枚举来实现更安全的单例模式。


想象一下你们公司的会议室系统。在预订会议室时,为了避免同一个时间段被多个人预订,我们需要一个预订检查机制:

  1. 第一次检查:快速看一眼会议室是否空闲
  2. 如果看起来空闲,我们会锁定预订系统
  3. 第二次检查:再次确认会议室确实空闲
  4. 最后才进行预订

这就类似于双检锁的工作方式。下面用代码来说明:

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
public class CompanyManager {
// volatile关键字确保多线程环境下instance变量的可见性
private static volatile CompanyManager instance;

private CompanyManager() {
// 私有构造方法
}

public static CompanyManager getInstance() {
// 第一次检查:无锁检查,提高性能
if (instance == null) {
// 加锁
synchronized (CompanyManager.class) {
// 第二次检查:确保没有其他线程已经创建实例
if (instance == null) {
instance = new CompanyManager();
}
}
}
return instance;
}

public void manage() {
System.out.println("管理公司业务...");
}
}

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
// 创建多个线程同时获取实例
Runnable task = () -> {
CompanyManager manager = CompanyManager.getInstance();
System.out.println(Thread.currentThread().getName() + ": " + manager);
};

// 启动多个线程
for (int i = 0; i < 5; i++) {
new Thread(task).start();
}
}
}

双检锁的好处:

  1. 线程安全:确保在多线程环境下也只会创建一个实例
  2. 性能优化:只有在第一次创建实例时才需要加锁
  3. 延迟加载:只有在真正需要时才创建实例

Spring等框架中使用了哪些模式?

  1. 单例模式(Singleton Pattern)
  • Spring中的Bean默认都是单例的
  • Spring通过ConcurrentHashMap实现单例注册表的特殊单例模式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Spring中单例的实现
    public class DefaultSingletonBeanRegistry {
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
    this.singletonObjects.put(beanName, singletonObject);
    }
    }
    }
  1. 工厂模式(Factory Pattern)
  • BeanFactory用于创建和管理Bean实例
  • 将Bean的创建过程封装,客户端只需要知道Bean的名字
    1
    2
    3
    // Spring中的工厂模式示例
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserService userService = context.getBean("userService", UserService.class);
  1. 代理模式(Proxy Pattern)
  • Spring AOP中使用JDK动态代理和CGLIB代理
  • 实现方法拦截和增强
    1
    2
    3
    4
    5
    6
    7
    8
    // AOP代理示例
    @Aspect
    public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
    System.out.println("Before method execution");
    }
    }
  1. 模板方法模式(Template Method Pattern)
  • JdbcTemplate封装了数据库操作
  • RestTemplate封装了REST请求操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // JdbcTemplate使用示例
    public class UserDao {
    private JdbcTemplate jdbcTemplate;

    public User getUser(long id) {
    return jdbcTemplate.queryForObject(
    "SELECT * FROM users WHERE id = ?",
    new Object[]{id},
    (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))
    );
    }
    }
  1. 观察者模式(Observer Pattern)
  • Spring事件机制(ApplicationEvent和ApplicationListener)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 自定义事件
    public class UserCreatedEvent extends ApplicationEvent {
    public UserCreatedEvent(User user) {
    super(user);
    }
    }

    // 事件监听器
    @Component
    public class UserCreatedListener implements ApplicationListener<UserCreatedEvent> {
    @Override
    public void onApplicationEvent(UserCreatedEvent event) {
    // 处理用户创建事件
    }
    }
  1. 适配器模式(Adapter Pattern)
  • Spring MVC中的HandlerAdapter
  • 将不同类型的处理器(handlers)适配到统一的接口
  1. 责任链模式(Chain of Responsibility Pattern)
  • Spring Security的过滤器链
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    .addFilter(new JwtAuthenticationFilter())
    .addFilter(new JwtAuthorizationFilter());
    }
    }
  1. 策略模式(Strategy Pattern)
  • Spring在资源访问时使用Resource接口
  • 不同的资源类型(ClassPathResource、FileSystemResource等)实现不同的访问策略
  1. 建造者模式(Builder Pattern)
  • 用于构建复杂对象,如SpringSecurity中的配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Security配置的Builder模式
    http
    .authorizeRequests()
    .antMatchers("/public/**").permitAll()
    .antMatchers("/admin/**").hasRole("ADMIN")
    .and()
    .formLogin()
    .loginPage("/login")
    .permitAll();

Java8新特性

lambda有什么好处?应用场景是什么?

传统使用场景:

定义接口及其定义方法-创造类实现接口-创造形参为接口的函数(面向接口编程)-创造类实例并调用接口中的方法。。。

Lambda的好处:

·简化代码:减少匿名内部类的冗余代码,简化创建类到创建类实例的所有步骤,直接调用方法

·支持函数式编程:使 Java 能更好地支持函数式编程范式

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
//注意:函数式接口只能有唯一一个定义方法,否则不能使用Lamda表达式
public interface Printable {
void print();
}
public class Print {
public static void main(String[] args){
Printable Lamda = () -> System.out.println("Hello World");
Lamda.print();//Hello World
}
}

当然,实践中经常用在stream流的处理上,使得代码变得十分简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在集合操作中的应用
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 过滤
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());

// 转换
List<Integer> doubled = numbers.stream()
.map(n -> n * 2)
.collect(Collectors.toList());

// 排序
List<String> words = Arrays.asList("banana", "apple", "orange");
Collections.sort(words, (s1, s2) -> s1.compareTo(s2));

方法引用是什么?有什么好处?应用场景是什么?

方法引用是 Java 8 引入的特性,它是 Lambda 表达式的一种简化写法。使用操作符 “::” 将方法名和对象或类的名字分隔开来。

当 Lambda 表达式仅仅是调用一个已存在的方法时,优先使用方法引用

如果需要对参数进行转换或者有额外的处理逻辑,则使用 Lambda 表达式

使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
//排序
// Lambda: names.sort((a, b) -> a.compareTo(b))
names.sort(String::compareTo);

// 打印集合元素
// Lambda: names.forEach(x -> System.out.println(x));
names.forEach(System.out::println);

// 转换操作
// Lambda: names.stream().map(str -> str.toUpperCase())
List<String> upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
upperNames.forEach(System.out::println);

//Optional类使用
Optional<String> optional = Optional.of("Hello");
// Lambda: optional.map(str -> str.length())
Optional<Integer> length = optional.map(String::length);

Java 中的 stream 流知道吗,有哪些功能?应用场景是什么?

参考链接:https://juejin.cn/post/7156055319490232327,侵删

StreamAPI 是一种用于处理数据的抽象概念,而不是具体的数据类型。它代表的是一种操作方式和思维模式。这就像是一条传送带(Stream),数据从一端输入,经过各种处理(过滤、转换等),最后从另一端输出结果。传送带本身不存储任何东西,它只是定义了”如何处理”的过程。

概括讲,可以将Stream流操作分为3种类型:

  • 创建Stream
  • Stream中间处理
  • 终止Steam

怎么获取流

有很多方法获取 Stream ,一般最常见的是从 Collection 对象中获取 Stream。由于集合对象都实现了 Collection 接口,所以通过接口里定义的 stream 方法获救获取到由集合元素构成的 Steam。下面是一个从 List 对象获取 Stream 的例子。

1
2
3
4
5
6
7
List<String> items = new ArrayList<String>();

items.add("one");
items.add("two");
items.add("three");

Stream<String> stream = items.stream();

流处理

举例:

1
2
3
4
5
6
7
8
9
10
11
12
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5);

numbers.stream()
.filter(n -> n > 2) // [3,3,4,5]
.map(n -> n * 2) // [6,6,8,10]
.flatMap(n -> Stream.of(n)) // [6,6,8,10]
.limit(3) // [6,6,8]
.skip(1) // [6,8]
.distinct() // [6,8]
.sorted() // [6,8]
.peek(System.out::println) // 打印[6,8]
.collect(Collectors.toList());

流终结

举例:

1
2
3
4
5
6
7
8
9
10
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

long count = nums.stream().count(); // 5
Optional<Integer> max = nums.stream().max(Integer::compareTo); // 5
Optional<Integer> first = nums.stream().findFirst(); // 1
boolean anyMatch = nums.stream().anyMatch(n -> n > 3); // true
boolean allMatch = nums.stream().allMatch(n -> n > 0); // true
List<Integer> collected = nums.stream().collect(Collectors.toList());
Integer[] array = nums.stream().toArray(Integer[]::new);
nums.stream().forEach(System.out::println);

Optional是什么?应用场景是什么?

Optional(可选值)是一种设计模式和编程概念,用于处理可能为空的值。

主要应用场景:

  1. 空值处理:避免NPE(空指针异常)

    1
    2
    3
    //ofNullable() 创建一个即可空又可非空的 Optional 对象
    Optional<String> name = Optional.ofNullable(user.getName());
    String displayName = name.orElse("Unknown");
  2. 方法返回值:明确表达返回值可能不存在

    1
    2
    3
    4
    public Optional<User> findUserById(Long id) {
    // 可能找不到用户
    return Optional.ofNullable(userDao.get(id));
    }
  3. 参数校验:优雅处理可选参数

    1
    2
    3
    public void processOrder(Order order, Optional<String> couponCode) {
    couponCode.ifPresent(code -> applyDiscount(order, code));
    }

不建议使用get方法,没有确认值是否存在的情况下可能会导致NoSuchElementException,而加入判断条件又违背了引入Optional的初衷。因此,Optional的最佳实践如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 示例一:值存在时执行println,否则不执行
//isPresent() 判断一个 Optional 对象是否存在
optionalValue.ifPresent(System.out::println);

// 示例二:值存在时执行前者,否则执行后者
optionalValue.ifPresentOrElse(
System.out::println,
() -> System.out.println("empty")
);

// 示例三:两种获取值或默认值的方式
//懒汉式与饿汉式
String value = optionalValue.orElse("default");
String value = optionalValue.orElseGet(() -> "default");

// 示例四:值不存在时抛出异常
String value = optionalValue.orElseThrow();

当然,Optional最大的优点是能与函数式编程相结合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        User user1 = new User("张三", new Address("北京", "朝阳区"));
User user2 = new User("李四", null);
User user3 = null;

List<User> users = Arrays.asList(user1, user2, user3);

users.forEach(user -> {
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知城市");
System.out.println("用户城市: " + city);
});
//用户城市: 北京
//用户城市: 未知城市
//用户城市: 未知城市

在实际应用场景中,大对象 json套json,如果看里面某个字段是不是空用这个链式map调用很直观,不然一堆if嵌套才是折磨。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Optional方式
optional.map(obj->obj.getA())
.map(a->a.getB())
.map(b->b.getC())
.orElse(null)

// if嵌套方式
if(obj != null) {
if(obj.getA() != null) {
if(obj.getA().getB() != null) {
return obj.getA().getB().getC();
}
}
}
return null;

有人说,Optional的出现是为了解决函数式编程中判空副作用的问题。举例来说,一个快递员给一户人家送快递,这户人家是一家三口和一条狗,狗不能签收,但是三口中任意都能签收。在optional中null就是那个狗,一家三口就是其余可能的值。利用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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class DeliveryExample {
// 快递包裹
static class Package {
private String content;
public Package(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}

// 家庭成员接口
interface FamilyMember {
boolean canSignPackage();
}

// 人类成员
static class Human implements FamilyMember {
private String name;

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

@Override
public boolean canSignPackage() {
return true;
}
}

// 狗
static class Dog implements FamilyMember {
@Override
public boolean canSignPackage() {
return false;
}
}

// 模拟快递签收
public static Optional<String> deliverPackage(Package pack, FamilyMember receiver) {
if (receiver.canSignPackage()) {
return Optional.of(pack.getContent());
}
return Optional.empty();
}

public static void main(String[] args) {
Package pack = new Package("快递内容");
FamilyMember father = new Human("父亲");
FamilyMember dog = new Dog();

// 人类签收 - 成功
deliverPackage(pack, father)
.ifPresentOrElse(
content -> System.out.println("签收成功:" + content),
() -> System.out.println("签收失败")
);

// 狗签收 - 失败
deliverPackage(pack, dog)
.ifPresentOrElse(
content -> System.out.println("签收成功:" + content),
() -> System.out.println("签收失败")
);
}
}

·