EffectiveJava经验总结:第二章--创建和销毁对象

EffectiveJava经验总结:第二章–创建和销毁对象

一、使用静态工厂代替构造方法

1、静态工厂方法可以减少创建对象的次数
减少对象创建次数,对于一些长时间使用的对象 例如数据库连接对象,不需要频繁的创建和销毁 整个生命周期一个就够了,将这种类的创建放在静态代码块里面,只创建一个,mysql驱动类创建方式就是这样类似单例模式享元模式

  static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

2、静态工厂方法有名字,能明确的知道创建出这个对象是什么样子的,不需要根据参数类型去判断(这个比较好理解)
3、静态工厂方法可以返回类的子类型

public class Animal {
  public static Animal getInstance() {
  	// 这里可以返回父类Animal,也可以返回Tiger或者Horse,所以比构造器创建对象要更加灵活一些。
      return new Animal();
   }
}
class Tiger extends Animal {

}
class Horse extends Animal { 
}

4、静态工厂方法可以根据方法的参数值返回不同的对象
可以在创建对象的工厂方法里面做判断,根据传进来的方法参数不同创建不同的对象,但是不需要传参数

public class Animal {
	// 吃荤的类型
    private static final int TYPE_EAT_MEAT = 0;
    // 吃素的类型
    private static final int TYPE_EAT_GRASS = 1;
    /**
  	* 根据传入参数的不同返回不同类型的
  	*/
    public static Animal getInstance(int type) {
        // 这里可以返回父类Animal,也可以返回Tiger或者Lion,所以比构造器创建对象要更加灵活一些。
        if (TYPE_EAT_MEAT == type) {
            return new Tiger();
        }
        if (TYPE_EAT_GRASS == type) {
            return new Horse();
        }
        return new Animal();
    }
}

5、静态工厂方法返回的对象类:在编写该静态方法时候可以不存在
比如下面这个例子,在这里虽然调用了静态方法创建对象,但实际上没被调用之前,没有对象被创建了,

	// 根据不同的class字节码创建不同的对象,这儿为了方便并没有对传入的字节码类型做校验。
public static <T> T getAnimal(Class c) throws Exception {
   T o = (T) c.newInstance();
    if (Animal.class.isInstance(o)) {
          return o;
     }
       return null;
   }

6、提供静态方法的类必须提供公共的或者受保护的构造器
要创建一个对象或者这个对象的子类,都需要有构造方法
7、静态工场方法容易和其他的静态方法混淆 要注意

二、当构造方法参数过多时使用builder模式

1、重叠构造器的弊端(深有体会,容易把参数放错位置,每次使用比较麻烦)
2、使用javabean的方式创建一个无参构造,然后再对其中参数进行赋值:(这种情况有个弊端就是线程安全,在不同的时刻不同的线程来获取对象可能得到的参数值不同,需要额外花精力来解决线程的数据同步)
3、Builder方式:使用建造者设计方式:
与构造器相比,Builder模式灵活,可以利用单个builder构建多个对象。这里的是线程安全的,且可读性也像javaBean那样setter方法那样。
builder的内部实现实际上就是使用一个静态内部类,看set了什么参数,创建什么样子的对象。

public class Card {
    private int id;
    private String name;
    private boolean sex;
    
    Card(int id, String name, boolean sex) {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }
    public static Card.CardBuilder builder() {
        return new Card.CardBuilder();
    }
    public static class CardBuilder {
        private int id;
        private String name;
        private boolean sex;

        CardBuilder() {
        }
        public Card.CardBuilder id(int id) {
            this.id = id;
            return this;
        }
        public Card.CardBuilder name(String name) {
            this.name = name;
            return this;
        }
        public Card.CardBuilder sex(boolean sex) {
            this.sex = sex;
            return this;
        }
        public Card build() {
            return new Card(this.id, this.name, this.sex);
        }
        public String toString() {
            return "Card.CardBuilder(id=" + this.id + ", name=" + this.name + ", sex=" + this.sex + ")";
        }
    }
}

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

主要是保证一个类只提供一个Singleton类型的对象,一般通过final创建一个实例,静态工厂方法可以灵活的返回这个实例(灵活体现在是否想反回这个单例还是一个新的对象),但是需要注意:序列化的时候可能回序列化出一个非单例的,仅仅使用implement方法不可行,还需要提供一个readResolve() 方法,确认实例化的是上面的枚举单例。

// readResolve method to preserve singleton property
private Object readResolve() {
    // Return the one true Elvis and let the garbage collector
    // take care of the Elvis impersonator.
    return INSTANCE;
}

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

意思是说我们在编写工具类的时候,比如一个关于时间的 TimeUtiles,里面都是相应的静态方法,可以让这个类有一个private的构造方法,但是这样子就不能有子类了, 子类的所有构造函数都必须显示或者隐式地调用父类的构造函数,在这种情形下,子类就没有可访问的父类构造器可用了。

// Noninstantiable utility class
public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass(( {
        throw new AssertionError();
    }
    ... // Remainder omitted
}

五、固定资源首选依赖注入

如果我要实现一个类,这个类有依赖其他资源类,但是这个资源类的类型和个数不相同,这种情况下, 用一个方法去添加创建这些资源类,同样会有线程安全问题,这时候可以用依赖注入的方式解决

// Dependency injection provides flexibility and testability
public class SpellChecker {
    private final Lexicon dictionary;
    public SpellChecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    }
    public boolean isValid(String word) { ... }
    public List<String> suggestions(String typo) { ... }
}

如上图所示:在构造方法里面传递一个资源类,就可以改变资源类的类型,在扩展一下,如果传进来的是一个资源类的工厂方法,就可以线程安全的创建使用不同资源类的对象。

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

六、避免创建不必要的对象
在使用一些方法时 我们会无意之间创建很多不必要的类拖垮了性能,
比如使用String str = new (“abc”);这种会循环的创建很多字符串对象,使用 String a = "abc"使用的就一直是一个对象了,另外在使用正则表达式的match方法时:match方法会创建一个partten对象,partten需要就爱哪个正则表达式便以为有限状态机,为了提高性能将正则表达式显式编译为Pattern实例(不可变)作为类初始化的一部分,对其进行缓存,并在每次调用使用正则验证方法时重用相同的实例:

// Reusing expensive object for improved performance
public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compile(
        "^(?=.)M*(C[MD]|D?C{0,3})"
        + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}

这样子可以大大的加快性能。
另一个例子是自动装箱:如下图

// Hideously slow! Can you spot the object creation?
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

图中使用Long创建了一个对象 在后进行相加,这种情况导致每次做加法都创建了一个Long的新对象,指向宿命,使用long的话会好很多。
七、清除过期对象的引用
看下这个例子:

  public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

在一个栈的pop方法里,仅仅是将元素返回,会不停的创建对象,前一个引用一直存在,导致栈溢出等问题,只需要返回前一个,将当前置为null即可解决
Stack类容易被内存影响的原因是:stack类自己管理内存它相当于一个小的栈,自己维护了一下elements的数组,只有在这里面的部分是有用的,而其他的元素不清除,JVM是不清楚的。
只要是自己管理内存的类,都需要注意下,
另一个常见的内存泄露是缓存:使用WeakHashMap过期清除,使用弱引用(一旦发现弱引用对象无论当前空间是否充足都会回收)能淘汰掉一些时间较久的缓存对象。
八、避免使用终结方法和清空方法
语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。不要被System.gc和System.runFinalization这两个方法所诱惑,他们确实增加了终结方法和清理方法被执行的机会,但是他们不保证终结方法或清理方法一定会被执行。唯一声称保证这两个方法一定会被执行的方法是System.runFinalizersOnExit,以及它臭名昭著的孪生兄弟Runtime.runFinalizersOnExit。这两个方法都有致命的缺陷,已经被废弃了[ThreadStop]。
因为终结器也会抑制有效的垃圾收集。某些类(比如文件或线程)封装了需要终止的资源,对于这些类的对象,你应该用什么方法来替代终结方法和清理方法呢?让他们实现AutoCloseable接口即可,
这里经验教训是很明确的(The lesson is clear):在处理必须关闭的资源时,相比于try-finally,始终优先使用try-with-resources。 生成的代码更短更清晰,它生成的异常更有用。 try-with-resources语句可以在使用必须关闭的资源的同同时轻松编写正确的代码,使用try-finally几乎是不可能的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值