当前位置 博文首页 > 华为云开发者社区:15个问题告诉你如何使用Java泛型

    华为云开发者社区:15个问题告诉你如何使用Java泛型

    作者:华为云开发者社区 时间:2021-04-28 12:18

    摘要:Java泛型其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

    Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

    泛型方法

    一般定义如下,即方法的前面加了个<T>

    public class FTest {
        public <T> List<T> f(T t){...};
    }

    三种泛型参数推断方式:

    1、直接在f()前面加确定泛型

    fTest.<Integer>f(xxx)

    2、通过输入参数确定, 下面这个推断为Integer

    int number = 0;
    fTest.f(number)

    3、可通过 返回值 确定

    List<Integer> list = fTest.f(xxx);

    Q: 下面这段代码哪里有问题? 是toString()那里吗?

    public class A<T> {
        public static void  test(T t){
              System.out.println(t.toString());  
      }
    }

    A:test是static方法, 因此无法感知A<T>实例里的T
    需要改成
    public static <T> void test(T t)

    toString()那里没问题,toString就是Object的方法。

    泛型参数和类型消除

    Q: 泛型参数T在运行时,会变成什么?
    A: 统一变成Object且不包含任何类型信息。

    Q: 泛型参数T可以可以使用instanceof做比较吗?

    class A<T> {
       void f(Object arg)
       if(arg instanceof T) {
          ...
       }
    }

    A: 不能,编译器会报错。

    Q: 泛型参数T可以进行new T()或者new T[]操作吗?
    A: 不能,编译器会报错。

    Q: 能调用泛型参数对象里的方法吗?

    T.f();

    A: 只能调用Object的方法。

    Q: 可以用T做强制转化吗?

    T t = (T)object;

    A: 能运行, 但不会真正发生转型, 编译时会触发waring警告。

    新建泛型对象时的问题

    先假定有2个类, 基类Parent 和子类Child

    class Parent{}
    class Child extends Parent{}

    回答以下问题:
    Q:下面这句话有问题吗?

    List<Parent> list = new ArrayList<Child>()

    A:有问题,编译就错误了。 List<Parent>和ArrayList<Child>并不存在父子类的关系

    Q:

    List<? extends Parent> list = new ArrayList<Child>();

    这个list有什么特点?

    A:这个list可以调用A a = list.get(), 但是不能list.add(new Parent())

    • 原因:
      list.get()所做的操作是在返回时, 把内部的<? extend Parent> 强转成Parent, 是合理的,任何Parent的子类都可以转成Parent
      list.add(new Parent())所做的操作是在输入时, 把外部的A转成内部的<? extend Parent>, 这是不合理的,因为我们不知道这个Parent对象可以转成哪个Parent的子类。

    Q:

    List<? super Child> list = new ArrayList<Parent>();

    这个list有什么特点?
    下面谁会报错

    list.add(new Child())
    list.add(new Parent())
    Parent a= list.get();
    Child b = list.get()

    A:截图如下:

    • Child c = list.get() 或者Parent p = list.get()所做的操作是在返回时, 把内部的<? super Child> 强转成外部的Parent或者child, 是不合理的, 因为编译器觉得child的父类 不一定 能转成parent或者child,所以禁止了这种行为( 比如parent的父类是object, 但object不一定就能转成parent或者child)。*list.add(new Child())所做的操作是在输入时, 把外部的child或者parent转成内部的<? super Child>, 这是合理的,因为child和parent一定能转成child的父类。

    Q:

    List<?> list = new ArrayList<A>();

    这个list有什么特点?

    A:get和add都不行,只能做remove等无返回值无输入A的操作。
    PS: 注意,不是说不能调用get或add方法, 而是调用get或add时,不能使用A这个对象去操作。
    即无法做add(A) 或者 A a = get(0)
    但是可以做add(object) 或者Object o = get(0)
    因为?可以转为Object, 但是无法转为A。

    Q:下面这个代码会报错吗?

       List<Fruit> fruitList = new ArrayList<>();
       fruitList.add(new Fruit());
       List<Apple> appleList = new ArrayList<>();
       appleList.add(new Apple());
       fruitList.addAll(appleList);
       System.out.println(fruitList);

    A:不会报错。会正常打印结果。

    PECS原则
    注意PECS原则和上面的区别!
    上面之前提到的? extend或者? supert, 都是在声明对象的时候用的。
    而PECS原则是用于泛型对象的方法输入参数!

    假设有一个类定义如下:

    public static class MyList<T> {
        List<T> list = new ArrayList<>();
    
        // 把输入参数塞给自己,类似于生产操作
        public void pushList(List<T> t) {
            list.addAll(t);
        }
    
        // 把自己的内容塞给输入参数,类似于让输入参数做消费。
        public void pollList(List<T> t) {
             t.addAll(list);
        }
    }

    则T就是泛型参数。

    Q:下面代码能正常运行吗?

    MyList<Number> myList = new MyList<>();
    
    List<Integer> intList = new ArrayList<>();
    myList.pushList(intList);
    
    List<Object> objectList = new ArrayList<>();
    myList.pollList(objectList);

    A:不能正常运行, pushList和pollList都会报错

    因为编译器检查后,认为 List<Integer>和List<Number>不是一个东西!

    Q: 如果上文要支持pushList,应该怎么修改pushList方法的定义?
    A:改成这样:

    // 把输入参数塞给自己,类似于生产操作
    public void pushList(List<? extends T> t) {
        list.addAll(t);
    }

    即编译器认为,List<Integer> 和List<? extend Number>是一个东西,允许!

    Q: 如果要支持pollList,怎么修改定义?
    A:

    // 把自己的内容塞给输入参数,类似于让输入参数做消费。
    public void pollList(List<? super T> t) {
        t.addAll(list);
    }

    因为是把自己的东西塞给输入参数, 而想要能塞进去,必须保证自己这个T,是输入参数的子类,反过来说,输入参数必须是T的父类,所以用super
    于是编译器认为,List<Object> 和List<? super Number>是一个东西,允许!

    PECS原则出自Effective Java, 注意只是一个编程建议而已!

    • 如果有一个类A,泛型参数为T
    • 如果他一般只用于接收输入容器List后,塞入自己内部的T容器, 则类A就叫生产者, 因此输入参数最好定义为<? extend T>最好, 以便能接收任何T子类的容器。
    • 如果他一般只用于接收输入容器后List, 把自己内部的T元素塞给它, 那么这个类A就叫消费者, 输入参数最好定义为<? super T>\ 最好, 以便自己的T元素能塞给任何T元素的父类容器。

     本文分享自华为云社区《15个问题掌握java泛型》,原文作者:breakDraw 。

    点击关注,第一时间了解华为云新鲜技术~

    bk
    下一篇:没有了