❤️ 处理对象
💛 如何创建对象
💙 避免创建不必要对象
- 不可变对象总是可以复用
java
// ❌
String s = new String("123");
// ✔️
String s = "123";- 对于频繁需要创建的对象,我们应该缓存,比如
Jackson 的 objectMapper,OKHttp 的 OKHttpClient - 优先使用基本类型,而不是封装类
💙 尽量避免 JavaBeans 模式创建对象
JavaBeans 模式是通过无参构造函数创建对象,然后使用 setter 方法逐个设置属性
java
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);缺点 :
- 由于对象的构造过程被拆分为多个步骤,在所有属性设置完成之前,JavaBean 可能处于不完整或不一致的状态。这个状态下,某些操作可能依赖于尚未设置的属性,从而导致对象在使用时行为异常或失败
- 无法对多个属性做有效性检查
💙 静态工厂
[!quote] 静态工厂 静态工厂 就是在类中声明一个静态方法,用于创建对象
from类型转换方法,用于将某个类型的值转换成目标类型的实例。通常用于将一个已有的对象或值转换为另一种表示形式的对象
java
Date d = Date.from(instant);of聚合方法,用于创建包含多个参数的实例
java
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);valueOf这是一个更为详细的可选方案,用于从给定的值创建相应的对象
java
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);getInstance获取一个已经存在的实例
java
StackWalker luke = StackWalker.getInstance(options);newInstance每次调用都返回一个新的实例
java
Object newArray = Array.newInstance(classObject, arrayLen);💙 Builder 模式
- 创建拥有 Builder 模式的类
java
public abstract class Pizza {
// 配料枚举
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
// 创建一个空的,专门用于存放配料枚举的集合
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
// 这两个方法子类必须重写,所以定义为抽象方法
abstract Pizza build(); // 让子类创建自己的实例,自己new出来
protected abstract T self(); // 为了让子类调用addTopping()方法,能返回子类自己的实例
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}- 子类
java
public class NyPizza extends Pizza {
public enum Size {SMALL, MEDIUM, LARGE}
private final Size size;
@AllArgsConstructor
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}- 使用
java
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE)
.addTopping(ONION)
.build();💛 单例模式
设计单例模式时,要把其构造方法设为私有,这样就不能实例化这个类,也保证其子类也不能实例化
java
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis(){}
}如果类实现了序列化接口,还要额外重写 readResolve() 方法 :
java
// JVM 在反序列化时会自动调用该方法
@Serial
private Object readResolve() {
return INSTANCE;
}枚举是实现单例的最佳方式 :
- 实现简单
- 枚举本身是线程安全的,不需要额外的同步处理
- 枚举类型天生支持序列化,无需实现
Serializable接口,也不会出现反序列化创建新实例的问题 - 枚举类型由 JVM 保证,反射无法创建新的实例
java
public enum Singleton {
// 定义一个枚举实例
INSTANCE;
// 构造函数
Singleton() { …… }
// 获取枚举实例
public static Singleton getInstance() {
return INSTANCE;
}
public void someMethod() { // 实现具体功能 }
}java
// 使用
Singleton instance = Singleton.INSTANCE;
instance.someMethod();💛 销毁对象
内存泄漏通常不会表现为明显的故障,所以可能会在一个系统中存在很多年
要及时清除过期的对象引用 :
- 我们应该做到尽量缩小变量的作用域,让变量随作用域的结束而结束
- 如果这个变量的作用域很大,我们要避免维护这个变量(对象的引用),如果这个对象我们不再需要了,我们要将指向这个对象的引用置为 null。
如果一直引用着,垃圾回收器将不会对其进行回收
用 WeakHashMap 代替 HashMap 来清理无用的弱引用 key :
- 在
HashMap中,键值对的键对象会一直持有强引用,这就意味着即使外部代码不再需要这个对象,它依然会存在于缓存中 - 在
WeakHashMap中,如果缓存中某个对象的 key 不再被其他地方强引用,那么这个对象就会自动被垃圾回收器回收,只有当缓存外部还有对该对象的强引用时,它才会继续存在于缓存中
java
// 创建一个WeakHashMap缓存
Map<String, String> cache = new WeakHashMap<>();
cache.put(new String("exampleKey"), "exampleValue");
// key通过弱引用存储在cache中,但是此时gc还没有执行
System.out.println("Before GC: " + cache);
// 有一个强引用指向 key
String externalKeyReference = key;
// 清除强引用
key = null;
// 手动调用垃圾回收器
System.gc();
// 检查缓存,发现不存在键值对
System.out.println("After GC: " + cache);LinkedHashMap 替代 HashMap ,在添加新键值对时,检查是否要删除最旧的键值对
java
// LRU - Least Recentyly Used 最近最少使用
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
// 构造函数,指定缓存的最大容量
public LRUCache(int maxSize) {
super(maxSize, 0.75f, true); // 设置访问顺序为 true
this.maxSize = maxSize;
}
// 覆盖 removeEldestEntry 方法
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当缓存大小超过最大容量时,删除最旧的元素
return size() > maxSize;
}
}java
// 创建一个容量为 3 的 LRU 缓存
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "One");
cache.put(2, "Two");
cache.put(3, "Three");
System.out.println("初始集合 : " + cache);
// 添加新的元素,删除 1
cache.put(4, "Four");
System.out.println("新增4 : " + cache);
// 访问一个元素,使其变为最近使用的元素
cache.get(2);
// 添加新的元素,删除3
cache.put(5, "Five");
System.out.println("新增5 : " + cache);监听器和其他回调中可能引发内存泄漏 :
如果你实现了一个 API,客户端注册了回调,但是没有显式地注销,除非你采取一些措施,否则这些回调对象就会不断累积起来。确保回调及时被垃圾收集处理的一个方法是只存储对它们的弱引用(weak reference),例如,将其仅作为 WeakHashMap 中的键来存储。
❤️ 方法
设计方法时,应该让这个方法尽可能的通用(传递任何参数进来都能合理地处理)
我们应该在方法的开头就校验方法的入参是否合理,尽早地暴露出不合法的参数
❤️ api 设计
设计类中的状态依赖方法(类似迭代器中的 next 方法) 和状态测试方法(类似迭代器中的 hasNext 方法)的准则 :
- 如果该对象在并发下,可能导致状态依赖方法和状态测试方法不一致(hasNext 返回 true,但是 next 时又没有),那此时应该合并这两个操作为 nextIfAvailable 方法(返回 Optional)
- 如果这两个方法都需要做相同的操作(比如检查 ……),那也应该合并这两个操作
- 其他情况下,都应该依赖于状态测试方法,而不是依赖于返回值 Optional
📚 按钮权限
- 按钮状态
java
/**
* 按钮状态
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class ButtonStatus {
private String buttonNameEn; // 按钮名称
private String buttonNameCn; // 按钮名称
private Boolean showFlag; // 是否显示
private Boolean enableFlag; // 是否启用
}- 页面按钮通用接口
java
/**
* 页面按钮通用接口
*/
public interface PageButton<T> {
/**
* 获取按钮中文名称
*/
String getButtonNameCn();
/**
* 获取显示条件断言
*/
Predicate<T> getShowPredicate();
/**
* 获取启用条件断言
*/
Predicate<T> getEnablePredicate();
/**
* 获取按钮英文名称(枚举名称)
*/
String name();
/**
* 返回按钮状态
*/
default ButtonStatus getStatus(T context) {
return ButtonStatus.builder()
.buttonNameEn(this.name())
.buttonNameCn(getButtonNameCn())
.showFlag(getShowPredicate().test(context))
.enableFlag(getEnablePredicate().test(context))
.build();
}
}- 按钮通用策略类
java
/**
* 按钮通用策略类
*/
@Component
public class PageButtonStrategy {
/**
* 获取按钮状态
*
* @param pageButtonEnumClass 页面按钮枚举类(必须实现 PageButton 接口)
* @param context 页面上下文
* @param <E> 枚举类型
* @param <T> 上下文类型
* @return 按钮状态列表
*/
public <E extends Enum<E> & PageButton<T>, T> List<ButtonStatus> getButtonStatus(Class<E> pageButtonEnumClass, T context) {
return Arrays.stream(pageButtonEnumClass.getEnumConstants())
.map(buttonEnum -> buttonEnum.getStatus(context))
.collect(Collectors.toList());
}
}- 采购申请列表页面按钮
java
/**
* 采购申请列表页面按钮
*/
@Getter
@AllArgsConstructor
public enum PurchaseApplicationListButtonEnum implements PageButton<List<AdminUserDO>> {
LEADER_APPROVAL("领导审核",
adminUserDOList -> true,
adminUserDOList -> {
String username = SecurityUtils.getUsername();
return SecurityUtils.getRoles().contains(RoleEnum.ADMIN.name())
|| adminUserDOList.stream()
.map(AdminUserDO::getParentName)
.anyMatch(username::equals);
}
),
EXPORT("导出",
adminUserDOList -> true,
adminUserDOList -> {
List<String> roles = SecurityUtils.getRoles();
return roles.contains(RoleEnum.ADMIN.name())
|| roles.contains(RoleEnum.PROCURE.name())
|| roles.contains(RoleEnum.PROCURE_MANAGER.name());
}
);
private final String buttonNameCn;
private final Predicate<List<AdminUserDO>> showPredicate;
private final Predicate<List<AdminUserDO>> enablePredicate;
}- 使用
java
private @Resource PageButtonStrategy pageButtonStrategy;
List<ButtonStatus> list = pageButtonStrategy.getButtonStatus(PurchaseApplicationListButtonEnum.class, adminUserDOList);