• Java泛型


    1 为什么使用泛型

    为什么使用泛型?首先,我们必须知道什么是泛型。泛型,简单来说,就是将类型参数化,即用一个参数来代表类型。比方说,在学习泛型之前,我们定义的变量都是指明了具体类型的,如String str定义了字符串类型的str,  Integre num;定义了整型的num等(包括Object类型)。那么学习了泛型之后,我们就可能会定义类似T a;这种不指明具体类型的变量。a的类型随着传入参数的不同而变化。

    比方说Java集合类List<E>就是一个泛型类(准确说是接口),里面不仅可以装Integer类型的数据,也可以装String类型的数据,以及用户自定义的对象等等。

    因此,编写泛型程序意味着:1.我们的代码可以被不同类型的对象所重用;2.比随意地使用Object变量具有更好的安全性(比如避免java.lang.ClassCastException)和可读性(消除源代码中的许多强制类型转换,所有的强制转换都是自动和隐式的);3.性能较高,泛型代码可以为java编译器和虚拟机带来更多的类型信息,这些信息对java程序做进一步优化提供条件。

    2 泛型类

    一个泛型类是具有一个或多个类型变量的类。类型变量使用大写形式。在Java库中,T(需要时还可以用临近的字面U和S)表示任意类型。在我们写泛型类时,用T、U等只是习惯用法,其它的A、B、C等都是可以的。

    比如,下面代码定义了具有一个类型变量T的泛型类。

    public class Computer<T> {
    private T data;

    public Computer() {
    }

    public Computer(T data) {
    this.data = data;
    }

    public T getData() {
    return data;
    }

    public void setData(T data) {
    this.data = data;
    }
    }

    下面代码定义了具有两个类型变量T和U的泛型类:
    public class Computer<T,U> {
    private T data;
    private U com;

    public Computer() {
    }

    public Computer(T data, U com) {
    this.data = data;
    this.com = com;
    }

    public T getData() {
    return data;
    }

    public void setData(T data) {
    this.data = data;
    }

    public U getCom() {
    return com;
    }

    public void setCom(U com) {
    this.com = com;
    }
    }
    类型变量用<>括起来,放在类名的后面。类型变量可以有一个或多个,如果有多个时,中间用逗号隔开。
    在实际的程序当中,类型参数用在什么地方呢?泛型类中的类型变量,主要用在三个地方,一是指定方法的返回类型,二是指定域的类型,三是指定局部变量的类型。
    指定方法的返回类型:比如前面程序里的getXxx方法。
    指定域的类型:比如前面程序变量的定义T xxx。
    指定局部变量的类型:比如前面程序setXxx和getXxx方法里的参数。

    用具体的类型替换类型变量就可以实例化泛型类型。
    我们仍用上面的Computer类来说明。
    Computer<String> com = new Computer<String>();
    Computer<String ,String> computer = new Computer<String ,String>("abc","def");
    或Computer<String ,String> computer = new Computer("asd","bcv"); //后面构造函数的<String ,String>可以不写。


    3 泛型方法
    泛型方法时一个带有类型参数的简单方法。泛型方法可以定义在普通类中,也可以定义在泛型类中。类型变量放在修饰符的后面,返回类型的前面。
    下面这个例子展示了如何在普通类中定义泛型方法:
    public class Phone {
    //定义泛型方法
    public static <T> String fun(T t){
    return t.toString();
    }

    public static void main(String[] args) {
    //当调用一个泛型方法时,在方法名前的<>中放入具体的类型
    String str1 = Phone.<Integer>fun(8);
    System.out.println(str1);
    //实际上大多数情况下,也是编译器推荐的方式,方法名前的<>是省略的,如下方式
    String str2 = Phone.fun("hello");
    System.out.println(str2);
    }
    }

    4 类型变量的限定
    像上面这样,我们会写一个泛型类、泛型方法,以及调用使用他们,基本在日常的开发中就没有什么问题了。当然,如果想要了解泛型的更多东西,还可以继续往下看。
    这里,我们说一下类型变量的限定。所谓类型变量的限定,就是给类型变量加上约束,比如类型参数必须实现某个接口,继承某个类等,而不是让类型参数可以任意取值。
    我们一泛型方法为例,比如:
    定义一个Father类
    public class Father {
    public void hunt(){
    System.out.println("I'm good at hunting");
    }
    }
    定义一个儿子类:
    public class Son {
    public static <T> void fun(T t){
    //下面这句编译器会报错
    t.hunt();
    }
    }
    如上,在Father类中,有一个hunt()方法。在Son类里,有一个泛型方法fun,并调用了hunt()方法。程序显然是错误的,因为我们不知道T是什么类型,它有没有hunt()方法,直接t.hunt()肯定不行,而且编译器也会直接给我们报错。
    可是,如果我们必须要让t具有hunt()函数,即函数fun(T t);必须以t。hunt()的方式调用hunt函数,那该怎么办呢?很简单,因为hunt()方法在Father类中定义,我们只要让传入的参数t是Father类型或其子类类型就可以了,也就是,我们传入的这个参数t,不能再是任意类型,必须让它继承Father类。
    所以,上述代码改成下面这样:
    public class Son {
      //<T>改为<T extends Father>
        public static  <T extends Father> void fun(T t){
    t.hunt();
    }
    }
    这样,我们就可以知道,如果你想调用Son里的fun(T t);函数,那么T必须extends Father,即传入的参数必须的Father及其子类型,不然没法调用。这样我们就不用担心t.hunt()的调用会出错了。
    一个类型变量可以有多个限定,例如:
    T必须同时继承(或实现)A、B 、C三个类(或接口):<T extends A & B & C>
    T必须同时继承(或实现)A、B 、C三个类(或接口),U必须同时继承(或实现)D、E两个类(或接口):<T extends A & B & C, U extends D & E>
    限定类型用 & 分隔,类型变量用逗号分隔。
    需要注意的是:
    (1)无论的类型变量需要继承某个父类还是实现某个接口,统统用关键字extends,而不能用implements。
    (2)类型参数可以指定多个限定接口,但只能指定一个限定类,如果有限定类,限定类必须放在限定列表的第一个。比方说,Father和Animal类是我们自定义的两个类,Comparable和List是Java自带的接口。
    <T extends Father & List & Comparable>是可以的,完全没问题,
    <T extends Father & Animal>是不可以的,因为限定类只能有一个,不能是Father和Animal两个,
    <T extends List & Comparable & Father>是不可以的,因为List和Comparable是接口,Father是类,类必须放在限定列表的第一个。
    另外,
    <? extends T>表示包括T在内的任何T的子类,属于子类型限定。
    <? super T>表示包括T在内的任何T的父类,属于超类型限定。
    5 擦除
    参考文章 http://blog.csdn.net/lonelyroamer/article/details/7868820
    使用泛型的时候加上的类型参数,编译器在编译时会去掉,在生成的Java字节码中是不包含泛型中的类型信息的。这个过程就称为类型擦除
    因此,事实上,虚拟机并不知道泛型,无论是Computer<String>,还是Computer<Integer>,在JVM的眼里,统统是Computer。所有的泛型在编译阶段就已经被处理成了普通类和方法,即我们所说的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定类型的变量用Object)。
    比如我们写的第一个泛型类Computer<T>的原始类型如下:
    public class Computer {
      //因为T是一个无限定的类型变量,所以直接替换为Object
    private Object data;

    public Computer() {
    }

    public Computer(Object data) {
    this.data = data;
    }

    public Object getData() {
    return data;
    }

    public void setData(Object data) {
    this.data = data;
    }
    }
    结果就是普通的类,和引入泛型之前,我们使用Object一样。
    类型变量如果没有限定,就用Object替换,如果有限定类型,就替换为限定类型,如果限定类型有多个,就替换为第一个限定类型。
    其实,我们还可以手动验证一下类型擦除。
    为了方便,我直接使用Java自带的泛型类List,代码如下:
    public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<String>();
    ArrayList<Integer> integerList = new ArrayList<Integer>();
    System.out.println(stringList.getClass()==integerList.getClass());
    }
    运行这个main方法,控制台会打印true。这说明,JVM在运行的时候,类型变量已经擦除了,它所知道的只有List。
    我们还可以以另一种方式验证:
        public static void main(String[] args) {
    ArrayList<Integer> arrayList=new ArrayList<Integer>();
    arrayList.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer
    try {
    //通过反射获取类的运行时信息可以存储字符串
    arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "Hello!");
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    System.out.println(arrayList);
    }
    运行这个main函数,控制台会打印:[1, Hello!]
    简直震惊!我们竟然在一个整型数组列表ArrayList<Integer>里存进了一个字符串"Hello!"。不用惊讶,事实本该如此。因为类型擦除,类型变量被替换为Object,字符串"Hello!"自然可以存进去。






    禅语:一直以来我们找不到对的人,是因为我们不能改变错误的自己!
  • 相关阅读:
    JS实现继承的6种方式
    apply、call
    JS闭包
    javascript中的变量提升和函数提升
    判断设备
    c#对象的内存结构(学习笔记)
    快速排序发 继承构造方法的调用顺序
    .NetFrameWork介绍 枚举 结构复习 位运算(第三天的培训内容)
    摸底练习(培训第二天的内容)
    摸底练习
  • 原文地址:https://www.cnblogs.com/ttflove/p/7647566.html
Copyright © 2020-2023  润新知