从泛型的使用情况看出你对语言的理解程度(2)

上篇我们提到:Java中的泛型是不可变的,可以通过实现了泛型的协变,实现泛型的逆变。从泛型的使用情况看出你对语言的理解程度(1)

今天我们来讲讲泛型单例工厂,在之前的推文中也有推送过单例模式的实现,但是不是用泛型实现的,这次我们先讲一个泛型单例的例子,然后再讲泛型单例工厂会更好理解一些。

首先定义一个泛型接口,里面包含一个apply方法:

public interface Money { 
  T apply(T args);
}

然后我们需要一种String类型的数据进行apply操作的单例对象。按惯性思维,我们会这么实现。


public class PaperMoney implements Money{
  private static PaperMoney paperInstance = new PaperMoney();
  private PaperMoney(){} 
  public PaperMoney getInstance() {
      return paperInstance;
  }
  @Override
  public String apply(String args) {      
      return args;
  }
}

在工程需求复杂的情况下,这种模式没有考虑到代码的扩展性,当未来需要对Integer对象数据进行apply操作呢?再写一个类吗?这明显是过于麻烦了。这时候就需要再结合工厂的设计模式了。

泛型单例工厂

在工厂中,会产生不同参数类型的Money对象,在结合static的特性,实现复用生成的Money对象。

public class MoneyImp {
    //Money是类似函数式接口实现
    private static Money IDENTITY_FUNCTION = arg -> String.valueOf(arg.hashCode());
    
    @SuppressWarnings("unchecked")
    public static  Money getMoneyInstance() {
        return (Money) IDENTITY_FUNCTION;
    }
    
    public static void main(String[] args) {
        String[] strings = { "one", "five", "ten" };
        Money paperMoney = getMoneyInstance();
        for (String s : strings) {
            System.out.println(paperMoney.apply(s));
        }
    
        Integer[] numbers = { 1, 2, 3 };
        Money coinMoney = getMoneyInstance();
        for (Integer n : numbers)
            System.out.println(coinMoney.apply(n));
        
        JSONObject[] jsonObjects = {JSON.parseObject("{hah:1}")};
        Money objMoney = getMoneyInstance();
        for (JSONObject n : jsonObjects)
            System.out.println(objMoney.apply(n));
    }
} 
  

上面的代码中Money接口其实是仿照Java8中的Function函数式接口定义的,或者说是仿照Function接口的子类接口:UnaryOperator。关于Function函数式接口可以看今天的另一篇推文介绍。简单的说,Function就是实现了这样的一个公式:y=f(x),而UnaryOperator实现的是:x=f(x)。

也就是说IDENTITY_FUNCTION  其实实现的是x=String.valueOf(f(x))的功能。在这里我们将传进来的x获取其hashCode,然后转换成字符串形式返回回去。同时由于IDENTITY_FUNCTION  是一个Money 。用Object接收返回的String(f(x))所以是合理的(父类引用指向子类对象)所以,还是可以把IDENTITY_FUNCTION  看作是x=f(x)。

arg -> String.valueOf(arg.hashCode());这个函数由于hashCode() 属于Object,所以任何类调用都不会报错。否则很容易报错,这里要多注意。

如果没有getMoneyInstance() 方法,而是直接把IDENTITY_FUNCTION  赋值给paperMoney  即:

Money paperMoney = IDENTITY_FUNCTION();

则会报编译错误:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  at com.ltjh.imports.job.MoneyImp.main(MoneyImp.java:27)

这个很明显,因为泛型是不可以协变的。所以我们需要一个静态工厂方法:getMoneyInstance()。

所有重点来了:

getMoneyInstance() 方法的作用,则是作为一个静态工厂方法用于获取我们编写的IDENTITY_FUNCTION  函数。代码中由于对IDENTITY_FUNCTION  进行了强制类型转换return (Money) IDENTITY_FUNCTION; 所以需要添加unchecked 转换警告,因为编译器并不知道Money 的每个都是Money ,但是因为IDENTITY_FUNCTION  表示的是x=f(x),所以我们可以确定返回的就是Money 。这是类型安全的。一旦我们这样做了,代码编译就不会出现错误或警告。

于是,我们就可通过getMoneyInstance() 获取到处理特定类型的Money 如:paperMoney 、coinMoney 和 objMoney 。也就实现了一个泛型的单例工厂。

上面代码的输出结果如下:


110182
3143346
114717
1
2
3
103054

《Effective Java》中对泛型单例工厂的描述如下:

有时,你需要创建一个对象,该对象是不可变的,但适用于许多不同类型。因为泛型是由擦除实现的,所以你可以为所有需要的类型参数化使用单个对象,但是你需要编写一个静态工厂方法,为每个请求的类型参数化重复分配对象。这种模式称为泛型单例工厂,可用于函数对象,如 Collections.reverseOrder,偶尔也用于集合,如 Collections.emptySet。

最后再提一点关于擦除的,由于Java泛型是由擦除实现的,所以,其实上面的代码在便后后类似于这样:


public class MoneyImp {
    //Money是类似函数式接口实现
    private static Money IDENTITY_FUNCTION = arg -> String.valueOf(arg.hashCode());
    
    @SuppressWarnings("unchecked")
    public static Money getMoneyInstance() {
        return IDENTITY_FUNCTION;
    }
    
    public static void main(String[] args) {
        String[] strings = { "one", "five", "ten" };
        Money paperMoney = getMoneyInstance();
        for (String s : strings) {
            System.out.println(paperMoney.apply(s));
        }
    
        Integer[] numbers = { 1, 2, 3 };
        Money coinMoney = getMoneyInstance();
        for (Integer n : numbers)
            System.out.println(coinMoney.apply(n));
        
        JSONObject[] jsonObjects = {JSON.parseObject("{hah:1}")};
        Money objMoney = getMoneyInstance();
        for (JSONObject n : jsonObjects)
            System.out.println(objMoney.apply(n));
    }
}

运行结果和前面的一样且不报错。那我们为什么还这么费劲用个泛型搞得云里雾里的呢?因为我们想要获取到专门处理某一种类型的Money:paperMoney 、coinMoney 、objMoney 。如果不适用泛型,用上面的代码,那么这三个paperMoney 、coinMoney 、objMoney 其实是没有限制的,没办法装门处理某一种特定类型啦。

好了,就到这里,不理解的朋友可以留言一起讨论哦~

关注公众号获取更多内容,有问题也可在公众号提问哦:

 

强哥叨逼叨

叨逼叨编程、互联网的见解和新鲜事

 

 

 

你可能感兴趣的:(java,泛型)