美文网首页
泛型(Generic)总结(二)

泛型(Generic)总结(二)

作者: 白花蛇草可乐 | 来源:发表于2019-08-17 16:49 被阅读0次

三、类型参数<T>与无界通配符<?>

3-1、区别使用

首先要区分开两种不同的场景:

  • 声明一个泛型类或泛型方法。这种情况下要使用类型参数“<T>”
  • 使用泛型类或泛型方法。这种情况下要使用无界通配符“<?>”。具体解释,就是对已经存在的泛型,我们不想给她一个具体的类型做为类型参数,我们可以给她一个不确定的类型作为参数,(前提是这个泛型必须已经定义)

<T>的作用是保证所有出现T的地方,指代的都是同一种类型,是一种约束。

<?>的作用是保证能在容器类里面放入各种不同类型的元素(顺便说,?的英文是wildcard:通配符)。通配符?是不能用来声明泛型的。以下声明直接报错:

public class GenericDemo<?>  // 报错

<?>的使用例:用<?>声明List容器的变量类型,然后用一个实例对象给它赋值。

List<?> list = new ArrayList<String>();

或者

public static void showObj(List<?> list) {
    for (Object object : list) {
        System.out.println(object);
    }
}
小结(伪代码)
List<T> aa = new ArrayList<T>();  // 正确
List<?> aa = new ArrayList<T>();  // 正确
List<?> aa = new ArrayList<?>();  // 报错,ArrayList里面必须是一种确定类型
List<Object> aa = new ArrayList<Object>();  // 正确
List<?> aa = new ArrayList<Object>();  // 正确

注意 <?> 和 <object>是完全不同的概念。List<?> 表示未知类型的列表,而 List<Object> 表示任意类型的列表——不管是哪一种,都算是已知的。

3-1-附、List<Object> 和 List 之间的区别?

List是原始类型。List<Object>使用了泛型。

  • 编译时编译器不会对原始类型进行类型安全检查,但是会检查泛型
  • 可以把任何带参数的类型传递给原始类型 List,但却不能把 List< String> 传递给接受 List< Object> 的方法,因为泛型的不可变性,会产生编译错误。(泛型的不可变性参看 6-4 )

3-2、List<?>(以及其他容器类)的坑

使用List<?>这个写法时,通配符会捕获具体的String类型,但编译器不叫它String,而是起个临时的代号,比如”CAP#1“。

以后再也不能往list里存任何元素,包括String。唯一能存的就是null。

在Java集合框架中,对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素。

因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,而null不涉及类型问题,所以是唯一的例外

List<?> list = new ArrayList<String>();

list.add("hello");    //ERROR
list.add(111);    //ERROR

//argument mismatch; String cannot be converted to CAP#1
//argument mismatch; int cannot be converted to CAP#1

另外如果拿List<?>做参数的话

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}
    // 只是把item先用get()方法读出来,然后再用set()方法存回去
    public void getSet(Box<?> box){box.set(box.get());}
}

会报错:

error: incompatible types: Object cannot be converted to CAP#1

原因同样是通配符box<?>.set()的参数类型被编译器捕获,命名为CAP#1,和box<?>.get()返回的Object对象无法匹配。

解决方法,是给getSet()方法写一个辅助函数:

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}
    //helper()函数辅助getSet()方法存取元素
    public void getSet(Box<?> box){helper(box);}
    public <V> void helper(Box<V> box){box.set(box.get());}
}

3-3、无界通配符的类型限定

在一些情况下,<?>需要配合extends或者super来使用。

在《Effective Java》中有如下的总结:

泛型-无界通配符-类型限定

更通俗地总结一下,就是:

  • <? super E> 用于灵活写入,主要目的是统一使用父类的容器,使得对象可以写入父类型的容器。或者用于比较,使得父类型的比较方法可以应用于子类对象。
  • <? extends E> 用于灵活读取,使得方法可以读取 E 或 E 的任意子类型的容器对象。
  • 如果既是生产又是消费,那使用通配符就没什么意义了,因为需要的是精确的参数类型

这样的话,回过头来看 3-2 一节中的代码,使用了<? extends Fruit>,其目的就是要将Apple放进父类Fruit的容器中。

四、泛型的使用限制以及部分变通方法

4-1、不能实例化类型变量

如T obj = new T ();  // 报错, 提示: Type parameter 'T' cannot be instantiated directly

解决方法:使用反射创建泛型实例

public class GenericObj<T> {
  private T obj;
  public GenericObj(Class<T> c){
      try {
        obj = c.newInstance(); // 利用反射创建实例
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
}

public class Test {
  public static void main (String args[]) {
    GenericObj<String> go = new GenericObj<> (String.class);
  }
}

Class类本身就是泛型, 而String.class是Class<T>的实例

4-2、不能实例化泛型数组

如T [] arr = new T[3];// 报错, 提示: Type parameter 'T' cannot be instantiated directly

不过只是单纯声明的话是允许的,例如 T [] arr

解决方法一:创建Object类型的数组,然后获取时转型为T类型:

public class GenericArray<T>  {
  private Object [] arr;
  public GenericArray(int n) {
    this.arr = new Object[n];
  }
  public void set (int i, T o) {
    this.arr[i] = o;
  }
  public T get (int i) {
    return (T)this.arr[i];
  }
}

解决方法二: 使用反射机制中的Array.newInstance方法创建泛型数组

public class GenericArray<T> {
  private T [] arr;
  public GenericArray(Class<T> type, int n) {
    arr = (T[])Array.newInstance(type, n); // 利用反射创建泛型类型的数组
  }
  public void set (int i, T o) {
    this.arr[i] = o;
  }
  public T get (int i) {
    return (T)this.arr[i];
  }
}

public class Test {
  public static void main (String args[]) {
  GenericArray<String> genericArr = new GenericArray<>(String.class, 5);
  genericArr.set(0, "abcdefg");
  System.out.println(genericArr.get(0)); 
  }
}

解决方法三:可以声明通配类型的数组, 然后做强制转换

Foo<Node> [] f =(Foo<Node> [])new Foo<?> [3]; 

4-3、不能在泛型类的静态上下文中使用类型变量

public class Foo<T> {
  private static T t;
  public static T get () { // 报错: 'Foo.this' can not be referenced from a static context
    return T;
  }
}

原因在于静态变量,不需要创建对象即可调用;而对于泛型类,对象不创建的话无法确定泛型是哪种类型。二者的要求矛盾所以编译禁止通过。

在非泛型类的静态泛型方法中是可以使用类型变量的。

4-4、不能使用基本类型的值作为类型变量的值

Foo<int> node = new Foo<int> (); // 报错

必须使用封装类型

4-5、泛型类不能继承exception

// ERROR:Generic class may not extend java.lang.throwable
public class GenericException<T> extends Exception {
}

扩展Throwable也是不合法的

public class Foo {
  public static  <T extends Throwable> void doWork () {
    try {
      // ERROR: Cannot catch type parameters
    }catch (T t) {
    }
  }
}

但是以下写法可以通过

public class Foo {
  public static  <T extends Throwable> void doWork  (T t) throws T {
    try {
      // ...
    }catch (Throwable realCause) {
      throw  t;
    }
  }
}

五、泛型与模板模式

5-1、模板模式简要回顾

所谓模板模式,就是把做事情的流程整理好,共通方法提炼出来,由个体实施的方法空出来,谁要实施谁自己填空。

比如,要实现生成、上传合同。先定义好模板(抽象类),就是每一步要干啥,自己能干的自己干,自己干不了的让各个具体需要合同的需求方自己做。

→ 其实这一步就算是在完成骨架类。

public abstract class Abstract合同{
    public void createProcess(){
        查询数据();
        读取合同模板();
        替换合同模板中的关键字();
        生成合同PDF文件();
        签章();
        信息入库();
    }
    
    protected abstract void 查询数据();
    private void 读取合同模板(){...};    
    protected abstract void 替换合同模板中的关键字();
    private void 生成合同PDF文件(){...};
    private void 签章(){...};
    private void 信息入库(){...};
    
}

要生成采购合同的时候,只需要完成跟自己业务实际相关的方法即可:

public class 采购合同 extends Abstract合同{

    @Override
    protected void 查询数据() {
        "select A,B,C from T_Contract"
        ...
    }

    @Override
    protected void 替换合同模板中的关键字() {
        strContractContent.replaceAll("#contract_no#","采购合同编号001-01");
        ...
    }

}

5-2、加入泛型

5-1 的例子中,只是简单地用抽象类、抽象方法对于公共行为和不明确的行为进行了隔离。
实际上情况往往要复杂的多。

比如查询出来的数据一定是一个List,要对List做遍历分别生成合同,而每种合同返回的数据类型一定是不一样的。也就是说除了不明确的行为以后,还出现了不明确的对象类型。

这就需要引入泛型来解决问题。比较完整的伪代码如下:

public abstract class Abstract合同<T>{
    public void createProcess(){
        List<T> contractDatas = 查询数据();
        for (T data : contractDatas) {
            读取合同模板();
            替换合同模板中的关键字(data);
            生成合同PDF文件();
            签章();
            信息入库();
        }
    }
    
    protected abstract List<T> 查询数据();
    private void 读取合同模板(){...};    
    protected abstract void 替换合同模板中的关键字(T t);
    private void 生成合同PDF文件(){...};
    private void 签章(){...};
    private void 信息入库(){...};

}

5-3、小结

模板模式的特点:

  • 抽象类:控制程序总体流程(骨架);实现共通(确定)的方法
  • 实现类:继承抽象类,实现具体差异部分的逻辑

模板模式的技术实现:

  • 用抽象方法隔离不明确的行为
  • 用泛型代表不明确的对象类型

相关文章

  • 泛型(Generic)总结(二)

    三、类型参数与无界通配符 3-1、区别使用 首先要区分开两种不同的场景: 声明一个泛型类或泛型方法。这...

  • 泛型(Generic)总结(一)

    一、泛型的引入 泛型这个概念的出现,根本目的是解决在“通用方法”中使用“通用类型”的问题。 泛型的本质是参数类型化...

  • 泛型(Generic)总结(三)

    六、泛型擦除 6-1、什么是泛型擦除 泛型这个概念,只存在于编译器中。而不存在于虚拟机(JVM)中。 意思是说,编...

  • Java 中的泛型 (Generic)

    泛型 (Generic) 泛型 (Generic),即“参数化类型”,就是允许在定义类、接口、方法时使用类型形参,...

  • Generic泛型

    泛型:JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制。 好处:1.将运行时期出现问题Cla...

  • 泛型Generic

    用二位坐标定义一个平面上的点a(x,y): 精度不够,提高精度需要重新定义高精度的类: 上面定义的两个类的代码非常...

  • 泛型(Generic)

    泛型:JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制。 好处:1.将运行时期出现问题Cla...

  • 泛型Generic

    a. 在java泛型中,如果创建一个运用泛型的数组,完整的写法为: 即无法直接创建,只能创建Object类型,然后...

  • Generic泛型

    网址 https://www.cnblogs.com/dotnet261010/p/9034594.html De...

  • 泛型generic

    先看一段代码 上边的join方法的参数,有3种情况,都可以运行成功。 但是,当我们提出了新的需求,比如当first...

网友评论

      本文标题:泛型(Generic)总结(二)

      本文链接:https://www.haomeiwen.com/subject/pjdksctx.html