Java—泛型详解和使用
1 泛型介绍
1.1 泛型的出现
泛型的出现还得从集合说起,没有泛型的时候,我们将一个对象存入集合时,集合不care这个对象的数据类型是什么,当我们再次从这个对象取出来的时候,对象的编译类型会变成Object类型,这时候我们就需要强制类型转换,但是这种行为每次都要去指定类型进行强制转换,并且有可能强制转换不了,比如我存的是Integer类型,误转换为String类型,那就可能会引发ClassCastException异常。
当Java 5引入了一个叫做“参数化类型”的概念后,我们可以在创建集合时去指定集合,这样我们再从集合取出数据时,这个数据就是我们当初指定的类型,不会出现需要强制类型转换的情况了。这种参数化类型就是泛型。
1.2 泛型在集合中的使用示例
在集合接口或者类后面增加尖括号<>
,里面注明数据类型,创建这个集合后,这个集合就只能存储这个特定的数据类型对象。
从Java 7开始,在使用带泛型的接口、类定义变量,我们调用构造器创建对象时构造器后面不需要带完整的泛型信息,只需要带一对尖括号<>
就行。如List<String> strList = new ArrayList<>();
只需要前面声明<String>
泛型,后面只需要<>
即可。
示例:
public class DemoApplication {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("t1");
stringList.add("t11");
Map<String, List<String>> map = new HashMap<>();
map.put("t", stringList);
stringList.forEach(str -> System.out.println(str.length()));
map.forEach((key, value) -> System.out.println(key + "---->" + value));
}
}
结果:
2
3
t---->[t1, t11]
2 泛型的进阶
泛型就是允许在定义接口、类和方法时使用类型形参,通过传入实际的类型参数(类型实参),该类型形参会在声明变量、创建对象、调用方法的时候动态指定。类型形参可以在整个接口、类内当作类型使用。这种方式可以动态生成无限个逻辑上的子类(实际物理中没有)。
无论泛型的类型形参传入的是什么类型实参,系统最后都是当作一个类来处理,内存中也只是占用一块内存空间。如List<Integer> intList = new ArrayList<>();
和List<String> strList = new ArrayList<>();
通过intList.getClass()
和strList.getClass()
得到的是相等的结果,这就表明两个类是相同的。
另: 在静态变量和静态方法声明中不可以使用类型形参。
2.1 泛型接口举例
public interface List<T> {
void create(T e);
void delete(T e);
void update(T e);
void find(T e);
}
其中,List为例,若形参T传入的是String类型的实参,则会产生一个List<String>
,逻辑上是List的子接口,只不过这个接口内的E类型都为String类型。
2.2 泛型类举例
定义泛型类Person
:
//创建带泛型声明的自定义类
public class Person<T>{
//使用T类型形参来定义实例变量
private T info;
public Person(){}
//使用T类型形参定义构造器,注意:构造器不用增加泛型声明,直接使用原类名即可;
public Person(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
泛型形参传入实参测试:
public class DemoApplication {
public static void main(String[] args) {
//实参传入String类型给T形参,则构造器构造对象时传参为String类型;
Person<String> personStr = new Person<>("小明");
System.out.println("姓名: " + personStr.getInfo());
//实参传入Integer类型给T形参,则构造器构造对象时传参为Integer类型;
Person<Integer> personInt = new Person<>(28);
System.out.println("年龄: " + personInt.getInfo());
}
}
结果:
姓名: 小明
年龄: 28
从上面的实验中,我们可以看出:通过泛型类Person<T>
传入实参后,可以生成诸如Person<String>
,Person<Integer>
,Person<Float>
等多个逻辑子类。
2.3 泛型类无参子类
创建ChinesePerson类
继承无参泛型类。
public class ChinesePerson extends Person {
@Override
public String getInfo() {
return super.getInfo().toString();
}
}
测试:
public class DemoApplication {
public static void main(String[] args) {
Person<String> chinesePerson = new ChinesePerson();
chinesePerson.setInfo("中国人");
System.out.println("Chinese person: " + chinesePerson.getInfo());
}
}
结果:
Chinese person: 中国人
2.5 泛型类带参子类
创建JiangsuPerson类
继承带参泛型类。
public class JiangsuPerson extends Person<String> {
//重写父类方法,返回值类型需保持一致
public String getInfo() {
return "城市信息: " + super.getInfo();
}
}
测试:
public class DemoApplication {
public static void main(String[] args) {
Person<String> jiangsuPerson = new JiangsuPerson();
jiangsuPerson.setInfo("南京");
System.out.println(jiangsuPerson.getInfo());
}
}
结果:
城市信息: 南京
3 泛型的类型通配符
3.1 类型通配符<?>
通配符?
在类型形参中使用后,可以表示各种泛型的父类,如List<?>
,即List的类型未知,可以传入任何类型的实参。
这种List<?>
不能直接添加元素,因为无法确定集合内的类型是什么,所以无法添加对象。(除了null,因为null是所有引用类型的实例)
List<?>
通过get()方法返回的一定是Object,所以可以将其赋值给Object类型变量。
3.2 类型通配符上限<? extends xx>
通过<? extends xx>
表示所有xx泛型的父类,如List<? extends Person>
表示所有Person泛型List的父类。?
代表的是一个未知类型,但这个未知类型又受到extends
限制,之鞥呢是Person的自身及子类。
3.3 类型形参的上限
类型形参的上限主要是限制实参只能是形参类型本身或子类。
示例:
public class Person<T extends Number> {
T info;
public static void main(String[] args) {
Person<Integer> person = new Person<>();
Person<Float> person2 = new Person<>();
}
}
若上述代码中增加Person<String> person3 = new Person<>();
,则编译报错,因为String类型不是Number或子类;通过<T extends xx>
限制住形参的上限。