1.泛型
1.1 介绍
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值或参数的类型。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)。
@Testpublic void test1(){//类型推断,JDK7的新特性ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);}
1.2 在集合中使用泛型之前可能存在的问题
- 类型不安全。因为add()的参数是Object类型,意味着任何类型的对象都可以添加成功。
- 需要使用强制类型转换操作,可能导致ClassCastException异常。
1.3 使用说明
集合框架在声明接口和其实现类时,使用了泛型(JDK5.0),在实例化集合对象时:
- 如果没有使用泛型,则认为操作的是Object类型的数据。
- 如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型,则在集合的相关方法中,凡是使用泛型参数的位置,都指定为具体的参数类型。
1.3.1 集合中使用泛型
@Testpublic void test2() {HashMap<String, Integer> map = new HashMap<>();map.put("张三", 89);map.put("李四", 98);var entrySet = map.entrySet();var iterator = entrySet.iterator();while (iterator.hasNext()) {Map.Entry<String, Integer> entry = iterator.next();String key = entry.getKey();Integer value = entry.getValue();System.out.println(key + "--->" + value);}}
1.3.2 比较器中使用泛型
package p164;import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Predicate;public class ArrayListTest {public static void main(String[] args) {//1.创建一个ArrayList集合对象并指定泛型为<Integer>ArrayList<Integer> list = new ArrayList<>();//2.添加5个[0,100)以内的随机整数到集合中list.add((int) (Math.random()*100-1));list.add((int) (Math.random()*100-1));list.add((int) (Math.random()*100-1));list.add((int) (Math.random()*100-1));list.add((int) (Math.random()*100-1));//3.使用foreach遍历输出5个整数for (Integer i :list) {System.out.println(i);}//4.使用集合的removeIf方法删除偶数,为Predicate接口指定泛型<Integer>list.removeIf(new Predicate<Integer>() {@Overridepublic boolean test(Integer value) {return value % 2 == 0;}});System.out.println("-----");//5.再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型<Integer>Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()){Integer i = iterator.next();System.out.println(i);}}
}
1.4 自定义泛型类、接口
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口。
1.4.1 格式
class A<T>{}
interface B<T1,T2>{}
1.4.2 使用说明
- 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
- 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
- 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照 Object 处理,但不等价于Object。
- 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
- 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
1.4.3 注意点
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>。
- JDK7.0 开始,泛型的简化操作:ArrayList flist = new ArrayList<>();
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- 不能使用 new E[]。但是可以:E[] elements = (E[])new Object[capacity];——>参考:ArrayList 源码中声明:Object[] elementData,而非泛型参数类型数组。
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
- 异常类不能是带泛型的。
1.5 自定义泛型方法
1.5.1 格式
//通常在形参列表或返回值类型的位置会出现泛型参数T
权限修饰符 <T> 返回值类型 方法名(形参列表){}
public <E> E method(E e){return null;
}
1.5.2 说明
- 声明泛型方法时,一定要添加泛型参数。
- 泛型参数在方法调用时,指明其具体的类型。
- 泛型方法可以根据需要声明为static。
- 泛型方法所属的类是否是一个泛型类,都可以。
1.6 通配符?
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator 类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ? 。
使用类型通配符:?比如:List<?>,Map<?,?>,List<?>是 List、List等各种泛型 List 的父类。
1.6.1 写操作
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList();
c.add(new Object()); // 编译时错误
因为我们不知道 c 的元素类型,我们不能向其中添加对象。add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一可以插入的元素是 null,因为它是所有引用类型的默认值。
1.6.2 读操作
读取 List<?>的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是 Object。
1.6.3 有限制的通配符
- <?>
- 允许所有泛型的引用调用。
- 通配符指定上限:<? extends 类/接口 >
- 使用时指定的类型必须是继承某个类,或者实现某个接口,即<= 。
- 通配符指定下限:<? super 类/接口 >
- 使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即>=。