《Effective Java 2》白话版 (一)

1,101 阅读9分钟

我很希望10年前就拥有这本书。可能有人认为我不需要任何Java方面的书籍,但是我需要这本书。

——Java 之父 James Gosling

通常我们会使用类的构造器 对其进行实例化 而今天我们讨论的就是另一个不可忽视的方法—-静态工厂方法

那什么是“静态工厂方法” ?

1.静态工厂方法有名称,能够正确的表达正在返回的对象 (比如说你定义一个方法能让用户清楚的知道这个方法返回了一个什么对象)当一个类需要多个相同签名的构造器时,用静态工厂方法替代它们

举个例子:

构造器BigInteger(int,int,Random) 返回的BigInteger可能为素数,
但是如果用BigInteger.probablePrime()的静态工厂方法可能更容易理解

一个类只能有一个带有指定签名的构造器,编程人员通常避开这一限制是提供两个构造器,而它们的参数列表只是在参数列表顺序上有所不同。实际上这不好。对于这样的API,client永远也记不住该调用哪个构造器,结果通常会调用错误的构造器。并且读到这些构造器的代码时,如果没有参考文档,也不知所云。所以由于静态工厂方法有名称,所以他们不受上述限制。当一个类需要多个带有相同签名的构造器(只是参数列表顺序不同)时,就用静态工厂方法代替构造器并且慎重选择名称已突出他们之间的区别

上一句话的理解:

class Person {
    private String name;
    private int age;
    private int score;
    public Person() {
    }
 
    /**
     *
     * @param name
     * @param age
     */
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    /**
     * not able
     * @param name
     * @param score
     */
    public Person(String name, int score) {
        this.name = name;
        this.age = age;
    }
 
    /**
     * instead
     * 即使是这样解决了
     * 对于外部的调用还是存在问题
     * 所以我们使用静态工厂方法
     * @param score
     * @param name
     */
    public Person(int score, String name) {
        this.name = name;
        this.age = age;
    }
 
    public static Person getInstanceAge(String name,int age) {
        return new Person(name, age);
    }
 
    public static Person getInstanceScore(String name, int score) {
        return new Person(score,name);
    }
        
 
}

上述代码 使用一个getInstanceAge or getInstanceScore 方法简单明了的创建了我们想要的实例 而不用关注复杂的内部实现

2.当一个类需要多个相同签名的构造器时,用静态工厂方法替代它们

静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。这种类被称作实例受控的类(instance-controlled)。编写实例受控的类有几个原因。实例受控使得类可以确保它是一个Singleton或者是不可实例化的

3.静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象

这种技术有一个很重要的应用就是隐藏真正的实现类,而只提供公有的基类,以这种方式隐藏实现类会使得API变得非常简洁。书中指明,这项技术适用于基于接口的框架,因为在这种框架中,接口为静态工厂方法提供了自然返回类型。接口是不能持有静态方法的,因此按惯例接口Type的静态工厂方法被放在一个名为Types的不可实例化的类中。通过这种方法,用户可以知道被返回的对象是由相关的接口定义的,所以不需要阅读有关的类文档。使用静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象而不是通过实现它们的类,这是一种良好的习惯。

静态工厂方法所返回的对象的类是可变的,这取决了传入给其的参数,其实就是一种约定的控制符。只要是已声明的实现类类型,都是可以被实例化返回的。这就大大的增加了灵活性。

// Service provider framework sketch  
  
// Service interface  
public interface Service {  
    ...// Service-specific methods go here  
}  
  
// Service provider interface  
public interface Provider {  
    Service newService();  
}  
  
//Noninstantiable class for service registration and access  
public class Services {  
    private Services() {} // Prevents instantiation  
      
    // Maps service names to services  
    private static final Map<String, Provider> providers =  
            new ConcurrentHashMap<String, Provider>();  
    public static final String DEFAULT_PROVIDER_NAME = "<def>";  
    // Provider registration API  
    public static void registerDefaultProvider(Provider p) {  
        registerProvider(DEFAULT_PROVIDER_NAME, p);  
    }  
    public static void registerProvider(String name, Provider p) {  
        providers.put(name, p);  
    }  
      
    // Service access API  
    public static Service newInstance() {  
        return newInstance(DEFAULT_PROVIDER_NAME);  
    }  
    public static Service newInstance(String name) {  
        Provider p = providers.get(name);  
        if (p == null)  
            throw new IllegalArgumentException(  
                    "No provider registered with name : " + name);  
        return p.newService();  
    }  
}

4.静态工厂方法与构造器不同的第四大优势在于,在创建参数化的类型实例的时候,它们使得代码变得更加简洁

这条适合1.7以前

在创建参数化的类型实例的时候,它们使得代码变得更加简洁。在调用参数化类的构造器的时候,即使类型参数很明显也必须提供 例如以下代码

Map<String, List<String>> m =  
    new HashMap<String, List<String>>();

但是在1.7之后 我们直接可以

Map<String, List<String>> m =  
    new HashMap<>();

但是在1.7之前随着类型参数的增多和名子的增长很快让人无法接受,但是使用静态工厂方法,编译器就可以替你找到类型参数,这叫类型推导

public static <K, V> HashMap<K, V> newInstance() {
		return new HashMap<K, V>();
	}

然后我们就可以 用下面这句话进行声明

Map<String, List<String>> m = HashMap.newinstance();


缺点


说完了使用静态工厂方法的优点 我们再说点缺点

1、静态工厂方法的主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化。这主是要由于JAVA的类继承机制的权限限制的。但是这样也会鼓励我们尽量使用类的复合而不是继承

2、静态工厂方法的第二个缺点在于,它们与其他的静态方法实际上没有任何区别

这导致在API文档中,它们没有像构造器那样在API文档中明确的标注出来,因此对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。可以采用惯用名的方式暂时弥补这一劣势。

valueOf :不太严格的讲,这个方法返回的实例与它的参数具有相同的值。实际这只是做了一个类型转换。

of:valuefOf的一种简洁的替代,在EnumSet中使用并流行起来。

getInstance:返回的实例是通过方法的参数来描述的,但是不能说与参数具有同样的值。参于Singleton来说,该方法就没有参数,并总是返回唯一的实现。

newInstance:像getInstance一样,但newInstance能够确保返回的每个实例都与所有其他的实例不同。

getType:像getInstance一样,但是在工厂方法处于不同的类型的时候使用。Type表示工厂方法所返回的对象类型。

newType:像getInstance一样,但是在工厂方法处于不同的类型的时候使用。Type表示工厂方法所返回的对象类型。


Android相关应用


1.强制不可实例化

如果你不希望一个对象通过关键字 new 来创建,那么强制让它的构造方法私有。这尤其对一些只包含静态方法的工具类有用。

class MovieUtils {
    private MovieUtils() {}
 
    static String getTitle(Movie movie) {
        [...]
    }
}

2静态工厂方法

不要使用 new 关键字和构造方法创建对象,而应当使用静态工厂方法(和私有构造方法)。这些工厂方法具有名字,不需要每次返回一个新的对象实例,它们可以依据需求返回不同的子类型对象。

class Movie {
    [...]
    public static Movie create(String title) {
        return new Movie(title);
    }
}

 

 

最后如果您发现了 任何问题 为了给后面看到这篇文章的人一个指引 请留言 我将改正