设计模式

反射

image-20241113154244223

单例模式

静态代码块

类在jvm中加载时执行,只执行一次。

“静态代码块只执行一次”的意思是,在Java程序运行期间,当一个类被加载到Java虚拟机(JVM)时,与该类关联的静态代码块会被自动执行,而且这个执行过程只发生一次。

这里有几个关键点需要理解:

  1. 类加载:当程序首次引用一个类(例如,通过创建该类的实例,访问该类的静态方法或静态字段等),JVM会检查该类是否已经被加载。如果没有,JVM会加载该类,包括执行其静态初始化代码(即静态代码块)。
  2. 静态代码块的执行:静态代码块在类加载过程中执行,而不是在创建类的实例时执行。这意味着,无论创建多少个类的实例,静态代码块都只会执行一次。
  3. 只执行一次:静态代码块的执行与类的实例化无关。一旦类被加载,静态代码块就执行了,后续对类的任何引用(包括创建更多实例)都不会再次触发静态代码块的执行。

这种特性使得静态代码块非常适合用于执行只需要进行一次的初始化操作,例如创建单例对象、初始化静态资源或设置全局配置等。

下面是一个简单的例子来说明这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Example {
static {
System.out.println("静态代码块执行");
}

public static void main(String[] args) {
System.out.println("main方法开始");

Example example1 = new Example();
Example example2 = new Example();

System.out.println("main方法结束");
}
}

在这个例子中,当你运行main方法时,输出将会是:

1
2
3
静态代码块执行
main方法开始
main方法结束

尽管在main方法中创建了两个Example类的实例,但静态代码块只执行了一次。

非静态代码块

非静态代码块(也称为实例初始化块)是在创建类的实例时执行的代码块,它不是在类加载时执行,而是在每次创建对象时都会执行。非静态代码块通常用于执行一些通用的实例初始化操作,这些操作对于类的所有实例都是相同的。

非静态代码块的语法如下:

1
2
3
4
5
6
public class MyClass {
// 非静态代码块
{
// 初始化代码
}
}

非静态代码块在每次创建类的实例时都会执行,并且在构造函数之前执行。如果有多个非静态代码块,它们将按照在类中出现的顺序依次执行。

下面是一个简单的例子来说明非静态代码块的执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Example {
// 非静态代码块
{
System.out.println("非静态代码块执行");
}

public Example() {
System.out.println("构造函数执行");
}

public static void main(String[] args) {
System.out.println("main方法开始");

Example example1 = new Example();
Example example2 = new Example();

System.out.println("main方法结束");
}
}

在这个例子中,当你运行main方法时,输出将会是:

1
2
3
4
5
6
main方法开始
非静态代码块执行
构造函数执行
非静态代码块执行
构造函数执行
main方法结束

可以看到,每次创建Example类的实例时,非静态代码块都会执行,并且在构造函数之前执行。因此,对于example1example2两个实例,非静态代码块都分别执行了一次。

饿汉式

在Java中,static关键字用于定义类级别的成员,这些成员不属于类的任何特定实例,而是属于整个类。静态代码块(也称为静态初始化器)是使用static关键字声明的代码块,它在类被加载到JVM(Java虚拟机)时执行,并且只执行一次。

静态代码块通常用于初始化静态变量或执行一些只需要在类加载时执行一次的操作。它的基本语法如下:

1
2
3
4
5
6
public class MyClass {
static {
// 静态代码块
// 初始化静态变量或执行其他操作
}
}

在你提供的代码片段中,静态代码块用于创建一个Singleton类的实例,并将其赋值给静态变量instance。这是一种常见的单例模式实现方式,称为“饿汉式”单例模式。以下是完整的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static Singleton instance;

// 静态代码块,在类加载时执行,创建Singleton实例
static {
instance = new Singleton();
}

// 私有构造函数,防止外部创建实例
private Singleton() {}

// 提供公共的静态方法获取Singleton实例
public static Singleton getInstance() {
return instance;
}
}

在这个例子中,当Singleton类被加载到JVM时,静态代码块会自动执行,并创建一个Singleton实例。之后,通过调用getInstance()方法,可以获取这个已经创建的实例。由于静态代码块只执行一次,因此Singleton类也只会创建一个实例,从而实现了单例模式。

需要注意的是,这种“饿汉式”单例模式在类加载时就创建了实例,可能会造成资源浪费,如果实例的创建需要消耗大量资源或者实例的创建依赖于某些配置信息,那么可以考虑使用“懒汉式”单例模式,在第一次使用时再创建实例。

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 懒汉式:内部类
*/
public class Singleton {
private Singleton() {
}

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

public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 懒汉式1:线程不安全
*/
public class Singleton {
private static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

工厂模式

简单工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 简单工厂:把耦合逻辑抽象出来不影响业务代码
*/
public class CoffeeFactory {
public Coffee CreateCoffee(String coffeeType) {
Coffee coffee = null;
if (coffeeType.equalsIgnoreCase("A")) {
coffee = new ACoffee();
} else if (coffeeType.equalsIgnoreCase("B")) {
coffee = new BCoffee();
}else {
throw new RuntimeException("没有您需要的咖啡种类!");
}
return coffee;
}
}

静态工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 简单工厂:把耦合逻辑抽象出来不影响业务代码
*/
public class CoffeeFactory {
public static Coffee CreateCoffee(String coffeeType) {
Coffee coffee = null;
if (coffeeType.equalsIgnoreCase("A")) {
coffee = new ACoffee();
} else if (coffeeType.equalsIgnoreCase("B")) {
coffee = new BCoffee();
}else {
throw new RuntimeException("没有您需要的咖啡种类!");
}
return coffee;
}
}

工厂方法

只考虑生产同级别的产品(如下面这个例子只生产咖啡!)

对多态性的完美应用。

image-20241113145642446

工厂也抽象出一个工厂接口

1
2
3
4
5
6
/**
* 工厂抽象接口
*/
public interface CoffeeFactory {
public Coffee createCoffee();
}

然后咖啡分类继承出不同的咖啡工厂。

1
2
3
4
5
6
7
8
9
10
/**
* 继承工厂接口,专门生成A咖啡
*/
public class ACoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
Coffee coffee = new ACoffee();
return coffee;
}
}

咖啡店实现类写入工厂接口的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 咖啡店负责创造实例
*/
public class CoffeeStore {
private CoffeeFactory coffeeFactory;

public void setCoffeeFactory(CoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}

public Coffee OrderCoffee() {
Coffee coffee = this.coffeeFactory.createCoffee();
coffee.addMike();
coffee.addSugar();
return coffee;
}
}

抽象工厂

多级别产品的生产,不只咖啡。

image-20241113150311054

简单工厂+配置文件

通过配置文件来进行工厂类的实现

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
public class CoffeeFactory {
private static HashMap<String, Coffee> coffeeMap = new HashMap<String,Coffee>();

//加载类的时候加载配置文件,只加载一次(所有使用静态代码块
static {
//创建配置文件对象
Properties prop = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
prop.load(is);
//获取map中的所有key成为一个set
Set<Object> keySet = prop.keySet();
for (Object key : keySet) {
//获取类名(引用路径
String className = prop.getProperty(key.toString());
//通过反射创建类
Class clazz = Class.forName(className);//获取反射对象
Coffee o = (Coffee) clazz.newInstance();
coffeeMap.put((String) key, o);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static Coffee CreateCoffee(String type) {
return coffeeMap.get(type);
}
}
1
2
3
//配置文件,等于左边与传入type一致
A=com.patterns.factory_config.ACoffee
B=com.patterns.factory_config.BCoffee

注意:反射

这行代码的作用是从类路径(classpath)中加载名为 bean.properties 的资源文件,并将其包装在一个 InputStream 对象中。以下是对这行代码的详细解释:

代码解析

1
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
  1. **CoffeeFactory.class**:

    • 获取 CoffeeFactory 类的 Class 对象。
  2. **.getClassLoader()**:

    • 调用 Class 对象的 getClassLoader() 方法,获取加载该类的类加载器(ClassLoader)。
  3. **.getResourceAsStream("bean.properties")**:

    • 调用类加载器的 getResourceAsStream(String name) 方法,传入资源文件的名称 "bean.properties"
    • getResourceAsStream 方法会在类路径(classpath)中查找指定的资源文件,并返回一个 InputStream 对象,如果找不到该资源文件,则返回 null

类路径(classpath)

类路径是 Java 虚拟机(JVM)用于查找类文件和资源文件的路径集合。它可以包含以下几种类型的路径:

  • 编译后的 .class 文件所在的目录。
  • JAR 文件中的类和资源。
  • 通过 -classpath-cp 命令行选项指定的路径。

资源文件的加载

在 Java 应用程序中,资源文件通常放在类路径下的某个目录中,例如 src/main/resources 目录(对于 Maven 项目)。通过 getResourceAsStream 方法,可以方便地从类路径中加载这些资源文件。

示例

假设你的项目结构如下:

1
2
3
4
src
└── main
└── resources
└── bean.properties

并且你在代码中使用上述行来加载 bean.properties 文件,那么 JVM 会在类路径中查找该文件,并将其包装在一个 InputStream 对象中。

使用示例

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

public class CoffeeFactory {
public static void main(String[] args) {
try (InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties")) {
if (is == null) {
throw new RuntimeException("Resource not found: bean.properties");
}

Properties properties = new Properties();
properties.load(is);

// 使用 properties 对象
System.out.println(properties.getProperty("key"));
} catch (Exception e) {
e.printStackTrace();
}
}
}

在这个示例中,我们使用 getResourceAsStream 方法加载 bean.properties 文件,并将其内容读取到一个 Properties 对象中,然后输出某个属性的值。

通过这种方式,可以方便地在 Java 应用程序中加载和使用资源文件。

代理模式

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
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
/**
* 动态代理jdk
* 获取代理对象的工厂类
* jdk提供了proxy代理对象
* 代理对象也继承了对应接口(SellTickets)
*/
public class ProxyFactory {
private TrainStation trainStation = new TrainStation();

public SellTickets getProxyObject() {
/**
* Proxy.newProxyInstance(形参如下)
* ClassLoader loader:类加载器,用于加载代理类,可以通过类目标对象获取类加载器
* Class<?>[] interfaces:代理类和目标对象都实现的接口的字节码对象
* InvocationHandler h:代理对象调用的处理程序
*/
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
trainStation.getClass().getClassLoader(),
trainStation.getClass().getInterfaces(),
new InvocationHandler() {
/**
*
* @param proxy 被调用方法的代理实例(就是proxyObject一般不使用
*
* @param method 对应于在代理实例上调用的接口方法的 {@code Method} 实例。
* 该 {@code Method} 对象的声明类将是方法声明所在的接口,
* 这可能是代理接口继承该方法通过的超接口。
*
* @param args 包含在代理实例上的方法调用中传递的参数值的对象数组,
* 或者如果接口方法不接受参数,则为 {@code null}。
* 原始类型的参数被包装在适当的原始包装器类的实例中,
* 例如 {@code java.lang.Integer} 或 {@code java.lang.Boolean}。
*
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("收取代理费用100元!(jdk动态代理)");
//使用反射调用method对象
//这个意思就是调用了目标对象中的method方法了(如sell,当然别的也行,obj就是返回值
Object object = method.invoke(trainStation, args);
return object;
}
}
);
return proxyObject;
}

}

代理模式区别:

静态代理:如果有新的方法需要添加,那么接口、目标对象、代理对象就都要新增方法,而动态代理就不用在代理对象中再新增,因为我们使用了代理工厂对代理对象进行动态代理。

适配器模式

类适配器模式