1.考虑使用静态工厂方法替代构造方法

优势:

  • 静态工厂方法相比构造器有名称
  • 使用静态工厂方法, 不比在每次调用的时候都创建一个新对象
  • 使用静态工厂方法, 可以返回原返回类型的任何子类型的对象
  • 使用静态工厂方法, 在创建参数化类型示例的时候可以使代码更加简洁

缺点:

  • 类如果不含有共有的或是受保护的构造器, 就不能被子类化
  • 它们与其他静态方法实际上没有任何区别

静态工厂方法的命名:
valueOd, of, getInstance, newInstance, getType, newType

补充: 静态工厂方法返回的对象所属的类, 在编写包含该静态工厂的方法的类时可以不必存在, 这种灵活的静态工厂方法构成了服务提供者框架的基础

服务提供者框架(Service Provider Framwork)

  • 服务接口, 提供者要实现的接口
  • 提供者注册API, 系统用来注册实现, 让客户端访问的
  • 服务访问API, 是客户端用来获取服务的实例的
  • 服务提供者接口(可选), 提供者负责创建其服务实现的实例 ```java public interface Provider {

    Service newService(); }


public interface Service {

}

public class Services {

  1. private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
  2. public static final String DEFAULT_PROVIDER_NAME = "<def>";
  3. private Services() {
  4. }
  5. public static void registerDefaultProvider(Provider p) {
  6. registerProvider(DEFAULT_PROVIDER_NAME, p);
  7. }
  8. private static void registerProvider(String name, Provider p) {
  9. providers.put(name, p);
  10. }
  11. public static Service newInstance() {
  12. return newInstance(DEFAULT_PROVIDER_NAME);
  13. }
  14. public static Service newInstance(String name) {
  15. Provider p = providers.get(name);
  16. if (p == null) {
  17. throw new IllegalArgumentException("No provider registered with name: " + name);
  18. }
  19. return p.newService();
  20. }

}

  1. <a name="f8XMd"></a>
  2. ## 2.遇到多个构造器参数是要考虑用构建器
  3. 简言之, 方便拓展大量的可选参数.<br />构造器示例:
  4. ```java
  5. /**
  6. * @author gavin
  7. * @date 2020-07-02
  8. */
  9. public class DemoBuilder {
  10. private int certainArg1;
  11. private int certainArg2;
  12. private int unCertainArg3;
  13. private int unCertainArg4;
  14. private int unCertainArg5;
  15. private DemoBuilder(Builder builder) {
  16. certainArg1 = builder.certainArg1;
  17. certainArg2 = builder.certainArg2;
  18. unCertainArg3 = builder.unCertainArg3;
  19. unCertainArg4 = builder.unCertainArg4;
  20. unCertainArg5 = builder.unCertainArg5;
  21. }
  22. public static class Builder {
  23. // 必须的参数
  24. private final int certainArg1;
  25. private final int certainArg2;
  26. // 非必须的参数, 初始化给默认值
  27. private int unCertainArg3 = 0;
  28. private int unCertainArg4 = 0;
  29. private int unCertainArg5 = 0;
  30. public Builder(int certainArg1, int certainArg2) {
  31. this.certainArg1 = certainArg1;
  32. this.certainArg2 = certainArg2;
  33. }
  34. public Builder unCertainArg3(int val) {
  35. unCertainArg3 = val;
  36. return this;
  37. }
  38. public Builder unCertainArg4(int val) {
  39. unCertainArg4 = val;
  40. return this;
  41. }
  42. public Builder unCertainArg5(int val) {
  43. unCertainArg5 = val;
  44. return this;
  45. }
  46. public DemoBuilder build() {
  47. return new DemoBuilder(this);
  48. }
  49. }
  50. }

使用:

  1. @Test
  2. public void testDemo(){
  3. DemoBuilder demo = new DemoBuilder.Builder(1, 2).unCertainArg3(3).unCertainArg4(4).unCertainArg5(5).build();
  4. }

3.用私有构造器或者枚举类型强化Singleton属性

4.通过私有构造器强化不可实例化的能力

5.避免创建不必要的对象

  • 对于同时提供了静态工厂方法和构造器的不可变类, 通常可以使用静态工厂方法而不是构造器, 以避免创建不必要的对象, 例如, Boolean.valueOf(String), 优先于Boolean(String).
  • 重用那些已知的不会被修改的可变对象, 可以在类初始化的时候只创建实例一次, 比如正则的Pattern
  • 优先使用基本类型而不是装箱基本类型, 要当心无意识的自动装箱

示例: 下面代码构造了大约2的31次方个多余的Long实例, 每次往Long sum中增加long时构造了一个实例

  1. /**
  2. * 计算所有int数的值
  3. * @param args
  4. */
  5. public static void main(String[] args) {
  6. Long sum = 0L;
  7. for (long i = 0; i < Integer.MAX_VALUE; i++) {
  8. sum += 1;
  9. }
  10. System.out.println(sum);
  11. }

6.消除过期的对象引用

  • 只要类是自己管理内存, 程序员就应该警惕内存泄漏问题, 一旦元素被释放掉, 则该元素中包含的任何对象引用都应该被清空

示例代码: 下面的栈实现, 若栈先增长, 后收缩; 栈还维护者>size的元素的过期引用, 实际上这些元素不会被访问到, 这导致了程序的内存泄露

  1. /**
  2. * 栈实现
  3. *
  4. * @author gavin
  5. * @version 2020/7/5
  6. */
  7. public class Stack {
  8. private Object[] elements;
  9. private int size = 0;
  10. private static final int DEFAULT_INITIAL_CAPACITY = 16;
  11. public Stack() {
  12. elements = new Object[DEFAULT_INITIAL_CAPACITY];
  13. }
  14. private void ensureCapacity() {
  15. if (elements.length == size) {
  16. elements = Arrays.copyOf(elements, 2 * size + 1);
  17. }
  18. }
  19. public void push(Object e) {
  20. ensureCapacity();
  21. elements[size++] = e;
  22. }
  23. public Object pop() {
  24. if (size == 0) {
  25. throw new EmptyStackException();
  26. }
  27. return elements[--size];
  28. }
  29. }

修复方案: 一旦对象引用已经过期, 只需清空这些引用即可; 且清空了过期引用, 若以后他们被错误的解除引用, 程序会立即抛出NPE, 而不是悄悄的错误运行下去.

  1. public Object pop() {
  2. if (size == 0) {
  3. throw new EmptyStackException();
  4. }
  5. Object element = elements[--size];
  6. // 出栈则清除引用
  7. elements[size] = null;
  8. return element;
  9. }
  • 内存泄漏的另一个常见来源是缓存, 缓存注意维护过期时间
  • 内存泄漏的第三个常见来源是监听器以及其他回调.

如果你实现了一个API, 客户端在这个API种注册回调, 却没有显示的取消注册, 那么除非你采取某些动作, 否则它们就会积聚, 确保回调立即被当做垃圾回收的最佳方法时只保存它们的弱引用, 例如, 只将它们保存成WeakHashMap中的键.

7.避免使用终结方法

终结方法是不可预测的, 也是很危险的, 一般情况下是不必要的.
Java语言规范不仅不保证终结方法会被及时的执行, 而且根本就不保证它们会被执行, 不应该依赖终结方法来更新重要的持久状态.