泛型与通配符详解

Myth丶恋晨 2022-05-31 02:23 351阅读 0赞

1回顾泛型类
  泛型类:具有一个或多个泛型变量的类被称之为泛型类。

复制代码

  1. class ClassGenericity<T> {
  2. //在类里面可以直接使用T的类型
  3. T aa;
  4. public void test11(T bb) {
  5. //................
  6. }
  7. //静态方法 在类上面定义的泛型,不能再静态方法里面使用
  8. public static <A> void test12(A cc) {
  9. //..................
  10. }
  11. }
  12. public class TestClassGenericity{
  13. public static void main(String[] args) {
  14. ClassGenericity<String>genericity=new ClassGenericity<String>();
  15. genericity.test11("1234");
  16. ClassGenericity.test12(6789);
  17. }
  18. }

复制代码

2 泛型方法
  泛型方法的特点:
    方法的参数中可以使用泛型变量;
    方法的返回值中可以使用泛型变量。

复制代码

  1. public class MethodGenericity {
  2. public static void main(String[] args) {
  3. String arr[]={"aaa","bbb","ccc","ddd"};
  4. System.out.println(Arrays.toString(arr));
  5. exchange(arr,0,3); //交换0,3位置元素
  6. System.out.println(Arrays.toString(arr));
  7. }
  8. private static<T> void exchange(T[] arr, int i, int j) {
  9. T temp=arr[i];
  10. arr[i]=arr[j];
  11. arr[j]=temp;
  12. }
  13. }

复制代码

复制代码

  1. public class Test1 {
  2. public <T> T get(T[] ts) {
  3. return ts[ts.length / 2];
  4. }
  5. @Test
  6. public void test()
  7. {
  8. String[] names ={"zhangSan", "liSi","wangWu"};
  9. System.out.println(get(names));//输出lisi
  10. }
  11. }

复制代码

  调用泛型方法时无需指定泛型变量,编译器会通过实际参数的类型来识别泛型变量的类型,上例中传递的参数为String[]类型,那么相当于给泛型类型T赋值为String。

3 继承(实现)泛型类(接口)
  继承泛型类需要为父类的泛型变量赋值!就好比创建泛型类的对象时需要给泛型变量赋值一样。
  创建泛型类对象:List list = new ArrayList();
  继承泛型类1(子类也是泛型类):public class MyList1 extends ArrayList {…}
  继承泛型类2(子类不是泛型类):public class MyList2 extends ArrayList {…}

4 通配符
  为了说明通配符的作用,我们先看个例子:
    List list1 = new ArrayList();
    List list2 = new ArrayList();

  上面的调用都是编译不通过的!这说明想写一个即可以打印list1,又可以打印list2的方法是不可能的!

  1. public static void fun(List<Object> list) {…}
  2. List<String> list1 = new ArrayList<String>();
  3. List<Integer> list2 = new ArrayList<Integer>();
  4. fun(list1);//编译不通过
  5. fun(list2);//编译不通过

  如果把fun()方法的泛型参数去除,那么就OK了。即不使用泛型!

  1. public static void fun(List list) {…}//会有一个警告
  2. List<String> list1 = new ArrayList<String>();
  3. List<Integer> list2 = new ArrayList<Integer>();
  4. fun(list1);
  5. fun(list2);

  上面代码是没有错了,但会有一个警告。警告的原因是你没有使用泛型!Java希望大家都去使用泛型。你可能会说,这里根本就不能使用泛型!!!!!

  4.1 通配符概述
    通配符就是专门处理这一问题的。

  1. public static void fun(List<?> list) {…}

    上面代码中的“?”就是一个通配符,它只能在“<>”中使用。这时你可以向fun()方法传递List、List类型的参数了。当传递List类型的参数时,表示给“?”赋值为String;当传递List类型的参数给fun()方法时,表示给“?”赋值为Integer。

  4.2 通配符的缺点
    带有通配符的参数不能使用与泛型相关的方法,例如:list.add(“hello”)编译不通过。
    上面的问题是处理了,但通配符也有它的缺点。在上面例子中,List<?> list参数中的通配符可以被赋任何值,但同时你也不知道通配符被赋了什么值。当你不知道“?”是什么时,会使你不能使用任何与泛型相关的方法。也就是说fun()方法的参数list不能再使用它的与泛型相关的方法了。例如:list.add(“hello”)是错误的,因为List类的add()方法的参数是T类型,而现在你不知道T是什么类型,你怎么去添加String的东西给list呢?如果使用者在调用fun()方法时传递的不是List,而是List时,你添加String当然是不可以的。当然,还可以调用list的get()方法。就算你不知道“?”是什么类型,但它肯定是Object类型的。所以你可以:Object o = list.get(0);

  4.3 通配符的限制
    通配符只能出现在引用的定义中,而不能出现在创建对象中。例如:new ArrayList<?>(),这是不可以的。ArrayList<?> list = null,这是可以的。

  4.4 带有下边界的通配符
    List<? extends Number> list;
      其中<? extends Number>表示通配符的下边界,即“?”只能被赋值为Number或其子类型。

  1. public static void fun(List<? extends Number> list) {…}
  2. fun(new ArrayList<Integer>());//ok
  3. fun(new ArrayList<Double>());//ok
  4. fun(new ArrayList<String>());//不ok

      当fun()方法的参数为List<? extends Number>后,说明你只能赋值给“?”Number或Number的子类型。虽然这多了一个限制,但也有好处,因为你可以用list的get()方法。就算你不知道“?”是什么类型,但你知道它一定是Number或Number的子类型。所以:Number num = list.get(0)是可以的。但是,还是不能调用list.add()方法!

  4.5 带有下边界的通配符
    List<? super Integer> list;
      其中<? super Integer>表示通配符的下边界,即“?”只能被赋值为Integer或其父类型。

  1. public static void fun(List<? super Integer> list) {…}
  2. fun(new ArrayList<Integer>());//ok
  3. fun(new ArrayList<Number>());//ok
  4. fun(new ArrayList<Object>());//ok
  5. fun(new ArrayList<String>());//不ok

      这时再去调用list.get()方法还是只能使用Object类型来接收:Object o = list.get(0)。因为你不知道“?”到底是Integer的哪个父类。但是你可以调用list.add()方法了,例如:list.add(new Integer(100))是正确的。因为无论“?”是Integer、Number、Object,list.add(new Integer(100))都是正确的。

  4.6 通配符小结
    1. 方法参数带有通配符会更加通用;
    2. 带有通配符类型的对象,被限制了与泛型相关方法的使用;
    3. 下边界通配符:可以使用返回值为泛型变量的方法;
    4. 上边界通配符:可以使用参数为泛型变量的方法。

  4.7 通配符应用实例

  boolean addAll(Collection<? extends E> c)//JDK集合的addAll方法

  1. List<Number> numList = new ArrayList<Number>();
  2. List<Integer> intList = new ArrayList<Integer>();
  3. numList.addAll(intList);//正确!!!!!!addAll(Collection<? extends Number> c), 传递的是List<Integer>

  如果用改成boolean addAll(Collection c)

  1. List<Number> numList = new ArrayList<Number>();
  2. List<Integer> intList = new ArrayList<Integer>();
  3. numList.addAll(intList);//错误!!!!!addAll(Collection<Number> c), 传递的是List<Integer>

5 通配符应用实例

复制代码

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class Demo2 {
  4. public void fun1() {
  5. Object[] objArray = new String[10];//正确!!!
  6. objArray[0] = new Integer(100);//错误!!!编译器不会报错,但是运行时会抛ArrayStoreException
  7. //List<Object> objList = new ArrayList<String>();//错误!!!编译器报错,泛型引用和创建两端,给出的泛型变量必须相同!
  8. }
  9. public void fun2() {
  10. List<Integer> integerList = new ArrayList<Integer>();
  11. print(integerList);
  12. List<String> stringList = new ArrayList<String>();
  13. print(stringList);
  14. }
  15. /*
  16. * 其中的?就是通配符,?它表示一个不确定的类型,它的值会在调用时确定下来
  17. * 通配符只能出现在左边!即不能在new时使用通配符!!!
  18. * List<?> list = new ArrayList<String>();
  19. * 通配符好处:可以使泛型类型更加通用!尤其是在方法调用时形参使用通配符!
  20. */
  21. public void print(List<?> list) {
  22. //list.add("hello");//错误!!!编译器报错,当使用通配符时,对泛型类中的参数为泛型的方法起到了副作用,不能再使用!
  23. Object s = list.get(0);//正确!!!但是只是得益于object类是所有类的父类,换成其他任何类编译器都会报错!说明当使用通配符时,泛型类中返回值为泛型的方法,也作废了!
  24. }
  25. public void fun3() {
  26. List<Integer> intList = new ArrayList<Integer>();
  27. print1(intList);
  28. List<Long> longList = new ArrayList<Long>();
  29. print1(longList);
  30. }
  31. /*
  32. * 给通配符添加了限定:
  33. * 只能传递Number或其子类型
  34. * 子类通配符对通用性产生了影响,但使用形参更加灵活
  35. */
  36. public void print1(List<? extends Number> list) {
  37. //list.add(new Integer(100));//错误!!!编译器报错,说明参数为泛型的方法还是不能使用(因为?也可能为Long型)
  38. Number number = list.get(0);//正确!!!返回值为泛型的方法可用了!
  39. }
  40. public void fun4() {
  41. List<Integer> intList = new ArrayList<Integer>();
  42. print2(intList);
  43. List<Number> numberList = new ArrayList<Number>();
  44. print2(numberList);
  45. List<Object> objList = new ArrayList<Object>();
  46. print2(objList);
  47. }
  48. /*
  49. * 给通配符添加了限定
  50. * 只能传递Integer类型,或其父类型
  51. */
  52. public void print2(List<? super Integer> list) {
  53. list.add(new Integer(100));//正确!!!参数为泛型的方法可以使用了
  54. Object obj = list.get(0);//正确!!!但是只是得益于object类是所有类的父类,换成其他任何类编译器都会报错!说明返回值为泛型的方法,还是不能使用
  55. }
  56. }

复制代码

6 泛型父类获取子类传递的类型参数

复制代码

  1. import java.lang.reflect.ParameterizedType;
  2. import java.lang.reflect.Type;
  3. import org.junit.Test;
  4. public class Demo1 {
  5. @Test
  6. public void fun1() {
  7. new B();//输出:class B java.lang.String
  8. }
  9. @Test
  10. public void fun2() {
  11. new C();//输出:class C java.lang.Integer
  12. }
  13. }
  14. class A<T> {
  15. public A() {
  16. //在这里获取子类传递的泛型信息,要得到一个Class!
  17. Class clazz = this.getClass(); //得到子类的类型
  18. System.out.print(clazz+" ");
  19. Type type = clazz.getGenericSuperclass(); //获取传递给父类参数化类型
  20. ParameterizedType pType = (ParameterizedType) type; //它就是A<String>
  21. Type[] types = pType.getActualTypeArguments(); //它就是一个Class数组
  22. Class c = (Class)types[0]; //它就是String或者Integer
  23. //连成一句:Class c = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
  24. System.out.println(c.getName());
  25. }
  26. }
  27. class B extends A<String> {}
  28. class C extends A<Integer> {}

复制代码

发表评论

表情:
评论列表 (有 0 条评论,351人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java 中的通配符详解

    目录 1、如何定义和使用上界通配符? 2、如何定义和使用无界通配符? 3、如何定义和使用下界通配符? 4、如何使用通配符定义泛型类或接口之间的子类型关系? 5、通配符

    相关 通配符

    > 在看Java核心技术卷I中泛型通配符相关的知识时产生了很多疑问,通过了解后简单的做下笔记方便回顾。 > 本文重点记录了一下这几个问题: > > 为什么要用泛型通