《Effective Java》第一章——创建和销毁对象

内容摘引自《Effective Java》(第二版,机械工业出版社)

第一条:考虑用静态工厂方法代替构造器

这样做的好处

  • 静态工厂方法有名称,能够让人更易阅读,防止在开发过程中忘记应该使用哪个构造器的情况
  • 不必每次调用的时候都创建一个新的对象,当需要不可变类(实例内容不可改变的类)的时候可以使用预先构建好的实例,从而避免了重复创建对象,提升了性能
  • 它可以返回原返回类型的任何子类型对象,从而提高的灵活性
  • 在创建参数化类型实例的时候,它能使得代码变得简洁

静态工厂方法的缺点

  • 类如果不含有公有的或者受保护的构造器,就不能被子类化
  • 它与其他的静态方法实际上并没有任何区别,他可能不能像API文档那样直接明确标识出来

静态方法的惯用名称

  • valueOf——类型转换,返回与它参数相同的值
  • of——是valueOf的简洁替代
  • getInstance——返回的实例时通过方法的参数来描述的,但不能够说与参数有相同的值。对于Singleton(单例)来说,改方法没有参数,并返回唯一的实例
  • newInstance——类似于getInstance,但是newInstance能够确保返回的每一个实例都与其他的不同
  • getType——像getInstance一样,表示工厂方法返回的数据类型,主要早不同的类的时候使用。
  • newType——像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法返回的对象类型

服务提供者框架

定于:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。
这是一个静态工厂方法的实例
服务提供者框架包含四个部分

  • 服务接口(Service Interface),这是提供者实现的
  • 提供者注册API(Provider Registation API),这是系统用来注册实现的,让客户端访问他们的。
  • 服务访问API(Service Access API),是客户端用来获取服务的实例的。它一般允许但是不要求客户端指定某种选择提供者的条件,如果客户端没有这样的指定,那么一般会返回给一个默认的实例。它是一个“灵活的静态工厂”,它构成了服务提供者框架的基础
  • 服务提供者接口(Service Provider Interface),这是一个可选的,这些提供者负责创建其服务实现的实例。如果没有提供实例那么实现就按照类名进行注册,并通过反射方式进行实例化。

第二条:遇到多个构造器参数是要考虑用构建器

当一个实体有多个属性,而有的属性是非必需的那么在创建实例的时候可能会有多个构造函数可供选择,举例来说如下

package com.myclass;

public class OldPerson {
    private int age;
    private String name;
    private double weight;
    private String hobby;
    public OldPerson(int age, String name, double weight, String hobby) {
        // TODO Auto-generated constructor stub
        this.age = age;
        this.name = name;
        this.weight = weight;
        this.hobby = hobby;
    }
    
    //当weight和hobby不是必须的时候,我们在创建OldPerson就需要这样
    public OldPerson(int age, String name) {
        this.age = age;
        this.name = name;
    }
}

如果有很多个参数那么构造函数就可能会有很多,可能会在使用的时候不小心颠倒了两个参数或者别的小问题,所以这种重叠构造器模式是可以的,但是当有许多参数的时候,客户端代码会很难编写,并且难以阅读。

更好地方法

package com.myclass;

public class Person {
    private int age;
    private String name;
    private double weight;
    private String hobby;
    
    public static class Builder {
        //必填参数
        private String name;
        
        //选填参数,设置默认值
        private int age = 0;
        private double weight = 0.0;
        private String hobby = "写bug";
        
        //设置必填参数
        public Builder (String name) {
            this.name = name;
        }
        
        //设置非必填参数
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        public Builder weight(int weight) {
            this.weight = weight;
            return this;
        }
        public Builder hobby(String hobby) {
            this.hobby = hobby;
            return this;
        }
        
        public Person build() {
            return new Person(this);
        }
    }
    
    public Person(Builder builder) {
        // TODO Auto-generated constructor stub
        age = builder.age;
        name = builder.name;
        weight = builder.weight;
        hobby = builder.hobby;
    }
}

这样当我们使用的时候只需要这样

     Person person = new Person.Builder("Slience爱学习").weight(120).hobby("看书").build();

就可以了(虽然我用的比较多的是JavaBean模式)

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

(这一条没怎看懂)

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

我们知道当类中没有显式的构造器的时候,系统会自动生成一个缺省的构造器,当我们想要让一个类不能被实例的时候(比如说一些工具类),我们可以自己写一个构造函数,并把构造函数设置为私有的,这样就不会创建这个实例了,举例来说就是

package com.myclass;

public class MyClass {
    public static String say() {
        return "Hello World";
    }
    private MyClass() {
        // TODO Auto-generated constructor stub
    }
}

这样我们就创建不了MyClass实例,而只能使用它的say方法了。

第五条:避免创建不必要的对象

String str = "Hello World";
String str2 = new String("Hello World");

str相对于str2效率要高,因为在创建对象的时候"Hello World"已经是一个对象了,没有必要在用一层new String包裹起来再创建一个对象。
再举一个例子

        long start = System.currentTimeMillis();
        long sum = 0L;
        for(long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
        System.out.println("耗时:" + (System.currentTimeMillis() - start));

        long start = System.currentTimeMillis();
        Long sum = 0L;
        for(long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
        System.out.println("耗时:" + (System.currentTimeMillis() - start));

两者的执行耗时是不一样的,后者使用Long的耗时更大,因为要将i转成Long给sum加上,这意味着程序创造了不必要的很多个Long实例,造成了更多的耗时。

第六条:消除过期的对象引用

(原书是一个栈增长再减少的例子),减少掉的元素可能不会被垃圾回收,在极端情况下可能会造成内存泄漏。这是因为栈内部维护者这些对象的过期应用。
过期应用是指永远也不会被解除的引用。
解决这种问题的办法也很简单,那就是当对象引用过期的时候清空这些引用就可以了。

        String[] strs = {"星期一","星期二","星期三","星期四"};
        //如果要取出最后一个,应该这样
        String str = strs[strs.length-1];
        strs[strs.length-1] = null;
        System.out.println(str);

这样做的好处是当你不小心错误的解除引用,程序会报空指针异常而不是悄咪咪的运行下去。

第七条:避免使用终结方法

终结方法finalizer的线程优先级比其他应用程序的线程要低,如果要使用finalizer去结束一个一个比较有限的资源,比如说打开很多个文件的描述符(原文如此,可能是指explorer这样的东东吧),当你想要关掉它的时候再打开新的,因为优先级低所以可能先去打开新的然后旧的没有去关掉,关掉的速度比打开的速度低造成了资源的占用。比较常见的终结方法有InputStream、java.sql.Connention中的colse方法。
显示终结方法通常与try-finally结合起来使用,确保及时终止。
终结方法前使用try-finally显示终结的好处

  • 可以充当终结方法的“安全网”,这样就算终结方法没有按时关闭也可以有所动作以便做一些补救。
展开阅读全文

没有更多推荐了,返回首页