• java 泛型


    1. 为什么使用泛型

    2. 泛型是什么

    3. 泛型方法

    4. 泛型接口

    5. 泛型类

    6. 泛型擦除

    7. 通配符

    8. 泛型经典实例

    ONE:为什么使用泛型

    好程序的一个指标就是通用性。java可以使用多态机制,将方法参数设置为基类,从而调用方法时可以接受该基类和其子类。由于单继承受限太多,可以将方法参数设为接口,正是所谓的面向接口编程,从而再次提高程序的通用性。

    通用性还有更好的做法就是让方法接收不具体的参数类型,也就是说参数不是一个具体的类或接口或常见类型,一份代码可以根据不同的需求,在实际使用中使用不同的类。具体而言就是将类型作为一个参数,使用的时候将这个参数用具体的类型来代替。这种需求就要依靠泛型机制来实现 ---- 编写更加通用的代码。

    为容器提供更加安全的机制,在没有泛型前,容器类,如ArrayList是接受任何一个对象的,因为任何对象放进来之后都会向上转型为Object,而在取出的时候在向下转型为期待的类型,如果接受的对象类型不一致,在向下转型的时候就会出现错误,这个原因是由于当初没有检查机制造成的,现在推出了泛型,同时增加了这种检测机制,从而避免了这种事情的发生 ---- 提供更加安全的机制。

    TWO : 泛型是什么

    泛化的类型,也就是使用于很多类型的类型,也就是参数化类型的概念,即一个类型是一个参数,在使用过程中用<>括住类型参数,如<T>,T就代表类型参数,然后使用T来泛指类型做各种事情。

    THREE : 泛型方法

    对一个方法应用泛型和对一个类应用泛型没什么区别。

      public static <T> void  methodTest (T t){
            System.out.println(t);
        }
        public static void main(String[] args) {
            methodTest("hello world");
            methodTest(1);
        }

    首先泛型方法都是先声明后使用,申明要在public static final 后边,同事必须在返回值前边,这样之后才能在方法中使用类型参数。

       public static <T> void getThing(T[] numbers){
          for (T t: numbers) {
               System.out.print(t + " ");
             }
       }

     泛型方法不需要像泛型类一样需要明确指定类型参数是什么,编译器会自动进行类型推断,

    FOUR : 泛型接口

    泛型接口的用法与泛型类的用法一样。

     public interface Testt<T>{

        T test();  

      }

    FIVE : 泛型类

    参数化类型,这里指的是将一个类中的参数参数化类型,即这个类中的成员变量不是一个具体的类型,在使用这个类的时候才指定他。例如我们经常用到的ArrayList<Student> students 就是在我们使用ArrayList的时候才将ArrayList中的成员变量类型指定为User。

    public class PageObject<T>{

      private T t;

    }

    在类名后边申明一个类型参数<T>,这个T在整个类中都可以使用,如果不申明一个类型参数,也可以使用<T,K,...,V>来申明多个。

    public class PageObject<T>{

      protected T t;

      public T getT(){

        return t;  

      }

      public void setT(T t){

        this.t = t;

      }

    }

    PageObject<Integer> pageInteer = new PageObject<Integer>();

    pageInteger.setT(1);

    SIX : 泛型擦除

    java泛型机制是采用擦除的形式来实现的。

    擦除的机制实现泛型是在编译器这个层次实现的。也就是说在生成字节码中是不包含泛型中的类型信息的,ArrayList<Integer> list,和ArrayList list这两个类,编译之后都会生成ArrayList.class这一个类文件。我们单单从类文件上是看不出这两个类的不同的,因为他们就是一个类文件。这就是擦除,擦除了我们使用泛型的证据。这样也导致了一些比较困难的事情,比如我们在运行期是没办法得到泛型的信息的。我们没法得知ArrayList<Integer> list中,list是Integer约束的。

    SEVEN : 通配符

     上述的几种泛型用法都是在声明泛型的时候的使用方法,而通配符是针对使用泛型的时候。

        public class Test {
        public static void main(String[] args) {
            List<A> OList = new ArrayList<B>()
        }

    这样写,编译器就会报错,应为泛型是没有继承的,即使B类是A类的子类,但是在泛型上List<A>和Lisb<B>然而并不等价。

    要实现这种向上转型,就需要通配符?

    public class Test {
        public static void main(String[] args) {
            List<?> OList = new ArrayList<B>()
        }
    }

    这里的?如果不加限制,默认指的任何类型,当然我们也可以给这个通配符添加限制。同样使用到extends
    代码改写后如下:
    public class Test {
        public static void main(String[] args) {
            List<? extends A> OList = new ArrayList<B>()
        }
    }

    这里extends 限定的意义是通配符的上界。表示类型参数可以是指定类型或者指定类型的子类。这样List<B> 就是List<?>的子类,就可以实现向上转型。这样是有弊端的。如果我们调用Olist的add方法,我们会发现我们无法往OList中添加任何一个值,即使这个对象是new B()得到,或者是new A()。这是因为编译器知道OList容器接受一个类型A的子类型,但是我们不知道这个子类型究竟是什么,所以为了保证类型安全,我们是不能往这个类型里面添加任何元素的,即使是A。从一个角度来看,我们知道这个容器里面放入的是A的子类型,也就是我们通过这个容器取出的数据都是可以调用A的方法的,得到的数据都是一个A类型的实例。下面这种写法是成功的。
    public class Test {
        public static void main(String[] args) {
            List<? extends B> OList;
            ArrayList<B> BList = new ArrayList<>();
            BList.add(new B());
            OList = BList;
            OList.get(0).f();
        }
    }
    使用通配符的容器则代表了一个确切的类型参数的容器。,我们可以把这个类型参数的容器用到定义方法上面。比如下面的例子。
    public class Test<E>{
        public static void main(String[] args) {
            List<? extends B> OList;
            ArrayList<B> BList = new ArrayList<>();
            BList.add(new B());
            OList = BList;

            new Test<A>().getFirstElement(OList).f();
        }
        public E getFirstElement(List<? extends E> list) {
            return list.get(0);
        }
    }
    在getFirstElement方法中,我们接受一个E类型或者其子类的容器,我们知道这个容器中取出的元素一定是E类型或者其子类。当我们给Test绑定泛型参数的时候,我们就可以根据泛型参数调用其相应的方法。
    上面的代码,如果我们用泛型方法也是可以做到的。其代码如下:
    public class Test<E>{
        public static void main(String[] args) {
            List<? extends B> OList;
            ArrayList<B> BList = new ArrayList<>();
            BList.add(new B());
            OList = BList;
            new Test<A>().getFirstElement(OList).f();
        }
        public <T extends E > E getFirstElement(List<T> list) {
            return list.get(0);
        }
    }

    这两者都是可以达到同样的效果。
    我们回到之前泛型通配符的问题上来,因为使用了通配符。我们无法向其中加入任何元素,只能读取元素。如果我们一定要加入元素,我们要怎么办?这个时候,需要使用通配符的 下界。? super T表示类型参数是T或者T的父类。这样,我们就可以向容器中加入元素。比如下面的代码:
    public class Test {
       public class Test{
        public static void main(String[] args) {
            List<? super A> OList = new ArrayList<>();
            OList.add(new B());
    }
    ? super A 表示类型是A或者A的子类。根据向上转型一定安全的原则,我们的类型参数为A的容器中加入其子类B一定是成功的,因为B可以向上转型成A,但是这个面临和上面一样的问题,我们无法加入任何A的父类,比如Object对象。
    然而在引用的时候,我们可以把OList引用到一个Object类型的ArrayList中。
    List<? super A> OList = new ArrayList<Object>();
    就和下面代码的引用会成功一样。
    List<? extends A> Olist = new ArrayList<A>();
    第一个通配符下界表明Olist是具有任何是A父类的列表。
    第二个通配符上界表明OList是具有任何是A子类的列表。
    我们把一个符合要求的列表引用给他是成功。但是他们在读取和写入数据是不同的。拥有下界的列表表明我们可以把A类或者子类的数据写入到这个列表中,但是我们无法调用任何A类的方法,因为编译器只知道返回的是A的父类或者A,但是具体是哪一个,他不知道,自然也无法调用除Object以外的任何方法。拥有上界的列表表明我们可以从列表中读取数据,因为数据一定是A类或者A的子类。但是由于无法确定具体是哪一个类型,我们自然也无法向其加入任何类型。
    如果我们要读取数据,则用拥有上界的通配符,如果我们要写入数据,则用拥有下界的通配符。既读又写则不要用通配符。

  • 相关阅读:
    FPGA实现USB2.0同步读数据传输且用chipscop抓取波形(3)
    FPGA实现对USB2.0的同步数据传输及USB2.0固件配置(2)
    通过MATLAB实现图像数据转换成.bin格式在USB2.0上传输(1)
    【转】ssh免密码登录的原理
    【转】ssh登录原理以及ssh免密码登陆
    Windows与VMware中的CentOS系统互通访问
    第十一章 条件逻辑
    第十章 再谈连接
    第九章 子查询
    第八章 分组和聚集
  • 原文地址:https://www.cnblogs.com/yangfei-beijing/p/5886242.html
Copyright © 2020-2023  润新知