关于泛型那些事?
1.关于 泛型 和 子类继承 :
对于以前关于继承而言,其中子类和父类可以进行类型转换,这就是我们常说的类型转换(向上转型和向下转型)。其中我们知道将父类转换为子类需要进行强制类型转换的。而子类可以直接向上转型转换。
如下代码:
Object obj = null; String str = “123”; obj = str; 这是完全正确的!
那么关于泛型涉及到子类继承需要怎么做呢?如下代码:
List<Object> objs = null; List<String> strs = new ArrayList<String>(); objs = strs //编译错误
objs = strs;是错误的,他们是不能进行类型转换的,这是为什么呢?我们可以通过反证法可知,当我们假设其是可以进行类型转换的!我们可知 strs 向上转换为 objs,也就是说现在我们可以在该类型中存储任何类型的数据!这就又有我们为什么要设计泛型的初衷违背了!我们在设计泛型时就是为了使在集合中进行封装数据时可以只封装一种类型的数据。故而其是错误的。
这时我们可以知道 List<Object> 不是 List<String> 的父类,那什么样的类型是 List<String> 的父类呢?这时就引入了 List<?> 来代表父类!,它的元素类型可以匹配任何类型。显然,? 它被称为通配符。下面我们来看将任意元素加入到带通配符的类中不是类型安全的这又是为什么?
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误。
这是因为我们不知道c 中存放的元素类型(c中可以存放任何类型的数据如String、Math、Date类型等等),我们不能向其中添加对象。add 方法有类型参数E 作为集合的元素类型。我们传给add 的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。
所以使用通配符是较为灵活,但是在使用通配符时,却要为此付出使用通配符的灵活性的代价的!即使你使用有限制的通配符也是同理。如下面的例子:
List<? extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们只是知道这个未知的类型实际上是Shape 的一个子类。我们说 Shape 是这个通配符的上限(upper bound)。
如下代码:
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile-time error!
}
为什么编译错误?这是因为,我们不知道它是不是Rectangle 的父类;它可能是也可能不是一个父类,所以这里传递一个Rectangle 不安全。
2. 如何定义泛型类或接口?
其实对于泛型类或接口的定义来说,我们可以参考 List<E> 的源码中的定义,其中我们要注意的是泛型的位置!!!我们需要在类名的后面紧跟着进行定义泛型,其中我们称紧跟在类名后面的泛型我们称为类型参数!类型参数在整个类的声明中可用,几乎是所有可以使用其他普通类型的地方(如: 返回值、方法的参数、方法内部的局部变量、定义在类中的全局变量。)其中我们也可以定义多个类型参数!
如下面的代码:
public interface List<E> { //定义泛型。
void add(E x); //声明泛型后可用到 方法的参数
Iterator<E> iterator(); //声明泛型后可用到 返回值类型
}
public interface Iterator<E> { //定义泛型。
E next();//声明泛型后可用到 返回值类型
boolean hasNext();
}
3. 如何定义泛型方法?
那又如何定义泛型方法呢?那么接下来就说一下,其中我们可以在普通类或者泛型类中定义泛型方法!不仅仅是在泛型类中可以定义,这点我们是要明白的,如下定义的泛型方法:
Public <T> T name(T t,String str){
T tt = null;
return tt;
}
故而我们可以知道类型参数在整个方法的声明中可用,几乎是所有可以使用其他普通类型的地方(如: 返回值、方法的参数、方法内部的局部变量。)