简单学学泛型
1.前言
泛型是我们学习和工作中,接触到的常见语法糖之一。其主要作用是用于规定一些类型或者作为通配符,让我们更方便地进行类型管理与使用。泛型广泛应用于集合、工具类等。我们有必要对泛型的常用使用方式进行学习与了解。
学习目标
学习泛型相关概念并能逐步熟练使用泛型,通过使用泛型,提高代码重用性、安全性,高效率开发。
阅读本文,我们将对泛型的主要使用方式进行学习,至于更深一步的学习,请参考以下参考资料:
参考资料
菜鸟教程-Java泛型:https://www.runoob.com/java/java-generics.html
CSDN-优秀参考博文:https://blog.csdn.net/weixin_45395059/article/details/126006369
2. 泛型
2.1 什么是泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是"参数化类型",也就是说所操作的数据类型被指定为一个参数。
泛型是Java中的一个重要概念,它允许我们编写可以处理各种类型数据的通用代码。泛型可以让我们在编写代码时不必关心数据类型,从而提高代码的可重用性和安全性。
泛型的一般格式:
// 其中,T代表的是引用类型
<T>
2.2 若不使用泛型
众所周知,集合是一种容器。我们在学习与使用集合的时候常常带上泛型来规定当前集合的数据类型。当我们不使用泛型的时候,集合是可以一次性装下不同类型的数据的。
我们来做个测试,首先我们定义几个用于测试的类:
创建用于测试的类
public class Animal {
String name;
Animal(){
}
Animal(String name){
this.name = "dog";
}
}
public class Cat extends Animal {
private String color;
public void voice(){
System.out.println("mao mao");
}
Cat(String name){
this.name = name;
}
Cat(){
}
}
public class Car {
private int price;
public void move(){
System.out.println("the car is move");
}
}
测试不用泛型
public static void main(String[] args) {
List miscellaneous = new ArrayList();
miscellaneous.add(new Animal());
miscellaneous.add(new Car());
miscellaneous.add(new Cat());
miscellaneous.forEach(System.out::println);
}
当不使用泛型时,显而易见,各种数据类型的数据都可以加入列表。当数据量大的时候,数据的管理将会变得异常麻烦。
2.3 如何使用泛型
泛型标记符
表记符 | 含义 |
---|---|
E | Element,表示集合的元素 |
T | Type,表示类型。实际使用中,其实使用A、B等都可以,一般约定俗称用Type |
K | Key 键 |
V | Value 值 |
N | Number 数值类型 |
? | 通配符,表示不确定的类型 |
实际使用中,由于使用方便与约定俗成的关系,我们最常用的泛型标记符还是 T
和 ?
2.3.1 泛型接口
语法
在接口名后添加泛型:
interface GenericsInterface<T> {
T method(T t);
// other methods ...
}
作用
如果一个接口使用了泛型,那么实现该接口的类可以就可以指定类型,举个例子:
interface GenericsInterface<T> {
/**
* 泛型接口演示
* @param t 泛型对象t
* @return 返回泛型对象
*/
T method(T t);
}
public class Cat extends Animal implements GenericsInterface<Cat> {
private String color;
public void voice(){
System.out.println("mao mao");
}
@Override
public Cat method(Cat cat) {
return null;
}
}
当我们实现泛型接口,而不指定泛型时,默认为Object:
public class Cat extends Animal implements GenericsInterface {
private String color;
public void voice(){
System.out.println("mao mao");
}
@Override
public Object method(Object o) {
return null;
}
}
2.3.2 泛型类
语法
在类名后添加泛型:
class GenericsClass<T> {
T method(T t);
// other methods ...
}
当我们实例化这个泛型类的对象时,就可以指定泛型了。
举例:
public class MyStack <T> {
LinkedList<T> values = new LinkedList<>();
public void push(T t){
values.addLast(t);
}
public T pull(){
return values.removeLast();
}
public T peek(){
return values.getLast();
}
public static void main(String[] args) {
MyStack<Car> carStack = new MyStack<>();
carStack.push(new Car());
carStack.push(new Car());
carStack.peek();
Car pull = carStack.pull();
MyStack<Cat> catMyStack = new MyStack<>();
catMyStack.push(new Cat());
catMyStack.push(new Cat());
catMyStack.push(new Cat());
Cat cat = catMyStack.pull();
}
}
2.3.3 泛型方法
语法
public <T> ReturnType methodName(Type typeName){
}
使用泛型方法的好处是可以在不同的数据类型上重用同一个方法,而不需要为每种数据类型编写重复的代码
举例
这里举一个工作中有概率用到的例子,当我们需要对一个列表进行手动分页时:
/**
* @param list 分页前的集合
* @param pageNum 页码
* @param pageSize 页数
* @param <T> 泛型标记符
* @return 分页后的集合
*/
public <T> List<T> pageList(List<T> list, int pageNum, int pageSize) {
//计算总页数
int page = list.size() % pageSize == 0 ? list.size() / pageSize : list.size() / pageSize + 1;
//兼容性分页参数错误
pageNum = pageNum <= 0 ? 1 : pageNum;
pageNum = pageNum >= page ? page : pageNum;
// 开始索引
int begin = 0;
// 结束索引
int end = 0;
if (pageNum != page) {
begin = (pageNum - 1) * pageSize;
end = begin + pageSize;
} else {
begin = (pageNum - 1) * pageSize;
end = list.size();
}
return list.subList(begin, end);
}
2.3.4 类型通配符
在上面的小节中,我们提到过通配符?
,相信小伙伴们肯定或多或少见过,特别是在底层源码中,用得很多。
?extends
很好理解,表示一个泛型或者其子类型,举个例子:
复用上文提到过的 Cat 类, 并让它继承 Aniaml
public class Cat extends Animal {
private String color;
public void voice(){
System.out.println("mao mao");
}
Cat(String name){
this.name = name;
}
Cat(){
}
}
public class Test {
public static void main(String[] args) {
// 定义 3 个 list
List<Cat> catList = new ArrayList<>();
List<Car> carList = new ArrayList<>();
List<Animal> animalList = new ArrayList<>();
catList.add(new Cat());
carList.add(new Car());
animalList.add(new Cat("cat"));
animalList.add(new Animal("dog"));
getCars(carList);
getAnimals(animalList);
// 通用
getCars(animalList);
}
public static void getCars(List<?> list){
System.out.println(list.get(0));
}
public static void getAnimals(List<? extends Animal> list){
System.out.println(list.get(0).name);
}
}
? super
也很好理解,表示只能接受 该类型及上层父类类型。
这里也举一个 实际工作中可能用到的例子:
/**
* 去重Predicate,filter参数
* @param keyExtractor 去重依据
* @param <T> 泛型类型
*/
public <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
3. 总结
通过本文,我们简单 学习/回顾 了泛型的概念与常用方式。通过泛型,我们可以在使用集合等一些场合指定操作的数据类型,方便数据类型管理;也可以借助泛型类、泛型方法、泛型接口的特性,做一些通用工具类或工具方法,方便日常开发使用。