前言
泛型即为“类型参数
”,这个类型参数在声明它的类、接口或方法中,代表未知的某种通用类型
集合类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK5.0之前只能把元素类型设计为Object,JDK5.0时Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型。比如:List<String>
,这表明该List只能保存字符串类型的对象。
所谓泛型,就是允许在定义类、接口时通过一个标识
表示类中某个属性的类型
或者是某个方法的返回值或参数的类型
。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)。
在集合中使用泛型之前可能存在的问题
问题1:类型不安全。因为add()的参数是Object类型,意味着任何类型的对象都可以添加成功
问题2:需要使用强转操作,繁琐。还有可能导致ClassCastException异常。
集合参数采用泛型之后 可以保证类型安全 不需进行强转
//泛型在Map中的使用
@Test
public void test2(){
HashMap<String,Integer> map = new HashMap<>();
map.put("Tom",67);
map.put("Jim",56);
map.put("Rose",88);
//编译不通过
// map.put(67,"Jack");
//遍历key集
Set<String> keySet = map.keySet();
for(String str:keySet){
System.out.println(str);
}
//遍历value集
Collection<Integer> values = map.values();
Iterator<Integer> iterator = values.iterator();
while(iterator.hasNext()){
Integer value = iterator.next();
System.out.println(value);
}
//遍历entry集
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Map.Entry<String, Integer> entry = iterator1.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ":" + value);
}
}
-
在创建集合对象的时候,可以指明泛型的类型。
具体格式为:List list = new ArrayList(); // 推荐
-
JDK7.0时,有新特性,可以简写为:
List list = new ArrayList<>(); //类型推断
-
泛型,也称为泛型参数,即参数的类型,只能使用引用数据类型进行赋值。(不能使用基本数据类型,可以使用包装类替换)
-
集合声明时,声明泛型参数。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。如果没有指明的话,看做是Object类型。
@Test
public void test() {
ArrayList<Integer> list = new ArrayList<>();
list.add(80);
list.add(75);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
//System.out.println(iterator.next());
Integer i = iterator.next();
int score = i;
System.out.println(score);
}
}
集合框架在声明接口和其实现类时,使用了泛型(jdk5.0),在实例化集合对象时,
如果没有使用泛型,则认为操作的是Object类型的数据。
如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关的方法中,凡是使用类的泛型的位置,都替换为具体的泛型类型
自定义泛型
<类型>这种语法形式就叫泛型。
//格式
class A<T>{
}
interface B<T1,T2>{
}
-
<类型>的形式我们称为类型参数,这里的"类型"习惯上使用T表示,是Type的缩写。即:。
-
:代表未知的数据类型,我们可以指定为,,
等。 -
类比方法的参数的概念,我们把,称为类型形参,将
称为类型实参,有助于我们理解泛型 -
这里的T,可以替换成K,V等任意字母。
-
在哪里可以声明类型变量<T> 声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为
泛型类
或泛型接口
。
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 接口们】{
}
//例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
- 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明了类型变量的方法,称为泛型方法。
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{
//...
}
//例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
....
}
① 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
- 经验:泛型要使用一路都用。要不用,一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤ 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。
如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
notice
① 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
② JDK7.0 开始,泛型的简化操作:ArrayList flist = new ArrayList<>();
③ 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
④ 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
⑤ 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
⑥ 异常类不能是带泛型的。
不能在try-catch中使用泛型定义
如果我们定义类、接口时没有使用<泛型参数>,但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>。
泛型方法的格式:
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常]{
}
//编写一个泛型方法,实现任意引用类型数组指定位置元素交换。
public static <E> void method1( E[] e,int a,int b){
E temp = e[a];
e[a] = e[b];
e[b] = temp;
}
@Test
public void test1(){
String[] arr = new String[]{"AA","BB","CC"};
method1(arr,0,2);
System.out.println(Arrays.toString(arr));
}
泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G并不是G的子类型!
比如:String是Object的子类,但是List并不是List的子类。
问题:在泛型类的方法中,使用了类的泛型参数。那么此方法是泛型方法吗?
A 泛型方法是指在方法定义中使用了类型参数的方法。如果一个类的方法在定义时使用了类的泛型参数,那么这个方法可以被视为泛型方法,即使这个类本身也是泛型的。
例如,以下是一个泛型类中的泛型方法的例子:
public class MyClass {
public void myMethod(T value) { // 方法体
}
}
在这个例子中,myMethod
方法使用了类的泛型参数 T
。这意味着方法可以操作与类实例相同类型的参数。由于方法直接使用了类的泛型参数,可以说这个方法是一个泛型方法,它利用了类的泛型特性。 总结一下,如果一个方法在其签名中使用了类的泛型参数,那么这个方法就是一个泛型方法,即使类本身是泛型的。
格式
权限修饰符 <T> 返回值类型 方法名(形参列表){
//通常在形参列表或返回值类型的位置会出现泛型参数T
}
public <E> E method(E e){
}
声明泛型方法时,一定要添加泛型参数
泛型参数在方法调用时,指明其具体的类型
泛型方法可以根据需要声明为static的
泛型方法所属的类是否是一个泛型类,都可以。
- 类SuperA是类A的父类,则G 与 G的关系:G 和 G是并列的两个类,没有任何子父类的关系。
比如:ArrayList 、ArrayList没有关系
- 类SuperA是类A的父类或接口,SuperA
与 A 的关系:SuperA 与A 有继承或实现的关系。
即A的实例可以赋值给SuperA 类型的引用(或变量)
比如:List 与 ArrayList
通配符的使用
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ?
使用类型通配符:?
比如:List<?>
,Map<?,?>
ArrayList<?>
List<?>
是List<String>
、List<Object>
等各种泛型List的父类。
通配符的读写
写操作:
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
唯一可以插入的元素是null,因为它是所有引用类型的默认值。
读操作:
另一方面,读取List<?>的对象list中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是Object
public static void main(String[] args) {
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Double>();
// list.add(3);//编译不通过
list.add(null);
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
l1.add("尚硅谷");
l2.add(15);
read(l1);
read(l2);
}
public static void read(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
通配符 不能用在泛型方法声明上,返回值类型前面<>不能使用? 不能用在泛型类的声明上 不能用在创建对象上,右边属于创建集合对象
有限制的通配符
-
允许所有泛型的引用调用
-
通配符指定上限:
<? extends 类/接口 >
-
使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
-
通配符指定下限:
<? super 类/接口 >
-
使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即>=
G<?> 可以看做是G类型的父类,即可以将G的对象赋值给G<?>类型的引用(或变量)
读写数据的特点(以集合ArrayList<?>为例说明)
读取数据:允许的,读取的值的类型为Object类型
写入数据:不允许的。特例:写入null值。
有限制条件的通配符
List<? extends A> : 可以将List或List赋值给List<? extends A>。其中B类是A类的子类。
List <? super A> :可以将List或List赋值给List<? extends A>。其中B类是A类的父类。
有限制条件的统配符的读写操作(难、了解)
比较前端的 Typescript 与 Java中 泛型区别