设计模式
反射

单例模式
静态代码块
类在jvm中加载时执行,只执行一次。
“静态代码块只执行一次”的意思是,在Java程序运行期间,当一个类被加载到Java虚拟机(JVM)时,与该类关联的静态代码块会被自动执行,而且这个执行过程只发生一次。
这里有几个关键点需要理解:
- 类加载:当程序首次引用一个类(例如,通过创建该类的实例,访问该类的静态方法或静态字段等),JVM会检查该类是否已经被加载。如果没有,JVM会加载该类,包括执行其静态初始化代码(即静态代码块)。
- 静态代码块的执行:静态代码块在类加载过程中执行,而不是在创建类的实例时执行。这意味着,无论创建多少个类的实例,静态代码块都只会执行一次。
- 只执行一次:静态代码块的执行与类的实例化无关。一旦类被加载,静态代码块就执行了,后续对类的任何引用(包括创建更多实例)都不会再次触发静态代码块的执行。
这种特性使得静态代码块非常适合用于执行只需要进行一次的初始化操作,例如创建单例对象、初始化静态资源或设置全局配置等。
下面是一个简单的例子来说明这一点:
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
类的实例时,非静态代码块都会执行,并且在构造函数之前执行。因此,对于example1
和example2
两个实例,非静态代码块都分别执行了一次。
饿汉式
在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;
static { instance = new Singleton(); }
private 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
|
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; } }
|
工厂方法
只考虑生产同级别的产品(如下面这个例子只生产咖啡!)
对多态性的完美应用。

工厂也抽象出一个工厂接口
1 2 3 4 5 6
|
public interface CoffeeFactory { public Coffee createCoffee(); }
|
然后咖啡分类继承出不同的咖啡工厂。
1 2 3 4 5 6 7 8 9 10
|
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; } }
|
抽象工厂
多级别产品的生产,不只咖啡。

简单工厂+配置文件
通过配置文件来进行工厂类的实现
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); 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
| 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");
|
**CoffeeFactory.class
**:
- 获取
CoffeeFactory
类的 Class
对象。
**.getClassLoader()
**:
- 调用
Class
对象的 getClassLoader()
方法,获取加载该类的类加载器(ClassLoader
)。
**.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);
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
|
public class ProxyFactory { private TrainStation trainStation = new TrainStation();
public SellTickets getProxyObject() {
SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance( trainStation.getClass().getClassLoader(), trainStation.getClass().getInterfaces(), new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("收取代理费用100元!(jdk动态代理)"); Object object = method.invoke(trainStation, args); return object; } } ); return proxyObject; }
}
|
代理模式区别:
静态代理:如果有新的方法需要添加,那么接口、目标对象、代理对象就都要新增方法,而动态代理就不用在代理对象中再新增,因为我们使用了代理工厂对代理对象进行动态代理。
适配器模式
类适配器模式