泛型(Generic)总结(二)

三、类型参数<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、小结

模板模式的特点:

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

模板模式的技术实现:

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

推荐阅读更多精彩内容

  • Java泛型是Java5推出的一个强大的特性,那什么是泛型?下面是从维基百科上摘下来的定义: 泛型的定义主要有以下...
    yeonon阅读 466评论 0 0
  • 1.泛型的由来 一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义的类型,如果要编写可以适用于多...
    BigDreamMaker阅读 556评论 0 1
  • 泛型,一个孤独的守门者。 大家可能会有疑问,我为什么叫做泛型是一个守门者。这其实是我个人的看法而已,我的意思是说泛...
    程序员BUG阅读 361评论 0 0
  • 参考地址:《Java 泛型,你了解类型擦除吗?》 《Java中的逆变与协变》 《java 泛型中 T、E .....
    琦小虾阅读 3,023评论 0 11
  • 莫芝君阅读 147评论 0 0