泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型结构包括泛型类、泛型接口和泛型方法
泛型类与泛型接口的区别其实就是类与接口的区别,这里以泛型类为例:
以JDK中的Collection接口为例,区分普通方法和泛型方法:
public interface Collection<E> extends Iterable<E> { //普通方法 boolean add(E data); //泛型方法,泛型方法的类型区别于类或接口中的类型,普通类中也可以含有泛型方法 <T> T[] toArray(T[] a); }
注意:区别于泛型类中带有类型符号的普通方法的是,泛型方法可以是静态的。具体说来就是,上面的add方法不能是静态的,因为E是在类创建时类型才确定下来;而toArray方法可以是静态的,因为T是在方法调用时就确定下来了。
//Son不是泛型类,它继承了Father中的Integer public class Son extends Father<Integer> { } //Son<T> 仍然是泛型类 public class Son<T> extends Father<T> { }
java中的向上转型有如下几种形式
//1.对象,编译通过 Object abj = null; String str = null; abj = str; //2.数组,编译通过 Object[] objArr = null; String[] strArr = null; objArr = strArr //3.泛型1,编译通过,其实就是平常写的List<String> list = new ArrayList<>(); List<String> list1 = null; ArrayList<String> list2 = null; list1 = list2 //4.泛型2,编译不通过,此时的objList和strList的类型不具有子父类关系 List<Object> objList = null; List<String> strList = null; objList = strList; ## 第四种情况会导致一些问题,比如如下方法,遍历集合中的元素 public void foreach(List<Object> list) { } ## List<String>类型的变量就无法用这个方法遍历,因为List<Object>类型和List<String>类型 不具备子父类关系,无法使用多态,这样就需要重载foreach方法
总结:List<Object>
是ArrayList<Object>
的父类;List<Object>
不是List<String>
的父类。针对上述的第四种情况,泛型通配符可以解决。
泛型通配符用?
来表示,在泛型中,List<?>
是List<String>
和List<Object>
的父类,可以声明一下方法解决上述第四种情况
public void foreach(List<?> list) { Iterator<?> iterator = list.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println(obj); } }
? extends Son
,其中Son是通配符?
的上限,可以理解为?
的类只能是Son
及其子类,即小于等于? super Son
,其中Son是通配符?
的下限,可以理解为?
的类只能是Son
及其父类,即大于等于List<? extends Son>
和List<? super Son>
,声明以下对象做测试List<? extends Son> list1 = new ArrayList<>(); List<? super Son> list2 = new ArrayList<>(); List<Grandson> list3 = new ArrayList<>(); List<Son> list4 = new ArrayList<>(); List<Father> list5 = new ArrayList<>(); //测试一 list1 = list3;//(编译成功) list1 = list4;//(编译成功) list1 = list5;//(编译失败) //结论:G<? extends A>可以作为G<A>和G<B>的父类,其中B是A的子类 //测试二 list2 = list3;//(编译失败) list2 = list4;///(编译成功) list2 = list5;///(编译成功) //结论:G<? super A>可以作为G<A>和G<B>的父类,其中B是A的父类
List<?>
List<String> listStr = new ArrayList<>(); List<Integer> listInteger = new ArrayList<>(); List<?> list = null; list = listStr ; list = listInteger ; //不能添加(只能添加null),编译失败 list.add("1"); list.add(1); //只能读取,读取的是object类型的对象 Object o = list.get(0);
List<? extends Son>
//读取数据 list1 = list4;//用list1 = list3同理 //由上面的测试可知,list1中的类型最大为Son,这里用了最大的类型,保证了多态的特性(父类引用指向子类对象) Son s = list1.get(0); //写入数据 //无法确定list1的下限,传入的Son类型不能确定是?的子类还是父类,不满足多态特性 list1.add(new Son());//(编译失败)
无法写入数据,那具体怎么使用呢?通配符在声明局部变量时没什么意义,但是作为形参,限制传入的参数类型时,它非常重要
List<? super Son>
//读取数据 list2 = list4; //只能返回Object,因为list2中的对象最小类型是Son,但是上限没有定,只能用顶级父类Object才能保证多态特性 Object o = list2.get(0); //写入数据 //list2的下限是Son,可以添加Son及其子类,以满足多态特性 list2.add(new Son()); list2.add(new Grandson());//(编译成功)
使用案例:TreeSet
的构造器中传入的比较器使用了Comparator<? super E>
ArrayList<String> strList = new ArrayList<>(); ArrayList<Integer> intList = new ArrayList<>(); System.out.println(strList.getClass().getSimpleName());//ArrayList System.out.println(intList.getClass().getSimpleName());//ArrayList System.out.println(strList.getClass() == intList.getClass());//true
//todo
class<?>
作为通配泛型,当我们不知道确切类型的时候,可以做以下声明:public class<?> clazz;
class<T>
和List<T>
类似,一般用在反射中,具体用法如下:// 通过反射的方式生成 Son // 对象,这里比较明显的是,我们需要使用强制类型转换,强制类型转换可能在运行期报ClassCastException Son son= (Son) Class.forName("com.test.model.Son").newInstance(); //使用class<T>优化 //当传入的类型不是T时,无法创建T类型的对象,且在编译期就会报错 public static <T> T createInstance(class<T> clazz) throw Exception { return clazz.newInstance(); }
//编译失败 new ArrayList<String>[5]; //编译成功 ArrayList[] list = new ArrayList[5]; ArrayList<String>[] listArr = list;
public class Fruit<T> { //编译成功, 可以声明T[] private T[] array; //public Fruit(int length) { //编译失败 //array = new T[length]; //} //可以使用反射加类型强转 public Fruit(Class<T> clazz, int length) { array = (T[]) Array.newInstance(clazz, length); } public void put(int index, T item) { array[index] = item; } }
数据库中的一张表对应java中的一个类型,对数据库数据的增删改查,都可以归结为操作这个类,针对多张不同的表,映射到多个类,所要进行的操作都相同(CRUD),因此可以使用泛型。具体用法体现在:
public class BaseDAO<T> { //带有类型参数T的增删改查 public T get(Query query) { ... } ... } public class XXXDAO extends BaseDAO<XXX> { //直接继承增删改查方法 }