理解泛型
泛型是将类型参数化,类定义时一些成员变量和一些方法形式参数的类型成为变量,在使用时再传入一个具体类型,进一步抽象了变量的类型。好像是给方法或类写了一个模板,根据模板赋予不同的类型参数,就会生成不同的类。将那些类似方法或类都集中在一起编写,也就是将相同的部分都封装起来,不同的部分参数化。某种程度,减少编写只因类型不同,逻辑大部分相同的多个类的代码量,使这部分代码耦合度降低,提高了代码的复用率。在Java中的泛型使用时指定类型但不需要强制类型转换,不必担心,类型是安全的,编译器会检查。
使用泛型
泛型类
定义泛型类:
public class Stack<E> implements Iterable {
private Node first;
private class Node {
private E element;
private Node next;
public Node(E e) {
this.element = e;
next = null;
}
public Node(Node x) {
element = x.element;
if (x.next != null) {
next = new Node(x.next);
}
}
}
// 其他方法省略
}
创建泛型类的对象:
Stack stack = new Stack<String>();
成员内部类是可以使用外部类的类型参数,静态内部类却不能。
泛型接口
定义泛型接口:
interface Callable<V> {
V call() throws Exception;
}
实现泛型接口,指定类型:
class StringCaller implements Callable<String> {
@Override
public String call() {
return "String";
}
}
实现泛型接口,不指定类型:
class Caller<V> implements Callable<V> {
private V val;
public V call() {
return val;
}
public void setVal(V val) {
this.val = val;
}
}
泛型方法
一个普通类的方法可以使用泛型。形式大致:
权限修饰符 <T> T 方法名(T a, T b) {}
非静态方法:
class GenericMethod {
public <T> void f(T t) {
System.out.println(t.getClass().getName());
}
}
静态方法:
例如:
import java.lang.reflect.*;
import java.util.regex.*;
class StaticGenericMethod<T> {
public static <T> void f(T t) {
Pattern p = Pattern.compile("\\w+\\.");
for(Method m : t.getClass().getMethods())
System.out.println(
p.matcher(
m.toString()).replaceAll(""));
}
}
由于static方法无法访问泛型类的类型参数,static的泛型方法必须在返回值类型前加上<类型参数>
这个类型参数的声明。非静态方法如果是在泛型类内部,其实已经隐含了声明,所以不用特别写出来,但如果是普通类中,一定要写。
泛型限制类型
在使用泛型时, 可以指定泛型的限定区域 ,
- 例如: 必须是某某类的子类或 某某接口的实现类,格式:
<T extends 类或接口1 & 接口2>
泛型的通配符
public class Demo {
public static void main(String[] args) {
ArrayList<Fruit> fruits = new ArrayList<Apple>();
}
}
class Fruit {}
class Apple extends Fruit {}
上面的main方法,编译器是无法通过运行的。不同于方法的多态,是可以传入变量参数时,实际参数时可以为形参的子类。但泛型的使用不允许这么做,这里可以理解,类型ArrayList<Fruit>和ArrayList<Apple>是完全不同的类型,之间更没父子关系。Java为了更好的使用泛型,提出了泛型的通配符。
类型通配符是使用?代替方法具体的类型实参。
无界通配符<?>表示某种特定类型,创建对象时传入的类型参数可以是任意类型,对象一旦传入创建好了,类型就确定,它方法使用类型参数也就跟着确定了。与<Object>是不同的意思,类型参数确定为Object,只不过使用这个类型参数的方法是可以传入任何类型的变量(因为Object是所有类共同的基类,Java是走单继承体系的);
超类通配符<? super T>或<? super 具体类>,由T或具体类的基类来界定类型参数,它规定类型的下界;
子类通配符<? extends T>或<? extends 具体类>,由T或具体类的子类来界定类型参数,规定了类型的上界。通配符用在方法的泛型类参数或泛型类变量声明上。
就如:
List<?> fruits = new ArrayList<Fruit>();
// 假设定义好类Fruit及其子类Apple,下面的代码在某个类内部
void write(List<? super Fruit> fruit, Fruit newFruit) {
fruit.add(newFruit);
}
Fruit read(List<? extends Fruit> fruit, int i) {
return fruit.get(i);
}
泛型擦除
在编译之后程序会采取去泛型化的措施。
也就是说Java中的泛型,只在编译阶段有效。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加 类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段