目录
泛型的设计背景和原理
对于集合类容器在设计阶段,不能确定添加进集合的到底是哪一个类型的对象,为解决这个问题,JDK1.5提出了泛型,指明集合类容器中添加的对象类型。列如Colleation<E> list<E> Set<E> 中的<E> 就是泛型。
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念, 允许我们在创建集合时再指定集合元素的类型,正如:List,这表明 该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持, 从而可以在声明集合变量、创建集合对象时传入类型实参。
泛型的好处是集合中添加对象时,在编译期间,能够检查对象的类型,如果和指定的泛型类不同,就会报错。同时也避免了类型转换,提高了效率
集合中使用泛型
在集合中使用泛型,在添加对象时,会进行检查,如果与指定的类型不一致,就会报错。该错误属于编译时错误
List list = new ArrayList(); // 这样写,泛型默认添加的都是Object类型
ArrayList<Integer> list = new ArrayList<>();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);
//遍历方式一: 增强for
for(Integer i : list){
System.out.println(i); //不需要强转
}
//遍历方式二: 迭代器遍历
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
map.put(33, "Tom"); //添加失败
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
Iterator<Entry<String,Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Entry<String,Integer> entry = iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
注意事项
interface List<K,V> K,V并不代表值,而是代表类
基本数据类型不能出现在泛型所要求的代表类型
class A<int>会报错 需要改成 class A<Integer>
List<Integer> list = new List<>();
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
泛型不同的引用不能相互赋值。
Set<String> set = null
Set<Integer> set1 = null;
set 不能指向 set1
尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型
但是,在运行时只有一个ArrayList被加载到JVM中。
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价
于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
对于静态成员变量、方法,内部类不能使用泛型,原因是静态成员会随着类的加载而加载,只会加载一次
泛型的指定中不能使用基本数据类型,可以使用包装类替换
异常类不能是泛型的
class A extends Exception {
try {
}catch(T t) {
}
}
不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
全部保留
class B<T> extends A<T> {
}
class B extends A { // class B extends A<Object>
}
全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
部分保留
class Son4<T2> extends Father<Integer, T2>
自定义泛型类
1.泛型标识符T、M、R等,一般为大写字符
2.泛型标识符可以为多个
3.使用泛型数组不能初始化,可以强转或者当参数
class A<T> { // 可以定义多个<A,B,C,D,E...>
T s;
public A() {} // 无参构造
public A(T s) {} // 有参构造
public void Test1(String name, T t) { //这并不是泛型方法
System.out.println(name + " " + t);
}
泛型参数和类的泛型参数没有任何关系
public T Test2(String name, T t) { // 这并不是泛型方法,只是普通方法
System.out.println("hello world" + name + t);
return t;
}
T[] t = new T[10]; //报错,不知道类型
public T add(T[] t, Collection<T> list) { //使用数组
for(T e: t) {
list.add(e);
}
}
泛型接口
1.接口中的静态成员也不能使用泛型
2.泛型接口的类型,在实现或继承接口的时候确定
3.没有指定类型,默认为Object类型
4.在JDK8中可以在默认方法中使用泛型
interface A<K,V> {
public abstract void say(K k);
public abstract void eat(V v);
public static final int n = 10 // K n = 10; 这里不可以使用泛型
default <K,V> void fly(K k,V v) { //默认方法
}
}
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {
}
//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double
//,在实现IUsu接口的方法时,使用String替换U, 是Double替换R
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
//给U 指定Integer 给 R 指定了 Float
//所以,当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
//没有指定类型,默认为Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb { //等价 class CC implements IUsb<Object,Object> {
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
interface IUsb<U, R> {
int n = 10;// 常量
//U name; 不能这样使用
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
//在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
泛型方法
1.方法可以被泛型化,不管方法所属的类是不是泛型类,在泛型方法中可以定义泛型参数,在调用该方法时,传入参数
2.泛型方法可以加static修饰,只有在调用时才会指明参数类型。
class A<T> {
public <E> List<E> getAll(E[] o) {
ArrayList<E> list = new ArrayList<E>();
for(E e: o) {
list.add(e);
}
return list;
}
泛型继承
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的 类或接口,G并不是G的子类型! 比如:String是Object的子类,但是List并不是List的子类。
Person[] persons = null;
Man[] mans = null;
// 而 Person[] 是 Man[] 的父类.
persons = mans;
Person p = mans[0];
// 在泛型的集合上
List<Person> personList = null;
List<Man> manList = null;
// personList = manList;(报错
通配符使用
1.使用类型通配符:?比如:List<?> ,Map<?,?> List<?>是List<Integer>、List<String>等各种泛型List的父类。
2.读取List的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object。
3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中 添加对象。 唯一的例外是null,它是所有类型的成员
List<?> list;
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);
list1.add(3);
list = list1;
for(List e: list) {
System.out.println(e);
}
//使用List<?> list不能写入,只能读取
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误,因为不确定c的类型
唯一的例外的是null,它是所有类型的成员
注意事项
//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();
//注意点4.有限制的通配符
通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口
通配符指定下限
下限super:使用时指定的类型不能小于操作的类
<? extends Number> (无穷小 , Number]
只允许泛型为Number及Number子类的引用调用
<? super Number> [Number , 无穷大)
只允许泛型为Number及Number父类的引用调用
<? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用