下面的例子都是从JDK 21的标准库源代码里面截取的,源代码在${JAVA_HOME}/lib/src.zip
,解压就ok了
单例模式
分为饿汉式、懒汉式。确保一个类只能有一个实例,无需频繁创建和销毁对象,节约了系统资源,提高系统的性能
Runtime
类就是典型的单例模式,因为获取其实例是Runtime.getRuntime()
而不是new Runtime()
。new出来的是不同的对象实例,而getRuntime()
返回的总是同一个对象引用。
饿汉式
我觉得sun.misc.Unsafe
类就比较典型,每次获取实例都要使用Unsafe#getUnsafe
方法
public final class Unsafe {
private Unsafe() {} // 私有化构造器
private static final Unsafe UNSAFE = new Unsafe(); // 必须得是静态的!!
public static Unsafe getUnsafe() {
return UNSAFE;
}
}
懒汉式
就是懒加载,获取时才创建实例
这里有个
synchronized
防止多线程并发获取是多new
了
你可能疑惑为什么要检查两次null
。因为为了防止多线程并发时,第一次检查完后怕有别的线程已经创建了,所以再检查一次
public class System {
private static volatile Console cons;
public Console console() {
if (cons == null) {
synchronized (System.class) {
if (cons == null) {
cons = SharedSecrets.getJavaIOAccess().console();
}
}
}
return cons;
}
}
工厂模式
工厂差不多就是传入一个对象,然后工厂内部一顿set猛如虎后输出
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
@SuppressWarnings("removal")
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement()
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
@Test
void main() {
var f = new DefaultThreadFactory();
f.newThread(() -> System.out.println("Hello, World!")).start();
}
工厂方法模式
让我们来回顾一段代码
import java.util.ExecutorService;
import java.util.Executors;
// JDK 21预览匿名类
void main() {
ExecutorService pool = Executors.newFixedThreadExecutor(1);
Future<String> future = pool.submit(() -> "Hello, World!");
// ...
}
Executors的各个静态方法就是ExecutorService的工厂方法
适配器模式
下面那个例子非常典型,来自于java.io.File
,File
其实调用的是接口FileSystem
public class File {
private static final FileSystem FS = DefaultFileSystem.getFileSystem();
}
接下来DefaultFileSystem#getFileSystem
就有意思了,因为不同的平台代码是不同的
final class DefaultFileSystem {
private DefaultFileSystem() {}
public static FileSystem getFileSystem() {
// MacOS, Linux, Unix
return new java.io.UnixLikeFileSystem();
// Windows NT Kernal
return new java.io.Win32NTFileSystem()
}
}
策略模式
有些方法体现了策略模式,譬如:
List.sort(Comparator)
我们可以自定义比较器,生序排序:
l.sort((o1, o2) -> o1.compareTo(o2))
倒序排序:
l.sort((o1, o2) -> o2.compareTo(o1))
你应该可以传一个null
,将会使用默认的比较器
外观模式
主要跟封装有关。封装到用户无需关注底层实现。譬如java.lang.Math
类,其封装的其实是java.lang.StrictMath
,然后最终实现是java.lang.FdLibm
。所以Math
是FdLibm
的外观
设计原则
单一职责原则(Single Responsibility Principle, SRP):一个类只应该有一个引起变化的原因。换句话说,一个类应该只有一个职责,只有一个改变它的原因。这样可以降低类的复杂度,提高可维护性。
最典型的例子就是java.util.ArrayList#sort
主要代码周围是并发修改检查,不允许超过一个线程修改,否则panic
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c); // <=== 主要代码
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
}
elementData
是ArrayList的一个成员变量,储存数据的数组,签名为:
transient Object[] elementData;
调用的其实是java.util.Arrays#sort
,这是一个数组工具类,源代码
public final class Arrays {
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, 0, a.length);
}
}
java.util.DualPivotQuicksort
类集成了各种排序算法,包括插入排序和计数排序,最主要还是双端快排,这是快速排序的加强版,源代码就不放了,就是你平常写的那些排序,但不过写的比你的好一点,没有更多封装了。感兴趣的可以看看源代码实现。
java.util.ArrayList
java.util.Arrays
负责集成各种数组工具方法,排序算法体格太大了应该分开
java.util.DualPivotQuicksort
负责排序算法的实现
实战
下面的例子涉及到 SSM 框架中的 MyBatis。初学者,基础不好的,或者大一生请绕行,除非你特别感兴趣,否则可能消磨你的学习兴趣。
假设我们要用 Apache MyBatis 框架写一个数据库持久业务,我们可以把业务代码和初始化 SqlSessionFactory的代码分开写,独立写成一个工厂类MyBatisFactories#getSqlSessionFactory
如下:
package demo.mybatis.util;
import org.apache.ibatis.io.SqlSession;
import org.apache.ibatis.io.SqlSessionFactory;
import org.apache.ibatis.io.SqlSessionFactoryBuilder;
import org.apache.ibatis.io.Resources;
public final class MyBatisFactories {
private MyBatisFactories() { throw new InternalError(); }
public static SqlSessionFactory getSqlSessionFactory() {
return sessionFactory();
}
public static SqlSession openSession() {
return getSqlSessionFactory().openSession();
}
private static final SqlSessionFactory sessionFactory;
static {
try {
final String cfg = MyBatis.getProperty("configFilename");
sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream(cfg));
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
}
初始化
SqlSessionFactory
一定要在static
代码块里保证其只加载一次。多次加载会浪费I/O资源。我相信你的教授曾经跟你讲过,如果没有,那就记住
package demo.mybatis.util;
import java.util.Properties;
public class MyBatis {
public static String getProperty(String name) {
return MyBatisProperties.props.get(name);
}
static class MyBatisProperties {
static final Properties props = new Properties();
static {
props.load(new FileInputStream("mybatis-cfg.properties"));
}
}
}
最终DAO业务代码变得这么简洁:
import demo.mybatis.util.MyBatisFactories;
import demo.mybatis.mapper.UserMapper;
@Test
void main() {
var session = MyBatisFactories.openSession();
try {
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.insertUser(new User("root", "toor"));
} catch (Exception e) {
session.rollback();
throw e;
} finally {
session.close();
}
}