第九课时:泛型

news/2024/7/10 15:39:50 标签: Apple, C, C++, C#, JDK

    一、定义泛型接口、类

    JDK 1.5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

    下面是 JDK 1.5 改写后 List 接口、Iterator 接口、Map 的代码片段:

    // 定义接口时制定了一个类型形参,该形参名为 E

    public interface List<E>

    {

        // 在该接口里,E 可作为类型使用

        // 下面方法可以使用 E 作为参数类型

        void add(E x);

        Iterator<E> iterator();

    }

    // 定义接口时制定了一个类型形参,该形参名为 E

    public interface Iterator<E>

    {

        // 在该接口里 E 完全可以作为类型使用

        E next();

        bollean hasNext();

    }

    // 定义接口时制定了两个类型形参,该形参名为 K、V

    public interface Map<K, V>

    {

        // 在该接口里 K, V 完全可以作为类型使用

        Set<K> keySet();

        V put(K key, V value);

    }

    我们可以为任何类增加泛型的声明(并不是只有集合类才可以使用泛型声明,虽然泛型是集合类的重要使用场所)。例:

    public class Apple<T>

    {

        // 使用 T 类型形参定义属性

        private T info;

        public Apple(){}

        // 下面方法中使用 T 类型参数来定义方法

        public Apple(T info)

        {

            this.info = info;

        }

        public void setInfo(T info)

        {

            this.info = info;

        }

        public T getInfo()

        {

            return this.info;

        }

    }

            实例化带有泛型的 Apple 类:

           

             二、从泛型类派生子类

             当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或者从该父类来派生子类,但是,当使用这些接口、父类时不能再包含类型形参。下面的代码是错误的:

             // 定义类 A 继承 Apple 类,Apple 类不能跟类型参数

             public class A extends Apple<T>{}

             如果想从 Apple 类派生一个子类,可以改为如下方法:

             // 使用 Apple 类时,为 T 形参传入 String 类型

             public class A extends Apple<String>

             当然也可以不为接口、类传入的类型参数传入实际类型,所以下面的代码也是正确的:

             public class A extends Apple

             如果从 Apple<String> 类派生子类,则在 Apple 类中所使用 T 类型形参的地方都将被替换成 String类型,即它的子类将会集成到 String getInfo() 和 void setInfo(String info) 两个方法,如果子类需要重新写父类的方法,必须注意到这一点。例如:

             public class A1 extends Apple<String>

             {

                      // 正确重写了父类的方法,返回值与父类 Apple<String> 的返回值完全相同

                      public String getInfo()

                      {

                               return "子类" + super.getInfo();

                      }

                      /*

                      // 下面方法是错误的,重写父类方法时返回值类型不一致

                      public Object getInfo()

                      {

                               return "子类";

                      }

                      */

             }

             三、并不存在泛型类

             我们可以把 ArrayList<String> 类当做 ArrayList 的子类,而事实上系统并没有为 ArrayList<String> 生成新的 class 文件,而且也不会把 ArrayList<String> 当成新类来处理。

             例如:下面程序的打印结果是true

             List<String> l1 = new ArrayList<String>();

             List<Integer> l2 = new ArrayList<Integer>();

             System.out.println(l1.getClass() == l2.getClass());

             静态方法、静态初始化或者静态变量的声明和初始化中不允许使用类型参数。

             下面程序演示了这种错误:

             public class R<T>

             {

                      // 下面程序代码错误,不能在静态属性声明中使用类型参数

                      static T info;

                      T age;

                      public void foo(T msg){}

                      // 下面代码错误,不能在静态方法声明中使用类型形参

                      public static void bar(T msg){}

             }

             由于系统中并不会真正生成泛型类,所以 instanceof 运算符后不能使用泛型类,例如下面的代码是错误的:

             Collection cs = new ArrayList<String>();

             // 下面代码编译时引发错误:instanceof 运算符后不能使用泛型类

             if(cs instanceof List<String>){...}

 

             四、类型通配符

             如果 SubClass 是 SuperClass 的子类型(子类或者子接口),而 G 是具有泛型声明的类或者接口,那么 G<SubClass> 是 G<SuperClass> 的子类型并不成立。例如:List<String> 并不是 List<Object> 的子类。

             与数组进行对比:

             // 下面程序编译正常、运行正常

             Number[] nums = new Integer[7];
             nums[0] = 9;
             System.out.println(nums[0]);

             // 面程序编译正常、运行时发生 java.lang.ArrayStoreException 异常
             Integer[] ints = new Integer[5];
             Number[] nums2 = ints;
             nums2[0] = 0.4;
             System.out.println(nums2[0]);

             // 下面程序发生编译异常,Type mismatch: cannot convert from List<Integer> to List<Number>
             List<Integer> iList = new ArrayList<Integer>();
             List<Number> nList = iList;

 

             数组和泛型有所不同,如果 SubClass 是 SuperClass 的子类型(子类或者子接口),那么 SubClass[] 依然是 SuperClass[] 的子类;但是 G<SubClass> 不是 G<SuperClass> 的子类。

             如何适用类型通配符:

             为了表示各种泛型 List 的父类,我们需要使用类型通配符,类型通配符是一个问号 (?),将一个问号作为类型实参传给 List 集合,写作:List<?> (意思是未知类型元素的 List)。这个问号 (?) 被称作通配符,它的元素类型可以匹配任何类型。例如:

             public void test(List<?> c)

             {

                      ...

             }

             现在我们可以使用任何类型的 List 来调用它,程序依然可以访问集合 c 中的元素,其类型是 Object。

             这种写法适用于任何支持泛型声明的接口和类,例如:Set<?>、Collection<?>、Map<?, ?>等。

             但是这种带通配符的 List 仅表示它是各种泛型 List 的父类,并不能把元素加入到其中,例如下面的代码将引发编译错误:

             List<?> c = new ArrayList<String>();

             // 下面程序引发编译错误

             c.add(new Object());

             因为我们不知道上面程序中 c 集合中的元素类型,所以不能向其中添加对象。唯一的例外是 null,它是所有引用类型的实例。例如:下面程序是正确的:

             c.add(null);

 

             五、设置类型通配符的上限

             当直接使用 List<?> 这种形式时,即表明这个 List 集合是任何泛型 List 的父类。但还有一种特殊的情况,我们不想这个 List<?> 是任何泛型 List 的父类,只想表示它是某一类泛型 List 的父类。

             被限制的泛型通配符如下表示:

             List<? extends SuperClass>

             六、设定类型形参的上限

             Java 泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型必须是上限类型,或是该上限类型的子类。例如:

             
             import java.util.*;

             public class Apple<T extends Number>
             {
                       T col;
 
                        public static void main(String[] args)
                        {
                                   Apple<Integer> ai = new Apple<Integer>();
                                   Apple<Double> ad = new Apple<Double>();
                                   //下面代码将引起编译异常
                                   //因为String类型传给T形参,但String不是Number的子类型。
                                   Apple<String> as = new Apple<String>();
                        }
            }

 

            有时候程序需要为类型形参设定多个上限(至多有一个父类上限,可以有多个接口上限)表明该类型形参必须是其父类的子类(包括是父类本身也行),并且实现多个上限接口。例如:

             // 表明 T 类型必须是 Number 类或其子类,并必须实现 java.io.Serializable 接口

             public class Apple<T extends Number & java.io.Serializable>

             {

                          ...

             }

 

             七、泛型方法

             1、定义泛型方法

             泛型方法的用法格式是:

             修饰符 <T, S> 返回值类型 方法名(形参列表)
             {

                         // 方法体……

             }

             示例:

             static void fromArrayToCollection(Object[] a, Collection<Object> c)

             {

                         for(Object o : a)

                         {

                                    c.add(o);

                         }

             }

             上面的方法中形参 c 的数据类型是 Collection<Object>,因为 Collection<Object> 不是 Collection<String> 类的父类,所以这个方法的功能非常有限,它只能将 Object 数组的元素复制到 Object (Object 的子类不行) Collection 集合,及下面的代码会引发编译异常:

             String[] str = {"a", "b"};

             List<String> strList = new ArrayList<String>();

             // Collection<String> 对象不能当成 Collection<Object> 调用,下面的代码出现编译异常

             fromArrayGToCollection(str, strList);

             上面方法的参数类型不可以使用 Collection<String>,那是用通配符 Collection<?> 也是不可行的,因为不能把对象放进一个未知类型的集合当中去。

             使用泛型方法解决这个问题:

             static <T> void fromArrayToCollection(T[] a, Collection<T> c)

             {

                      for (T o : a)

                      {

                                  c.add(o);

                      }

             }

             public static void main(String[] args)
             {
                      Object[] oa = new Object[100];
                      Collection<Object> co = new ArrayList<Object>();
                      //下面代码中T代表Object类型
                      fromArrayToCollection(oa, co);
                      String[] sa = new String[100];
                      Collection<String> cs = new ArrayList<String>();
                      //下面代码中T代表String类型
                      fromArrayToCollection(sa, cs);
                      //下面代码中T代表Object类型
                      fromArrayToCollection(sa, co);
                      Integer[] ia = new Integer[100];
                      Float[] fa = new Float[100];
                      Number[] na = new Number[100];
                      Collection<Number> cn = new ArrayList<Number>();
                      //下面代码中T代表Number类型
                      fromArrayToCollection(ia, cn);
                      //下面代码中T代表Number类型
                      fromArrayToCollection(fa, cn);
                      //下面代码中T代表Number类型
                      fromArrayToCollection(na, cn);
                      //下面代码中T代表String类型
                      fromArrayToCollection(na, co);
                      //下面代码中T代表String类型,但na是一个Number数组,
                      //因为Number既不是String类型,也不是它的子类,所以出现编译错误
                      fromArrayToCollection(na, cs);
           }

           上面程序定义了一个泛型方法,该泛型方法中定义了一个 T 类型形参,这个 T 类型形参就可以在该房内当成普通类型来使用。与在接口、类中定义的类型形参不同的是,方法声明中定义的类型形参只能在方法里使用,而接口、类声明中定义的类型形参则可以住在整个接口、类中使用。

           与类、接口中使用泛型参数不同的是,方法中的泛型参数无需显式传入实际类型参数,如上面程序中,当程序调用 fromArrayToCollection 时,无须在调用该方法前传入 String、Object 等类型,编译器可以根据实参推断出类型形参的值。

          但是不要是编译器迷惑,例如下面的程序:

          public class Test

          {

                    // 声明一个泛型方法,该泛型方法中带一个 T 类型参数

                    static <T> void test(Collection<T> a, Collection<T> c)

                    {

                                // 方法体

                    }

                    public static void main(String[] args)

                    {

                               List<Object> ao = new ArrayList<Object>();

                               List<String>  as = new ArrayList<String>();

 

                               // 下面代码将产生编译错误

                               test(as, ao);

                    }

          }

          上面程序中,编译器无法正确识别 T 所代表的实际类型。可以将该方法修改为下面的形式:

          public class Test

          {

                    // 声明一个泛型方法,该泛型方法中带一个 T 类型参数

                    static <T> void test(Collection<? extends T> a, Collection<T> c)

                    {

                                // 方法体

                    }

                    public static void main(String[] args)

                    {

                               List<Object> ao = new ArrayList<Object>();

                               List<String>  as = new ArrayList<String>();

 

                               // 下面代码编译正常

                               test(as, ao);

                    }

          }

          上面代码中将方法的第一个形参类型修改为 Collection<? extends T>,这种采用类型通配符的表示方法,只要 test 方法的前一个 Collection 集合元素类型是后一个 Collection 集合元素类型的子类即可。

          2、泛型方法和类型通配符的区别

          JDK 中对于 Collection 接口中两个方法的定义:

          public interface Collection<E>

          {

                    boolean containsAll(Collection<?> c);

                    boolean addAll(Collection<? extends E> c);

          }

          上面两个方法都采用了类型通配符的形式,如果采用泛型方法形式来代替它们,如下所示:

          public interface Collection<E>

          {

                    boolean <T> containsAll(Collection<T> c);

                    boolean <T extends E> addAll(Collection<T> c);

          }

          上面方法使用了 <T extends E> 泛型形式,这是定义类型形参时设定上限。

          3、设定通配符的下限

          Java 允许设置通配符下限:<? super Type>,这个通配符表示它必须是 Type 本身,或者是 Type 的父类。示例:

          public class MyUtils

          {

                     // 下面 dest 集合元素类型必须与 src 集合元素类型相同,或是其父类

                     public static <T> copy(Collection<? super T> dest, Collection<T> src)

                     {

                                 T last = null;

                                 for(T ele : src)

                                 {

                                             last = ele;

                                             dest.add(ele);

                                 }

                                 return last;

                     }

                     public static void main(String[] args)

                     {

                                 List<Number> ln = new ArrayList<Number>();

                                 List<Integer> ln = new ArrayList<Integer>();

                                 li.add(5);

                                 // 此处可准确的知道最后一个被复制的元素是 Integer 类型(与 src 集合元素的类型相同)

                                 Integer last = copy(ln, li);

                                 System.out.println(ln);

                     }

          }


http://www.niftyadmin.cn/n/1039917.html

相关文章

Android总结篇系列:Android Service

Android总结篇系列&#xff1a;Android Service http://www.cnblogs.com/lwbqqyumidi/p/4181185.html Service通常总是称之为“后台服务”&#xff0c;其中“后台”一词是相对于前台而言的&#xff0c;具体是指其本身的运行并不依赖于用户可视的UI界面&#xff0c;因此&#…

JAVA开发过程中如何避免代码编程上带来的性能问题?

在程序开发过程中怎么写代码才能避免性能上的问题&#xff0c;J2EE 开发的项目大部分都是大型项目&#xff0c;BS架构的因此在性能要求上比较严格&#xff01; 通过中间件参数调整&#xff0c;集群等方式可以解决部分性能问题&#xff0c;但是要真正的解决一个系统的性能问题还…

ACA和ACP认证哪个更超值

阿里云服务当下受到了众多企业用户的良好评价&#xff0c;因此能够获得阿里云相关考试认证&#xff0c;毕业生从业几率会明显提升。本年度由于疫情影响&#xff0c;众多IT领域企业举步维艰&#xff0c;因此在这一阶段毕业生想要获得就业机会&#xff0c;其难度会越来越高&#…

获得ACP认证就业薪资待遇解析

阿里云是目前国内多行业领域中首选的服务&#xff0c;因此当下阶段有很多希望从事it行业的用户将ACP作为关注重点之一&#xff0c;获得ACP认证之后&#xff0c;就业薪资是否让人满意呢&#xff1f;      获得ACP认证之后&#xff0c;进入对口企业&#xff0c;起步阶段从业者…

转载:J2EE架构和过程

架构是个听着神秘的东西&#xff0c;在一个知名博客上看到这篇文章&#xff0c;就让它来揭开架构的红盖头吧。 Java2企业版&#xff08;J2EE&#xff09;平台由四个关键部分构成&#xff1a;规格说明、参考实现、兼容性测试套件和蓝图&#xff08;BluePrint&#xff09;计划。蓝…

Android开发中常见的设计模式

Android开发中常见的设计模式 https://www.cnblogs.com/android-blogs/p/5530239.html 对于开发人员来说&#xff0c;设计模式有时候就是一道坎&#xff0c;但是设计模式又非常有用&#xff0c;过了这道坎&#xff0c;它可以让你水平提高一个档次。而在android开发中&#xf…

通信软件白盒测试的三种境界

通信软件被普遍认为是白盒测试最难实施的领域&#xff0c;一方面&#xff0c;通信软件以C语言为主体语言&#xff0c;先进的白盒测试技术尚未有效渗透到这个区域&#xff0c;另一方面&#xff0c;通信软件通常是嵌入式实时系统&#xff0c;搭建测试环境非常复杂&#xff0c;又加…