第27条 优先考虑泛型方法

考虑如下的方法,它的作用是返回两个集合的联合:

public static Set union(Set s1, Set s2) {  
    Set result = new HashSet(s1);  
    result.addAll(s2);  
    return result;  
}  

这个方法可以编译,但是有三条警告:

Multiple markers at this line
    - Type safety: The constructor HashSet(Collection) belongs to the raw type HashSet. References to 
     generic type HashSet<E> should be parameterized
    - Set is a raw type. References to generic type Set<E> should be parameterized
    - HashSet is a raw type. References to generic type HashSet<E> should be parameterized

有道翻译:
-类型安全:构造函数HashSet(集合)属于原始类型的HashSet。引用
泛型的HashSet应该是参数化的
-Set是一种原始类型。对泛型类型设置的引用应该是参数化的
-HashSet是一种原始类型。对泛型类型的HashSet的引用应该是参数化的

为了修正这些警告要将方法声明修改为声明一个类型参数,表示这三个集合的元素类型(两个参数及一个返回值),并在方法中使用类型参数。声明类型参数的类型参数列表,处在方法的修饰符及其返回类型之间,修改后的代码如下:

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {  
    Set<E> result = new HashSet<E>(s1);  
    result.addAll(s2);  
    return result;  
}  

上面的union方法即为一般的泛型方法,但是它有一个限制,要求三个集合的类型(两个输入参数及一个返回值)必须全部相同。利用有限制的通配符类型可以使这个方法变得更加灵活。第28条会详细介绍

泛型方法的一个显著特征是,无需明确指定类型参数的值,不像调用泛型构造器的时候是必须要指定类型参数的,因为泛型方法的类型会存在一个类型推导的过程(编译器通过检查方法参数的类型来计算类型的值),对于上述程序而言,编译器发现union的两个参数都是set<String>类型,因此知道类型参数E必须是String。在调用泛型构造器的时候,要明确传递类型参数的值可能有点麻烦。类型参数出现在了变量的声明的左右两边,显得冗余:

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

对于这情况 ,可以遵照第一条,提供一个静态工厂方法来简化:

public static <K, V> HashMap<K, V> newHashMap() {  
    return new HashMap<K, V>();  
}  
Map<String, List<String>> map = newHashMap() ;

利用上面的静态工厂方法,我们可以把变量声明右侧的参数类型省略掉,当参数类型多而复杂时尤其有效。

有时会需要创建不可变但是又适合于许多不同类型的对象,由于泛型是通过擦除来实现的,可以给所有的必要的类型参数使用同一个单个对象,但是需要一个静态的工厂方法来给每个必要的类型参数分发对象。这种模式叫做“泛型单例工厂”,这种模式最常用于函数对象。假设有一个接口,描述了一个方法,该方法接受和返回某个类型T的值:

package test;

public interface UnaryFunction<T> {
    T apply(T t);
}

现在假设要提供一个恒等函数,如果在每次需要的时候都重新创建一个这样会很浪费,因为它是无状态的。如果泛型被具体化,那么每个类型都必须持有相应类型的桓等函数,但是在运行时擦除类型信息后,它们并没有什么区别,所以在这种情况下,只需要一个泛型单例就够了。请看以下示例:

package test;

public class diGui {
    private static UnaryFunction<Object> INDENTITY_FUNCTION = new UnaryFunction<Object>() {
        public Object apply(Object arg) {
            return arg;
        }
    };

    // 标识函数是无状态的(它在执行时不会对外界的变量、对象、数组等值进行修改。),它的类型参数是无界,因此在所有类型中共享一个实例是安全的。
    @SuppressWarnings("unchecked")
    public static <T> UnaryFunction<T> indentityFunction() {
        return (UnaryFunction<T>) INDENTITY_FUNCTION;
    }

    public static void main(String[] args) {
        String[] StringSet = { "a", "b", "c" };
        UnaryFunction<String> sameString = indentityFunction();
        for (String s : StringSet) {
            System.out.println(sameString.apply(s));
        }

        Number[] numbers = { 1, 2.0, 3L };
        UnaryFunction<Number> sameNumber = indentityFunction();
        for (Number n : numbers) {
            System.out.println(sameNumber.apply(n));
        }
    }
}

运行结果

通过某个包含该类型参数本身的表达式来限制类型参数是允许的,这就是递归类型限制。最普遍的用途与Comparable接口有关,它定义类型的自然顺序。许多方法都带有一个实现Comparable接口的元素列表,为了对列表进行排序,并在其中进行搜索,计算出它的最小值或者最大值等等。要完成这其中的任何一项工作要求列表中的每个元素都能够与列表中的其他元素相比较,下面是如何表达这种约束条件的一个示例:

public static <T extends Comparable<T>> T max(List<T> list) {  
    ...  
} 

限制类型 <T extends Comparable<T>> ,可以读作“针对可以与自身进行比较的每个类型T”。下面的方法就带有上述声明。它根据元素的自然顺序计算列表的最大值,编译时没有出现错误或者警告:

    public static  <T extends Comparable<T>>  T max(List<T> list) {
        Iterator<T> i = list.iterator();
        T result = i.next();
        while(i.hasNext()) {
            T t = i.next();
            if(t.compareTo(result) > 0) {
                result = t;
            }
        }
        return result;
    }

总结,泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来的更加安全,也更加容易。就像类型一样,你应该确保新的方法可以不用转换就能使用,这通常意味着要将它们泛型化。并且就像类型一样,还应该将现有的方法泛型化,使新用户使用起来更加轻松,且不会破坏现有的客户端。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • object 变量可指向任何类的实例,这让你能够创建可对任何数据类型进程处理的类。然而,这种方法存在几个严重的问题...
    CarlDonitz阅读 908评论 0 5
  • 本章将会介绍 泛型所解决的问题泛型函数类型参数命名类型参数泛型类型扩展一个泛型类型类型约束关联类型泛型 Where...
    寒桥阅读 629评论 0 2
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,571评论 18 399
  • 提问 需求 有一个这样的数组结构 其实我没明白为啥他截图是评论回复的,但代码里写的question?方-_-#需求...
    懒先森阅读 513评论 0 0
  • 1. 不知是哪一位名人说过,即使是真理,我们也应当大胆地质疑。因为质疑会让我们主动思考,进而改变突破促使社会进步。...
    笨学徒阅读 1,657评论 7 5