泛型概念
泛型其实就是将类型参数化(即把要操作的数据类型当做一个参数),这样我们就可以在编译时就类,方法,接口的数据类型规定好了,减少了过多的向下转型,程序更加安全了。更通俗的讲,那垃圾分类举例子,一个垃圾桶,厂家生产的时候并没有给它规定,它里面只能放什么垃圾,这时的垃圾桶可以放任何垃圾,当我们确定了他只能放有害垃圾时,我们就给了他一个标签,他就只能放有害垃圾,其他的不行。
以我们的代码为例,就是如果我们需要在一个list中存放String类型的元素,就可以使用泛型,如果放入非String类型的数据,就会编译报错如下所以。
public class test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
//list.add(1); 编译出错,不能放入int类型
}
}
泛型的标记:E、T、K、V、N
- E:在集合中使用,表示集合中存放的元素
- T:表示Java类,所有的基本类和自定义的类
- K:表示键(Key)
- V:表示值(Value)
- N:表示数值类型(Number)
-
泛型上限、下限的限定
泛型上限限定:< ? extends T>
用通配符?和关键字extends来规定泛型的上限,我们知道extends是继承的关键字,这里表示通配符?代表的类型是T的子类或者子接口
泛型下限限定:< ? super T>
用通配符?和关键字super来规定泛型的下限,在方法中super代表着父类,这里表示通配符?代表的类型是T的父类或者父接口
泛型类(在类上定义类型)
public class Person<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
我们看下测试方法,person1对象实例的set方法泛型设置的String类型,放入18时编译报错。person2对象实例的set方法设置为Integer类型放入18则不报错。
泛型接口(在接口上定义类型)
public interface demo<T> {//泛型接口
void test(T t);
}
public class demoImpl implements demo<String>{//清楚接口类型
@Override
public void test(String s) {
System.out.println(s);
}
}
public class demoImpl<T> implements demo<T>{//不清楚类型
@Override
public void test(T t) {
System.out.println(t);
}
}
泛型方法(在方法参数上定义类型)
public class test {
public static void main(String[] args) {
Person<Integer> person = new Person<>();
person.setT(1);
test(1,2,3,4);
test("a","b","c","d");
}
public static <T> void test(T ... tArray ){
for (T t : tArray) {
System.out.print(t);
}
}
}
泛型擦除
使用泛型时加上的类型参数,会被编译器在编译时去掉,这个过程就是泛型擦除。所以泛型主要用于编译阶段,编译后生成的class文件并没有泛型中的类型信息。
举个体现泛型擦除的例子,一目了然。
在编译前,list2是无法赋值给list的,因为此时的存在泛型的类型信息,list存放的String类型数据,list2存放的Integer类型,所以相互之间无法赋值。
public class test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(1);
System.out.println(list.getClass() == list2.getClass()); //输出:true
}
}
删除赋值,查看编译之后的对象,果然相等,说明编译之后泛型的类型信息被擦除了。