泛型的概念
- 集合中是可以存放不同类型的对象,这是因为将所有对象都看做Object类型放入,因此从集合中取出元素时也是Object类型,需要强制类型转换,
而强制类型转换可能会引发类型转换异常 - 泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错
- 泛型只在编译时期有效,在运行时期不区分是什么类型
泛型就是在集合类型的右侧加上<数据类型>,就只能存放选择的数据类型了
编程使用
泛型不需要强制转换了
//1.准备一个支持泛型机制的List集合。明确要求集合中的要求是String类型
List<String> lt1 = new LinkedList<String>();
//2.向集合中添加元素并打印
lt1.add("one");
System.out.println("lt1="+lt1);//[one]
//3.获取集合中的元素并打印
String s = lt1.get(0);
System.out.println("获取到的元素是:"+s);//one
注意菱形特性
后面尖括号中的数据类型可以省略
//菱形特性
List<Double> lt3 = new LinkedList<>();
笔试考点
相互不能复制
//笔试考点
//试图讲lt1的数值赋值给lt3,也就是覆盖lt3中原来的数值
lt3 = lt1;//编译报错 集合中支持的类型不同
泛型机制的底层原理
泛型的本质就是参数化类型,让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,从而使得集合中所有的E被实际参数替换
自定义泛型接口和泛型类
- 泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:<E, T, … >
- 泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:<E, T, … >等
自定义泛型类Person
其中T相当于形式参数,负责占位,具体数值由实参决定
T看作是名字为T的数据类型即可
public class Person<T> {//这里的T是形式参数,负责在这里占个位置
private String name;
private int age;
private T gender;//这里表名gender是T类型的,取决于我们一会使用Person或new Person对象时来指定
public Person() {
}
public Person(String name, int age, T gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public T getGender() {
return gender;
}
public void setGender(T gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}
泛型类的使用
集合中不支持基本数据类型
public class PersonTest {
public static void main(String[] args) {
//声明Peron类型的引用指向Person类型对象
//不指定类型,按Object类型对待
Person p1 = new Person("zhangfei",30,"male");
//打印对象特征
System.out.println(p1);
System.out.println("--------------------");
//在创建对象的同时指定数据类型,用于给T进行初始化
Person<String> p2 = new Person<>();
p2.setGender("female");
System.out.println(p2); //null 0 female
System.out.println("--------------------");
//使用boolean作为性别类型
Person<Boolean> p3 = new Person<>();
p3.setGender(true);
System.out.println(p3); //null 0 true
}
}
实例化泛型类时应该指定具体的数据类型,并且是引用数据类型而不是基本数据类型
泛型类被继承时子类如何处理
- 擦除:不保留泛型并且没有指定类型,此时Person类中的T默认为Object类型
public class SubPerson extends Person{//不保留泛型并且没有指定类型,此时Person类中的T默认为Object类型
}
- 不保留泛型但指定了泛型的类型,此时Person类中的T被指定为String类型
public class SubPerson extends Person<String>{//不保留泛型但指定了泛型的类型,此时Person类中的T被指定为String类型
}
- 保留父类的泛型
public class SubPerson<T> extends Person<T>{//保留父类的泛型 可以在构造对象时指定T的类型
}
- 留父类的泛型 同时在子类中增加新的泛型
public class SubPerson<T, T1> extends Person<T>{//保留父类的泛型 同时在子类中增加新的泛型
}
使用:
子类除了指定或保留父类的泛型,还可以增加自己的泛型
泛型方法
泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化
在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法
- 定义泛型方法
//自定义方法实现将参数指定数组中的所有元素打印出来
public static <T1> void printArray(T1[] arr){//<T1>就是为了告诉java这是个泛型方法
for(T1 tt:arr){
System.out.println("tt="+tt);
}
}
- 调用泛型方法
System.out.println("--------------------");
//调用泛型方法进行测试
Integer[] arr = new Integer[]{11,22,33,44,55};
Person.printArray(arr);
泛型在继承上的注意事项
String是Object的子类,但是List并不是List的子类
就如有Dog类继承自Animal类,但若把Dog放进尖括号里面,那么List<Dog>
并不是List<Animal>
的子类
即:如果B是A的一个子类或子接口,而G是具有泛型声明的类或接口,则G<B>
并不是G<A>
的子类型
通配符的使用
- 有时候我们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了
- 如:之前传入的类型要求为Integer类型,但是后来业务需要Integer的父类Number类也可以传入。
- 泛型中有三种通配符形式:
- <?> 无限制通配符:表示我们可以传入任意类型的参数。
- <? extends E> 表示类型的上界是E,只能是E或者是E的子类。
- <? super E> 表示类型的下界是E,只能是E或者是E的父类
创建Dog类继承Animals类
public class GenericTest {
public static void main(String[] args) {
List<Animal> lt1 = new LinkedList<>();
List<Dog> lt2 = new LinkedList<>();
//使用通配符作为泛型类型的公共父类
List<?> lt3 = new LinkedList<>();
lt3 = lt1;//可以发生List<Animal> 到List<?> 的类型转换
lt3 = lt1;//可以发生List<Dog> 到List<?> 的类型转换
}
}
说明其实List<?>是Dog和Animal的公共父类
但实际上lt3中不能存放Animal和Dog类型对象,说明?这个通配符不支持添加操作
但是
Object o = lt3.get(0);
就OK,全当Object类型了,可以直接取
Set集合
java.util.Set集合是Collection集合的子集合,与List集合平级