Skip to content

❤️ 处理对象

💛 如何创建对象

💙 避免创建不必要对象

  • 不可变对象总是可以复用
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);