类型推导:发生在以下三个地方。1.Java编译器根据泛型方法传入的参数,推导出具体的类型。2.Java编译器,根据泛型构造器传入的类型来推导出实际要构造的实例类型。3.Java编译器根据表达式的目标类型(TargetType)来确定出实际的类型参数。

一、类型推导的例子:

1.通过泛型方法的实际参数类型进行推导。
比如:
       public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
              Set<E> result = new HashSet<E>(s1);
              result.addAll(s2);
               return result;
       }
在实际应用时,Java编译器根据传递的参数类型推导出实际的类型参数。
Java编译器,根据传入的参数guys,stooges推导出类型参数E的实际类型是String。
public class Union {
 
        // Generic method
        public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
              Set<E> result = new HashSet<E>(s1);
              result.addAll(s2);
               return result;
       }
 
        // Simple program to exercise generic method
        public static void main(String[] args) {
              Set<String> guys = new HashSet<String>(Arrays.asList( "Tom", "Dick" ,
                            "Harry"));
              Set<String> stooges = new HashSet<String>(Arrays.asList( "Larry", "Moe",
                            "Curly"));
              Set<String> aflCio = union(guys, stooges);
              System. out.println(aflCio);
       }
}
 
2.通过泛型构造器的参数来推导实际类型
比如
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
.....
}
使用:
通过<>中传递的参数,Java编译器会做类型推导,从而确定出创建的实例类型
 Map<String,List<String>> anagrams = new HashMap<String,List<String>>();
 
3.在Java SE 8中提供的机制,通过一个表达式的Target Type(目标类型),Java编译器可以推导出实际的类型参数是什么
 
比如
public class GenericStaticFactory {
        // Generic static factory method
        public static <K, V> HashMap<K, V> newHashMap() {
               return new HashMap<K, V>();
       }
 
        public static void main(String[] args) {
               // Parameterized type instance creation with static factory
              Map<String, List<String>> anagrams = newHashMap ();
            
       }
}
目标类型是<String,List<String>,Java编译器根据这个,就可以推导出,对应的<K,V>分别是String,List<String>。
在使用newHashMap时,没有外部的类型参数输入,不像泛型构造器(通过构造器旁边的<>提供类型参数),也不像有参数的泛型方法(通过输入的参数类型,Java编译器可以推导出实际的类型参数),Java编译器是根据表达式的目标类型(Target Type)来确定的。
 
又比如
public class Collections {
 
  public static final List EMPTY_LIST = new EmptyList<Object>();
  
  @SuppressWarnings("unchecked")
      public static final <T> List<T>  emptyList() {
          return (List<T>) EMPTY_LIST;
     }
  
  /**
   * @serial include
   */
  private static class EmptyList<E>
      extends AbstractList<E>
      implements RandomAccess, Serializable {
     ....
  }
}
使用:
public class TargetType {
 
 
 
  public void processStringList(List<String> stringList) {
    // process stringList
  }
 
  public static void main(String[] args) {
 
    TargetType one = new TargetType();
    one.processStringList(Collections.emptyList());
 
  }
 
 
}
 
二、泛型单例工厂---类型推导的重要应用
 
public class GenericSingletonFactory {
        // Generic singleton factory pattern
        private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
               public Object apply(Object arg) {
                      return arg;
              }
       };
 
        // IDENTITY_FUNCTION is stateless and its type parameter is
        // unbounded so it's safe to share one instance across all types.
        @SuppressWarnings("unchecked" )
        public static <T> UnaryFunction<T> identityFunction () {
               return (UnaryFunction<T>) IDENTITY_FUNCTION;
       }
 
        // Sample program to exercise generic singleton
        public static void main(String[] args) {
              String[] strings = { "jute", "hemp" , "nylon" };
              UnaryFunction<String> sameString = identityFunction();
               for (String s : strings)
                     System. out.println(sameString.apply(s));
 
              Number[] numbers = { 1, 2.0, 3L };
              UnaryFunction<Number> sameNumber = identityFunction();
               for (Number n : numbers)
                     System. out.println(sameNumber.apply(n));
       }
}
 
1.什么时候使用泛型单例工厂?
创建不可变,但又适合于不同类型的对象,这是使用场景。
不同的类型通过类型参数来区别,那么,就只需要创建一个固定的对象,但是该对象的类型由类型参数来指定。
2.为什么可以使用泛型实现?
泛型在编译的时候,会把一个具体的类型擦除为Object。这也意味着,当你使用泛型的时候,即使你使用的类型参数都是不一样的,但是,最终在编译的时候都会被擦除为Object,所以,可以用一个Object来表示所有的类型。
3.警告是什么意思?
因为,对于UnaryFunction<Object> 对于每个T来说,并非都是UnaryFunction<T>。也就是说,我们并没有确保类型是安全的,我们可能传入的是String,但类型转换的时候是Number。不过,在这里,我们并没有修改返回的UnaryFunction<T>,我们只是传递一个类型参数。
 
又一个例子:
假设有一个如下的接口: 
 
public interface NumberWrapper<T extends Number> {
     public double square(T num);
}
 
这个接口可以对Number的子类进行封装,它提供了一个方法可以输出值的平方。对于这样的Wrapper,我并不想对于每一个T都实例化一个相应的对象,那样有一些浪费,这时,就可以使用这个泛型单例工厂来生成支持不同T的单例。另外,因为NumberWrapper的泛型信息在运行时是会被擦除的,所以也没有必要对每一个T生成一个实例。泛型单例工厂的代码如下: 
 
public class GenericFactory {

private static NumberWrapper<Number> numWrapper = new NumberWrapper<Number>() {
          @Override
          public double square(Number num) {
               return num.doubleValue() * num.doubleValue();
          }
     };

@SuppressWarnings("unchecked")
     public static <T extends Number> NumberWrapper<T> getWrapperInstance() {
          return (NumberWrapper<T>) numWrapper;
     }

public static void main(String[] args) {
          NumberWrapper<Integer> integerWrapper = GenericFactory.getWrapperInstance();
          System.out.println(integerWrapper.square(2));
         
          NumberWrapper<Double> doubleWrapper = GenericFactory.getWrapperInstance();
          System.out.println(doubleWrapper.square(0.05));
     }

}

这里,有一个点要注意下,就是getWrapperInstance()方法的的类型转换,这里是一个NumberWrapper<Number>向NumberWrapper<T>的转换,这里,由于square()方法返回的仅仅是平方,并没有对numWrapper的状态进行修改,这里是不会导致类型错误的,所以可以放心的禁止这条警告了。 
 
 
 
三、递归类型限制(recursive type bound)
 
递归类型限制:通过某个包含该类型参数本身的表达式来限制类型参数是允许的。这就是递归类型限制。
 
 
public interface Comparable<T> {
 
  int compareTo(T o);
}
这个的含义:某个类型实现了Comparable接口。
某个类型只能与它自身的类型的元素进行比较,该类型要实现Comparable接口。String只能与String类型的元素比较。
 
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;
}
 
参考资料:
https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/Collections.java#Collections.0EMPTY_LIST
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java
http://zddava.iteye.com/blog/375950

Item27--优先考虑泛型方法的更多相关文章

  1. Effective Java 第三版——30. 优先使用泛型方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. 【Effective Java】7、优先考虑泛型方法

    package cn.xf.cp.ch02.item27; import java.util.HashSet; import java.util.Set; public class Union { / ...

  3. effective-java学习笔记---优先使用泛型方法30

    泛型类型比需要在客户端代码中强制转换的类型更安全,更易于使用. 当你设计新的类型时,确保它们可以在没有这种强制转换的情况下使用. 这通常意味着使类型泛型化. 如果你有任何现有的类型,应该是泛型的但实际 ...

  4. effective java 学习心得

    目的 记录一下最主要学习心得,不然凭我这种辣鸡记忆力分分钟就忘记白看了... 用静态工厂方法代替构造器的最主要好处 1.不必每次都创建新的对象 Boolean.valueOf Long.valueOf ...

  5. Effective java笔记(四),泛型

    泛型为集合提供了编译时类型检查. 23.不要在代码中使用原生态类型 声明中具有一个或多个类型参数的类或接口统称为泛型.List<E>是一个参数化类,表示元素类型为E的列表.为了提供兼容性, ...

  6. Effective Java 读书笔记之四 泛型

    泛型的本质是参数化类型.只对编译器有效. 一.请不要在新代码中使用原生态类型 1.泛型类和接口统称为泛型,有一个对应的原生态类型. 2.原生类型的存在是为了移植兼容性. 3.无限制通配类型和原生态类型 ...

  7. [Effective Java]第五章 泛型

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  8. java编程的78条黄金法则

    创建和销毁对象 1.考虑用静态工厂方法(返回类的实例的静态方法)代替构造器2.遇到多个构造器参数时要考虑用构造器3.用私有构造器或者枚举类型强化Singleton属性4.通过私有构造器强化不可实例化的 ...

  9. 《Effect Java》 归纳总结

    目录: 一.创建和销毁对象 (1 ~ 7) 二.对于所有对象都通用的方法 (8 ~ 12) 三.类和接口 (13 ~ 22) 四.泛型 (23 ~ 29) 五.枚举和注解 (30 ~ 37) 六.方法 ...

  10. 《Effective Java》读书笔记

    一.引言 1.几条基本规则:(清晰性和简洁性最为重要) 模块的用户永远也不应该被模块的行为所迷惑(那样就不清晰了),模块要尽可能小,但又不能太小 代码应该被重用,而不是被拷贝 模块之间的依赖性应该尽可 ...

随机推荐

  1. Alpha冲刺——第二天

    Alpha第二天 听说 031502543 周龙荣(队长) 031502615 李家鹏 031502632 伍晨薇 031502637 张柽 031502639 郑秦 1.前言 任务分配是VV.ZQ. ...

  2. lintcode-182-删除数字

    182-删除数字 给出一个字符串 A, 表示一个 n 位正整数, 删除其中 k 位数字, 使得剩余的数字仍然按照原来的顺序排列产生一个新的正整数. 找到删除 k 个数字之后的最小正整数. N < ...

  3. 【Linux】- 不可不知的小技巧

    1.Tab键:输入文件或目录名的前几个字符,然后按TAB键,如无相重的,完整的文件名立即自动在命令行出现:如有相重的,再按一下TAB键,系统会列出当前目录下所有以这几个字符开头的名字. 在命令行下,只 ...

  4. DVD与CD区别

    经常听朋友说什么DVD什么CD什么的,不知道到底有什么区别,专门百度找了下,找到以下资料 ======================================================= ...

  5. 微信支付java

    直接上代码: 1.支付配置PayCommonUtil import com.legendshop.payment.tenpay.util.MD5Util; import com.legendshop. ...

  6. html5 download all in one

    html5 download all in one HTML5 download & Fetch API & File API & Blob https://scarletsk ...

  7. OI入门

    学习顺序: 1.高精度计算: 高精度计算(一) 高精度计算练习1 高精度计算(二) 高精度计算练习2 2.递推 3.递归 递归算法 递归练习 4.搜索与回溯 搜索与回溯算法(一) 搜索与回溯练习(一) ...

  8. CF#508 1038E Maximum Matching

    ---题面--- 题解: 感觉还是比较妙的,复杂度看上去很高(其实也很高),但是因为n只有100,所以还是可以过的. 考虑一个很暴力的状态f[i][j][x][y]表示考虑取区间i ~ j的方格,左右 ...

  9. POJ3630:Phone List——题解

    http://poj.org/problem?id=3630 简单的trie树问题,先添加,然后每个跑一边看中途有没有被打上结束标记即可. #include<cstdio> #includ ...

  10. [Leetcode] Maximum depth of binary tree二叉树的最大深度

    Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...