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;
}
@Override
public String toString() {
return info.toString();
}
}
泛型形参传入实参测试:
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>
限制住形参的上限。
4 泛型方法
4.1 泛型方法定义
泛型方法就是在声明方法时定义一个或多个类型形参;相比较于普通方法,泛型方法多了类型形参声明,多了<>
,若有多个类型形参,使用逗号","隔开,并且在方法修饰符和方法返回值类型中间要有类型形参。
4.2 语法
修饰符 <T, S> 返回值类型 方法名(形参列表){
//方法体
}
4.3 举例
泛型方法:
public class GenericMethodExample {
//定义泛型方法,传递T类型形参
public static <T> void copyArrayToCollection(T[] array, Collection<T> collection) {
for (T obj : array) {
collection.add(obj);
}
}
//定义泛型方法,通过类型通配符设置T类型上限
public static <T> void copyCollectionToCollection(Collection<? extends T> fromCollection,
Collection<T> toCollection) {
for (T obj : fromCollection) {
toCollection.add(obj);
}
}
}
入口类:
public class DemoApplication {
public static void main(String[] args) {
//泛型方法
Object[] objects = new Object[]{"10", 12, new Person("hello, i'm from jiangsu")};
Collection<Object> collections = new ArrayList<>();
GenericMethodExample.copyArrayToCollection(objects, collections);
collections.forEach(obj -> System.out.println(obj.toString()));
//泛型方法通过类型通配符设置上限类型形参
List<Integer> fromCollection = new ArrayList<>();
fromCollection.add(1);
fromCollection.add(2);
fromCollection.add(3);
Collections.reverse(fromCollection);
List<Number> toCollection = new ArrayList<>();
GenericMethodExample.copyCollectionToCollection(fromCollection, toCollection);
System.out.println("泛型方法上限测试:" + toCollection);
}
}
运行结果:
10
12
hello, i'm from jiangsu
泛型方法上限测试:[3, 2, 1]
从上述测试中,我们可以看到<T extends ?
这种通配符设置类型形参上限,何时使用通配符,何时使用泛型方法?我们主要通过依赖关系来判断。如果方法中的一个或多个参数之间的类型是有依赖关系的,或者方法的返回值和参数类型有依赖关系,我们就使用泛型方法;反之,没有任何依赖关系的,我们就使用通配符去灵活的使用泛型。
5 泛型构造器
5.1 泛型构造器定义
在构造器签名中声明类型形参就是泛型构造器。
5.2 语法
public <T> 类名(形参列表){
//方法体
}
5.3 示例
给GenericMethodExample类定义泛型构造方法
public class GenericMethodExample {
public <T> GenericMethodExample(T t) {
System.out.println(t);
}
}
主类入口:
public class DemoApplication {
public static void main(String[] args) {
new GenericMethodExample("泛型构造器01");
new GenericMethodExample(100);
new <String> GenericMethodExample("显示指定泛型构造器形参T为String类型");
new <Integer> GenericMethodExample(50);
}
}
运行结果:
泛型构造器01
100
显示指定泛型构造器形参T为String类型
50
参考《疯狂Java》