zoukankan      html  css  js  c++  java
  • 15个问题告诉你如何使用Java泛型

    摘要: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 。

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

  • 相关阅读:
    toj 2819 Travel
    toj 2807 Number Sort
    zoj 2818 Prairie dogs IV
    zoj 1276 Optimal Array Multiplication Sequence
    toj 2802 Tom's Game
    toj 2798 Farey Sequence
    toj 2815 Searching Problem
    toj 2806 Replace Words
    toj 2794 Bus
    css截取字符
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/14713346.html
Copyright © 2011-2022 走看看