泛型(generics)
他是 JDK5 中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数
常见的泛型的类型表示
上面的 T 仅仅类似一个形参的作用,名字实际上是可以任意起的,但是我们写代码总该是要讲究可读性的。常见的参数通常有 :
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(表示Java 类,包括基本的类和我们自定义的类)
K - Key(表示键,比如Map中的key)
V - Value(表示值)
? - (表示不确定的java类型)
但是泛型的参数只能是类类型,不能是基本的数据类型,他的类型一定是自Object的
注意:泛型不接受基本数据类型,换句话说,只有引用类型才能作为泛型方法的实际参数
如果你想要学习Java的话,我给你分享一些Java的学习资料,你不用浪费时间到处搜了,从Java入门到精通的资料我都给你整理好了,这些资料都是我做Java这几年整理的Java最新学习路线,Java笔试题,Java面试题,Java零基础到精通视频课程,Java开发工具,Java练手项目,Java电子书,Java学习笔记,PDF文档教程,Java程序员面经,Java求职简历模板等,这些资料对你接下来学习Java一定会带来非常大的帮助,每个Java初学者都必备,请你进我的**Java技术qq交流群127522921**自行下载,所有资料都在群文件里,进去要跟大家多交流学习哦。
2. 为什么要使用泛型?
=============
说到为什么要使用,那肯定是找一大堆能说服自己的优点啊。
泛型的引入,是java语言的来讲是一个较大的功能增强。同时对于编译器也带来了一定的增强,为了
需要文中资料的朋友,可以加我\/信获取:vip1024b 备注Java
支持泛型,java的类库都做相应的修改以支持泛型的特性。
(科普:实际上java泛型并不是 jdk5(2004发布了jdk5) 才提出来的,早在1999年的时候,泛型机制就是java最早的规范之一)
另外,泛型还具有以下的优点:
1.提交了java的类型安全
泛型在很大程度上来提高了java的程序安全。例如在没有泛型的情况下,很容易将字符串 123 转成 Integer 类型的 123 亦或者 Integer 转成 String,而这样的错误是在编译期无法检测。而使用泛型,则能很好的避免这样的情况发生。
2.不需要烦人的强制类型转换
泛型之所以能够消除强制类型转换,那是因为程序员在开发的时候就已经明确了自己使用的具体类型,这不但提高了代码的可读性,同样增加了代码的健壮性。
提高了代码的重用性
泛型的程序设计,意味着编写的代码可以被很多不同类型的对象所重用
在泛型规范正式发布之前,泛型的程序设计是通过继承来实现的,但是这样子有两个严重的问题:
① 取值的时候需要强制类型转换,否则拿到的都是 Object
② 编译期不会有错误检查
我们来看下这两个错误的产生
2.1 编译期不会有错误检查
==============
public class DonCheckInCompile {
public static void main(String[] args) {
List list = new ArrayList();
list.add(“a”);
list.add(3);
System.out.println(list);
}
}
程序不但不会报错,还能正常输出
2.2 强制类型转换
==========
public class DonCheckInCompile {
public static void main(String[] args) {
List list = new ArrayList();
list.add(“a”);
list.add(3);
for (Object o : list) {
System.out.println((String)o);
}
}
}
因为你并不知道实际集合中的元素到底是哪些类型的,所以在使用的时候也是不确定的,如果在强转的时候,那必然会带来意想不到的错误,这样潜在的问题就好像是定时炸弹,肯定是不允许发生的。所以这就更体现了泛型的重要性。
3. 泛型方法
========
在 java 中,泛型方法可以使用在成员方法、构造方法和静态方法中。语法如下:
public <申明泛型的类型> 类型参数 fun();如 public T fun(T t);这里的 T 表示一个泛型类型,而 表示我们定义了一个类型为 T 的类型,这样的 T 类型就可以直接使用了,且 需要放在方法的返回值类型之前。T 即在申明的时候是不知道具体的类型的,只有的使用的时候才能明确其类型,T 不是一个类,但是可以当作是一种类型来使用。
下面来通过具体的例子来解释说明,以下代码将数组中的指定的两个下标位置的元素进行交换(不要去关注实际的需求是什么),第一种 Integer 类型的数组
public class WildcardCharacter {
public static void main(String[] args) {
Integer[] arrInt = {1, 2, 3, 4, 5, 6, 7, 8, 9};
change(arrInt, 0, 8);
System.out.println("arr = " + Arrays.asList(arrInt));
}
/**
-
将数组中的指定两个下标位置的元素交换
-
@param arr 数组
-
@param firstIndex 第一个下标
-
@param secondIndex 第二个下标
*/
private static void change(Integer[] arr, int firstIndex, int secondIndex) {
int tmp = arr[firstIndex];
arr[firstIndex] = arr[secondIndex];
arr[secondIndex] = tmp;
}
}
第二种是 String 类型的数组
编译直接都不会通过,那是必然的,因为方法定义的参数就是 Integer[] 结果你传一个 String[],玩呢。。。所以这个时候只能是再定义一个参数类型是 String[]的。
如果你想要学习Java的话,我给你分享一些Java的学习资料,你不用浪费时间到处搜了,从Java入门到精通的资料我都给你整理好了,这些资料都是我做Java这几年整理的Java最新学习路线,Java笔试题,Java面试题,Java零基础到精通视频课程,Java开发工具,Java练手项目,Java电子书,Java学习笔记,PDF文档教程,Java程序员面经,Java求职简历模板等,这些资料对你接下来学习Java一定会带来非常大的帮助,每个Java初学者都必备,请你进我的**Java技术qq交流群127522921**自行下载,所有资料都在群文件里,进去要跟大家多交流学习哦。
那要是再来一个 Double 呢?Boolean 呢?是不是这就产生问题了,虽然说这种问题不是致命的,多写一些重复的代码就能解决,但这势必导致代码的冗余和维护成本的增加。所以这个时候泛型的作用就体现了,我们将其改成泛型的方式。
/**
-
@param t 参数类型 T
-
@param firstIndex 第一个下标
-
@param secondIndex 第二个下标
-
@param 表示定义了一个类型 为 T 的类型,否则没人知道 T 是什么,编译期也不知道
*/
private static void changeT(T[] t, int firstIndex, int secondIndex) {
T tmp = t[firstIndex];
t[firstIndex] = t[secondIndex];
t[secondIndex] = tmp;
}
接下来调用就简单了
public static void main(String[] args) {
//首先定义一个Integer类型的数组
Integer[] arrInt = {1, 2, 3, 4, 5, 6, 7, 8, 9};
//将第 1 个和第 9 个位置的元素进行交换
changeT(arrInt, 0, 8);
System.out.println("arrInt = " + Arrays.asList(arrInt));
// 然后在定义一个String类型的数组
String[] arrStr = {“a”, “b”, “c”, “d”, “e”, “f”, “g”};
//将第 1 个和第 2 个位置的元素进行交换
changeT(arrStr, 0, 1);
System.out.println("arrStr = " + Arrays.asList(arrStr));
}
问题迎刃而解,至于普通的泛型方法和静态的泛型方法是一样的使用,只不过是一个数据类一个属于类的实例的,在使用上区别不大(但是需要注意的是如果在泛型类中 静态泛型方法是不能使用类泛型中的泛型类型的,这个在下文的泛型类中会详细介绍的)。
最后再来看下构造方法
public class Father {
public Father(T t) {
}
}
然后假设他有一个子类是这样子的
class Son extends Father {
public Son(T t) {
super(t);
}
}
这里强调一下,因为在 Father 类中是没有无参构造器的,取而代之的是一个有参的构造器,只不过这个构造方法是一个泛型的方法,那这样子的子类必然需要显示的指明构造器了。
- 通过泛型方法获取集合中的元素测试
既然说泛型是在申明的时候类型不是重点,只要事情用的时候确定就可以下,那你看下面这个怎么解释?
此时想往集合中添加元素,却提示这样的错误,连编译都过不了。这是为什么?
因为此时集合 List 的 add 方法,添加的类型为 T,但是很显然 T 是一个泛型,真正的类型是在使用时候才能确定的,但是 在 add 的并不能确定 T 的类型,所以根本就无法使用 add 方法,除非 list.add(null),但是这却没有任何意义。
4. 泛型类
=======
先来看一段这样的代码,里面的使用到了多个泛型的方法,无需关注方法到底做了什么
public class GenericClassTest{
public static void main(String[] args) {
//首先定义一个Integer类型的数组
Integer[] arrInt = {1, 2, 3, 4, 5, 6, 7, 8, 9};
//将第 1 个和第 9 个位置的元素进行交换
new GenericClassTest().changeT(arrInt, 0, 8);
System.out.println("arrInt = " + Arrays.asList(arrInt));
List list = Arrays.asList(“a”, “b”);
testIter(list);
}
/**
-
@param t 参数类型 T
-
@param firstIndex 第一个下标
-
@param secondIndex 第二个下标
-
@param 表示定义了一个类型 为 T 的类型,否则没人知道 T 是什么,编译期也不知道
*/
private void changeT(T[] t, int firstIndex, int secondIndex) {
T tmp = t[firstIndex];
t[firstIndex] = t[secondIndex];
t[secondIndex] = tmp;
}
/**
-
遍历集合
-
@param list 集合
-
@param 表示定义了一个类型 为 T 的类型,否则没人知道 T 是什么,编译期也不知道
*/
private static void testIter(List list) {
for (T t : list) {
System.out.println("t = " + t);
}
}
}
可以看到里面的 是不是每个方法都需要去申明一次,那要是 100 个方法呢?那是不是要申明 100 次的,这样时候泛型类也就应用而生了。那泛型类的形式是什么样子的呢?请看代码
public class GenericClazz{
//这就是一个最基本的泛型类的样子
}
下面我们将刚刚的代码优化如下,但是这里不得不说一个很基础,但是却很少有人注意到的问题,请看下面的截图中的文字描述部分。
为什么实例方法可以,而静态方法却报错?
-
首先告诉你结论:静态方法不能使用类定义的泛型,而是应该单独定义泛型
-
到这里估计很多小伙伴就瞬间明白了,因为静态方法是通过类直接调用的,而普通方法必须通过实例来调用,类在调用静态方法的时候,后面的泛型类还没有被创建,所以肯定不能这么去调用的
所以说这个泛型类中的静态方法直接这么写就可以啦
/**
-
遍历集合
-
@param list 集合
*/
private static void testIter(List list) {
for (K t : list) {
System.out.println("t = " + t);
}
}
- 多个泛型类型同时使用
我们知道 Map 是键值对形式存在,所以如果对 Map 的 Key 和 Value 都使用泛型类型该怎么办?一样的使用,一个静态方法就可以搞定了,请看下面的代码
public class GenericMap {
private static <K, V> void mapIter(Map<K, V> map) {
for (Map.Entry<K, V> kvEntry : map.entrySet()) {
K key = kvEntry.getKey();
V value = kvEntry.getValue();
System.out.println(key + “:” + value);
}
}
public static void main(String[] args) {
Map<String, String> mapStr = new HashMap<>();
mapStr.put(“a”, “aa”);
mapStr.put(“b”, “bb”);
mapStr.put(“c”, “cc”);
mapIter(mapStr);
System.out.println(“======”);
Map<Integer, String> mapInteger = new HashMap<>();
mapInteger.put(1, “11”);
mapInteger.put(2, “22”);
mapInteger.put(3, “33”);
mapIter(mapInteger);
}
}
到此,泛型的常规的方法和泛型类已经介绍为了。
5. 通配符
=======
通配符 ? 即占位符的意思,也就是在使用期间是无法确定其类型的,只有在将来实际使用的时候指明类型,它有三种形式
- <?> 无限定的通配符。是让泛型能够接受**未知类型**的数据
-
< ? extends E>有上限的通配符。能接受指定类及其子类类型的数据,E就是该泛型的上边界
- <? super E>有**下限**的通配符。能接受**指定类及其父类类型**的数据,E就是该泛型的下边界
5.1 通配符之 <?>
============
上面刚刚说到了使用一个类型来表示反省类型是必须要申明的,也即 ,那是不是不申明就不能使用泛型呢?当然不是,这小节介绍的 <?> 就是为了解决这个问题的。
<?> 表示,但是话又说话来了,那既然可以不去指明具体类型,那 ? 就不能表示一个具体的类型也就是说如果按照原来的方式这么去写,请看代码中的注释而又因为任何类型都是 Object 的子类,所以,这里可以使用 Object 来接收,对于 ? 的具体使用会在下面两小节介绍
另外,大家要搞明白泛型和通配符不是一回事
5.2 通配符之 <? extend E>
最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
用泛型呢?当然不是,这小节介绍的 <?> 就是为了解决这个问题的。
而又因为任何类型都是 Object 的子类,所以,这里可以使用 Object 来接收,对于 ? 的具体使用会在下面两小节介绍
另外,大家要搞明白泛型和通配符不是一回事
5.2 通配符之 <? extend E>
最后
码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到
又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考
以下是部分内容截图
[外链图片转存中…(img-lIrklvjM-1716310804989)]