天天看点

Java随笔—泛型程序设计引言1.为什么使用泛型?2.定义简单泛型类3.泛型方法4.类型变量的限定5.约束与局限性6.泛型类型的继承规则7.通配符

目录

  • 引言
  • 1.为什么使用泛型?
  • 2.定义简单泛型类
  • 3.泛型方法
  • 4.类型变量的限定
  • 5.约束与局限性
  • 6.泛型类型的继承规则
  • 7.通配符

引言

泛型类似于一个模板,实质就是参数化类型,通过一个类型参数T,用来指示元素的类型,可以被用在类、接口和方法中,对应的称为泛型类、泛型接口、泛型方法。

1.为什么使用泛型?

Java还没加入泛型类前,泛型程序设计实际是通过继承实现的。AllayList只维护一个Object引用的数组

public class ArrayList  {
  private Object [] arr;
  public ObjArray(int n) {
    this.arr = new Object[n];
  }
  public void add (int i, Object o) {
    this.arr[i] = o;
  }
  public Object get (int i) {
    return this.arr[i];
  }
}
           

这样设计有两个问题:

当获取一个值时,必须进行强制类型转换;

可以向数组列表中加入任何类型的对象,但是编译和运行不报错;

为此引入泛型机制,在调用get时,不需要进行强制类型转化,编译器知道返回值类型,并且可以避免插入错误类型,类型参数T使得程序具有更好的安全性和可读性。

2.定义简单泛型类

一个泛型类可以有一个或者多个类型变量的类

public class Pair<T>{...}
public class Pair<T,U>{...}
           

类型变量使用大写,Java中,一般使用变量E代表集合的元素类型,K表示关键字,V表示关键字值的类型,T表示任意类型。

用具体的类型替代类型变量就可以实例化泛型类型:Pair&ltString>(由于泛型的尖括号内容不被显示,用&lt 替换<)

3.泛型方法

类型变量放在修饰符后,返回类型前面;

class ArrayAlg{
    public static <T> T getMiddle(T a){
        return a[a.length / 2];
    }
}
           

泛型方法可以定义在普通类或者泛型类中;

在实例化一个泛型类对象时,构造函数可以省去泛型类型;

4.类型变量的限定

有时,类或方法需要对变量类型进行约束,例如要求它必须是某个超类的子类,或者必须实现了某个接口,那么被定义的泛型类作为接收方,也需要对传入的类型变量T的值做一些限定和约束。

public class Pair<T entends SuperClass>{...}
public static <T entends Comparable> T min(T[] a){...}
           

T和绑定类型可以是类或接口,关键字extends相比较implemets更接近子类的概念;

限定类型用&分隔,逗号分隔类型变量;

限定中至多一个类,如果用该类作为限定,必须位于限定列表的第一个,可以拥有多个接口超类型;

5.约束与局限性

先来讲一下什么叫擦除?无论何时定义一个泛型类型,都自动提供一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定的变量用object)

1.不能使用基本类型实例化类型参数

不能使类型参数替代基本类型,应使用基本类型对应的包装器类型,当包装器类型不能接受替换时,可以用独立的类或者方法处理。

Pair<int> node = new Pair<int> (); // 非法
Pair<Integer> node = new Pair<Integer> ();//合法
           

2.运行时类型查询只适用于原始类型

所有类型查询只产生原始类型,无论何时使用instanceof或者泛型类型的强制类型转换表达式都会报编译器错误。

if(a instanceof Pair<T>) //error
Pair<String> p=(Pair<String>) a;//warning--can only test that a is a Pair
           

当类型变量不同时,假如使用getClass方法比较,可能会得到true,两次调用getClass都将返回Pair.class

3.不能创建参数化类型的数组

不能实例化参数化类型的数组,如下代码会报错

可以把它转换为Object[]类型的数组,数组会记得它的元素类型,Object[] obj=table;

需要注意的是,只是不允许创建这些数组,但声明类型为Pair&ltString>[]的变量是合法的,不可以用new Pair&ltString>[10]初始化变量。

另外,如果需要收集参数化类型对象,可以使用AllayList:ArrayList<Pair&ltString>>

4.不能实例化类型变量

不能使用new T(…) , new T[…] , T.class这样的表达式中的类型变量,可以通过反射Class.newInstance方法来构造泛型对象,如下代码来支配class对象:

public static <T> Pair<T> makePair(Class<T> cl){
	try{
		return new Pair<> (cl.newInstance(),cl.newInstance())}
	catch(Exception ex){return null;}
}
           

可以使用如下调用:String.class实际上是Class&ltString>的唯一一个实例,方法能推断出Pair的类型

5.泛型类的静态上下文中类型变量无效

public class Pair<T> {
  private static T t;
  public static T get () { 
  /* 报错
  提示: 'Pair.this' can not be referenced from a static context
  由于类型擦除后,只有Pair类,包含一个t域,不能在静态域或方法中引用类型变量
  */
    return T;
  }
}
           

6.不能抛出或者捕获泛型类的实例

不能抛出或者捕获泛型类对象,包括泛型类拓展Throwable也不合法,如下:

public class Pair {
  public static  <T extends Throwable> void doWork () {
    try {
   		 do work
    }catch (T t) {// 报错 提示: Cannot catch type parameters
    }
  }
}
           

java异常处理中使用泛型可以消除对已检查异常的检查,如下是合法的:

public class Pair {
  public static  <T extends Throwable> void doWork  (T t) throws T {
    try {
     	do work
    }catch (Throwable realCause) {
     	throw  t;
    }
  }
}
           

6.泛型类型的继承规则

必须注意泛型与Java数组之间的区别,还是用雇员及经理来说,我们可以将一个Manager[]数组赋值给一个类型为Employee[]的变量,但是绝对不可以将Pair&ltManager>转换为Pair&ltEmployee>,另外泛型类可以拓展或者实现其他的泛型类,例如ArrayList<T类可以实现List&ltT>, 用一张图来形象的说明以上情况:

Java随笔—泛型程序设计引言1.为什么使用泛型?2.定义简单泛型类3.泛型方法4.类型变量的限定5.约束与局限性6.泛型类型的继承规则7.通配符

7.通配符

固定的泛型类型可能使用中并不是很愉快,假设我们要编写一个打印雇员的方法,但是向之前所说,不能将Pair&ltManager> 传递进来,因此Java的设计者提出了“通配符类型”来解决它。

1 .有限定通配符

public static void printBuddis(Pair<? entends Employee> p){
	Employee first = p.getFirst();
	...
}
           

有限定通配符存在一个问题,由于编译器只知道某个Employee的子类型,但是并不知道的具体什么类型,因此不能调用setFirst()方法

2.超类型限定通配符

带有超类型限定的通配符可以为方法提供参数,但是不能使用返回值;也就是说带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取;

?super Manager该通配符限制为Manager的所有超类型

3.无限定通配符

Pair<?>和Pair的本质区别在:可以为任意Object对象调用原始的Pair类的setObject调用,有方法如下:

? getFirst()
void setFirst(?)//可以调用setFirst(null)
           

可以测试pair是否包括一个null引用

public static boolean hasNull(Pair<?> p){
	return p.getFirst()==null|| p.getSecond()==null;
}
           

4.通配符获取

编写一个交换一个pair元素的方法 :

public static void swap(Pair<?>)

通配符不是类型变量,因此不能再代码中使用 ?为一种类型。代码 ?t=p.getFrist(); 是非法的。我们可以编写一个辅助方法swapHelper,如下:

public static <T> void swapHelper(Pair<T> p){
	T t=p.getFrist();
	p.setFirst(p.getSecond());
	p.setSecond(t);
}
           

现在我们可以在swap方法中调用swapHelper了

参考书籍:《Java核心卷Ⅰ》