`
holdbelief
  • 浏览: 704922 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

第九课时:泛型

阅读更多

    一、定义泛型接口、类

    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);

                     }

          }

  • 大小: 60 KB
分享到:
评论

相关推荐

    [北京圣思园Java培训教学视频]Java.SE.Lesson.4_code.rar

    【Java.SE.Lesson.4_code.rar】这个压缩包文件包含了北京圣思园Java培训课程的第四课时的源代码,主要关注的是Java Standard Edition(Java SE)的相关编程知识。Java SE是Java平台的核心,用于开发和运行桌面应用...

    [北京圣思园Java培训教学视频]Java.SE.Lesson.5_code.rar

    【Java.SE.Lesson.5_code.rar】这个压缩包文件显然包含了北京圣思园Java培训课程中的第五课时的源代码。这通常意味着我们将深入到Java编程语言的核心概念中,特别是那些在Java Standard Edition(Java SE)环境下的...

    Framework3_Aula02

    在这个特定的场景中,我们关注的是一个名为"Framework3_Aula02"的学习资源,这可能是某个课程或工作坊的第二课,主要涉及TypeScript语言的应用。 TypeScript是JavaScript的一个超集,它添加了静态类型系统、接口、...

    java初学者完整代码+注释3

    13. **泛型**:Java泛型提高了代码的类型安全性,允许在编译时检查类型。 14. **反射**:反射机制允许程序在运行时动态获取类的信息并操作类的对象。 15. **包装类**:Java为每种原始数据类型提供了对应的包装类,...

    AIC的Java课程1-6章

    第9章 常用类 4课时  理解Object类及其常用方法equals,hashCode和finalize等。  能够使用String,StringBuffer,StringBuilder类创建字符串对象和使用其方法,分辨不同类之间的区别。 ...

    .net编程系列课程(5)

    《.NET编程系列课程(5)》是一门深入探讨C#编程语言的在线讲座课程,总计30课,每节课时长达70至90分钟,确保学生能够充分理解和掌握每个主题。作为第五课,本课程继续深化了对C#语言的理解,包括实践演示DEMO和详细...

    精品C++视频教程下载地址汇总

    C++是一种静态类型的、编译式的、通用的、大小写敏感的、不仅支持过程化编程、数据抽象化、面向对象编程、泛型编程等多种编程范式的现代程序设计语言。由贝尔实验室的Bjarne Stroustrup于1979年开始设计并实现,并在...

    AULA22EST-TICOS

    【标题】"AULA22EST-TICOS" 指的可能是一场关于信息技术(TICs)的教育讲座或课程,其中"AULA"在西班牙语中是"课堂"的意思,而"22EST"可能是课程编号或者时间标记,表示第22课时。这个主题暗示我们将探讨与C#编程...

    JB_25_Lesson_09

    【Java编程二十五课:第九讲】 本课时主要聚焦于Java编程语言的深入学习,特别是针对Java中的核心概念和技术进行讲解。"JB_25_Lesson_09"可能涵盖了许多关键主题,包括但不限于类、对象、封装、继承、多态性等面向...

Global site tag (gtag.js) - Google Analytics