精华内容
下载资源
问答
  • 本文参考java 泛型详解、Java中的泛型方法、 java泛型详解 1. 概述泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”。一...

    对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。

    本文参考java 泛型详解Java中的泛型方法、 java泛型详解

    1. 概述

    泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

    什么是泛型?为什么要使用泛型?

    泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

    泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

    2. 一个栗子

    一个被举了无数次的例子:

    List arrayList = new ArrayList();
    arrayList.add("aaaa");
    arrayList.add(100);
    
    for(int i = 0; i< arrayList.size();i++){
        String item = (String)arrayList.get(i);
        Log.d("泛型测试","item = " + item);
    }

    毫无疑问,程序的运行结果会以崩溃结束:

    java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

    ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

    我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

    List<String> arrayList = new ArrayList<String>();
    ...
    //arrayList.add(100); 在编译阶段,编译器就会报错

    3. 特性

    泛型只在编译阶段有效。看下面的代码:

    List<String> stringArrayList = new ArrayList<String>();
    List<Integer> integerArrayList = new ArrayList<Integer>();
    
    Class classStringArrayList = stringArrayList.getClass();
    Class classIntegerArrayList = integerArrayList.getClass();
    
    if(classStringArrayList.equals(classIntegerArrayList)){
        Log.d("泛型测试","类型相同");
    }

    输出结果:D/泛型测试: 类型相同

    通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

    对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

    4. 泛型的使用

    泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

    4.3 泛型类

    泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

    泛型类的最基本写法(这么看可能会有点晕,会在下面的例子中详解):

    class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
      private 泛型标识 /*(成员变量类型)*/ var; 
      .....
    
      }
    }

    一个最普通的泛型类:

    //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
    //在实例化泛型类时,必须指定T的具体类型
    public class Generic<T>{ 
        //key这个成员变量的类型为T,T的类型由外部指定  
        private T key;
    
        public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
            this.key = key;
        }
    
        public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
            return key;
        }
    }
    //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
    //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
    Generic<Integer> genericInteger = new Generic<Integer>(123456);
    
    //传入的实参类型需与泛型的类型参数类型相同,即为String.
    Generic<String> genericString = new Generic<String>("key_vlaue");
    Log.d("泛型测试","key is " + genericInteger.getKey());
    Log.d("泛型测试","key is " + genericString.getKey());
    12-27 09:20:04.432 13063-13063/? D/泛型测试: key is 123456
    12-27 09:20:04.432 13063-13063/? D/泛型测试: key is key_vlaue

    定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

    看一个例子:

    Generic generic = new Generic("111111");
    Generic generic1 = new Generic(4444);
    Generic generic2 = new Generic(55.55);
    Generic generic3 = new Generic(false);
    
    Log.d("泛型测试","key is " + generic.getKey());
    Log.d("泛型测试","key is " + generic1.getKey());
    Log.d("泛型测试","key is " + generic2.getKey());
    Log.d("泛型测试","key is " + generic3.getKey());
    D/泛型测试: key is 111111
    D/泛型测试: key is 4444
    D/泛型测试: key is 55.55
    D/泛型测试: key is false

    注意:

      1. 泛型的类型参数只能是类类型,不能是简单类型。
      1. 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

        if(ex_num instanceof Generic<Number>){   
        } 

    4.4 泛型接口

    泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

    //定义一个泛型接口
    public interface Generator<T> {
        public T next();
    }

    当实现泛型接口的类,未传入泛型实参时:

    /**
     * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
     * 即:class FruitGenerator<T> implements Generator<T>{
     * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
     */
    class FruitGenerator<T> implements Generator<T>{
        @Override
        public T next() {
            return null;
        }
    }

    当实现泛型接口的类,传入泛型实参时:

    /**
     * 传入泛型实参时:
     * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
     * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
     * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
     * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
     */
    public class FruitGenerator implements Generator<String> {
    
        private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
    
        @Override
        public String next() {
            Random rand = new Random();
            return fruits[rand.nextInt(3)];
        }
    }

    4.5 泛型通配符

    我们知道IngeterNumber的一个子类,同时在特性章节中我们也验证过Generic<Ingeter>Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实例传入呢?在逻辑上类似于Generic<Number>Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?

    为了弄清楚这个问题,我们使用Generic<T>这个泛型类继续看下面的例子:

    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }
    Generic<Integer> gInteger = new Generic<Integer>(123);
    Generic<Number> gNumber = new Generic<Number>(456);
    
    showKeyValue(gNumber);
    
    // showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer> 
    // cannot be applied to Generic<java.lang.Number>
    // showKeyValue(gInteger);

    通过提示信息我们可以看到Generic<Integer>不能被看作为`Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

    回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时Generic<Integer>Generic<Number>父类的引用类型。由此类型通配符应运而生。

    我们可以将上面的方法改一下:

    public void showKeyValue1(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。

    可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

    4.6 泛型方法

    在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

    尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

    泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型

    /**
     * 泛型方法的基本介绍
     * @param tClass 传入的泛型实参
     * @return T 返回值为T类型
     * 说明:
     *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
     *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
      IllegalAccessException{
            T instance = tClass.newInstance();
            return instance;
    }
    Object obj = genericMethod(Class.forName("com.test.test"));

    4.6.1 泛型方法的基本用法

    光看上面的例子有的同学可能依然会非常迷糊,我们再通过一个例子,把我泛型方法再总结一下。

    public class GenericTest {
       //这个类是个泛型类,在上面已经介绍过
       public class Generic<T>{     
            private T key;
    
            public Generic(T key) {
                this.key = key;
            }
    
            //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
            //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
            //所以在这个方法中才可以继续使用 T 这个泛型。
            public T getKey(){
                return key;
            }
    
            /**
             * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
             * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
            public E setKey(E key){
                 this.key = keu
            }
            */
        }
    
        /** 
         * 这才是一个真正的泛型方法。
         * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
         * 这个T可以出现在这个泛型方法的任意位置.
         * 泛型的数量也可以为任意多个 
         *    如:public <T,K> K showKeyName(Generic<T> container){
         *        ...
         *        }
         */
        public <T> T showKeyName(Generic<T> container){
            System.out.println("container key :" + container.getKey());
            //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
            T test = container.getKey();
            return test;
        }
    
        //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
        public void showKeyValue1(Generic<Number> obj){
            Log.d("泛型测试","key value is " + obj.getKey());
        }
    
        //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
        //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
        public void showKeyValue2(Generic<?> obj){
            Log.d("泛型测试","key value is " + obj.getKey());
        }
    
         /**
         * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
         * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
         * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
        public <T> T showKeyName(Generic<E> container){
            ...
        }  
        */
    
        /**
         * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
         * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
         * 所以这也不是一个正确的泛型方法声明。
        public void showkey(T genericObj){
    
        }
        */
    
        public static void main(String[] args) {
    
    
        }
    }

    4.6.2 类中的泛型方法

    当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下

    public class GenericFruit {
        class Fruit{
            @Override
            public String toString() {
                return "fruit";
            }
        }
    
        class Apple extends Fruit{
            @Override
            public String toString() {
                return "apple";
            }
        }
    
        class Person{
            @Override
            public String toString() {
                return "Person";
            }
        }
    
        class GenerateTest<T>{
            public void show_1(T t){
                System.out.println(t.toString());
            }
    
            //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
            //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
            public <E> void show_3(E t){
                System.out.println(t.toString());
            }
    
            //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
            public <T> void show_2(T t){
                System.out.println(t.toString());
            }
        }
    
        public static void main(String[] args) {
            Apple apple = new Apple();
            Person person = new Person();
    
            GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
            //apple是Fruit的子类,所以这里可以
            generateTest.show_1(apple);
            //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
            //generateTest.show_1(person);
    
            //使用这两个方法都可以成功
            generateTest.show_2(apple);
            generateTest.show_2(person);
    
            //使用这两个方法也都可以成功
            generateTest.show_3(apple);
            generateTest.show_3(person);
        }
    }

    4.6.3 泛型方法与可变参数

    再看一个泛型方法和可变参数的例子:

    public <T> void printMsg( T... args){
        for(T t : args){
            Log.d("泛型测试","t is " + t);
        }
    }
    printMsg("111",222,"aaaa","2323.4",55.55);

    4.6.4 静态方法与泛型

    静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

    即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

    public class StaticGenerator<T> {
        ....
        ....
        /**
         * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
         * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
         * 如:public static void show(T t){..},此时编译器会提示错误信息:
              "StaticGenerator cannot be refrenced from static context"
         */
        public static <T> void show(T t){
    
        }
    }

    4.6.5 泛型方法总结

    泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

    无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

    4.6 泛型上下边界

    在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

    • 为泛型添加上边界,即传入的类型实参必须是指定类型的子类型

    public void showKeyValue1(Generic<? extends Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }
    Generic<String> generic1 = new Generic<String>("11111");
    Generic<Integer> generic2 = new Generic<Integer>(2222);
    Generic<Float> generic3 = new Generic<Float>(2.4f);
    Generic<Double> generic4 = new Generic<Double>(2.56);
    
    //这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
    //showKeyValue1(generic1);
    
    showKeyValue1(generic2);
    showKeyValue1(generic3);
    showKeyValue1(generic4);

    如果我们把泛型类的定义也改一下:

    public class Generic<T extends Number>{
        private T key;
    
        public Generic(T key) {
            this.key = key;
        }
    
        public T getKey(){
            return key;
        }
    }
    //这一行代码也会报错,因为String不是Number的子类
    Generic<String> generic1 = new Generic<String>("11111");

    再来一个泛型方法的例子:

    //在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
    //public <T> T showKeyName(Generic<T extends Number> container),编译器会报错:"Unexpected bound"
    public <T extends Number> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        T test = container.getKey();
        return test;
    }

    通过上面的两个例子可以看出:泛型的上下边界添加,必须与泛型的声明在一起

    4.7 关于泛型数组要提一下

    看到了很多文章中都会提起泛型数组,经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。

    也就是说下面的这个例子是不可以的:

    List<String>[] ls = new ArrayList<String>[10];  

    而使用通配符创建泛型数组是可以的,如下面这个例子:

    List<?>[] ls = new ArrayList<?>[10];  

    这样也是可以的:

    List<String>[] ls = new ArrayList[10];

    下面使用Sun的一篇文档的一个例子来说明这个问题:

    List<String>[] lsa = new List<String>[10]; // Not really allowed.    
    Object o = lsa;    
    Object[] oa = (Object[]) o;    
    List<Integer> li = new ArrayList<Integer>();    
    li.add(new Integer(3));    
    oa[1] = li; // Unsound, but passes run time store check    
    String s = lsa[1].get(0); // Run-time error: ClassCastException.

    这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。

    而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。

    下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。

    List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
    Object o = lsa;    
    Object[] oa = (Object[]) o;    
    List<Integer> li = new ArrayList<Integer>();    
    li.add(new Integer(3));    
    oa[1] = li; // Correct.    
    Integer i = (Integer) lsa[1].get(0); // OK 

    5. 最后

    本文中的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性。另外,一提到泛型,相信大家用到最多的就是在集合中,其实,在实际的编程过程中,自己可以使用泛型去简化开发,且能很好的保证代码质量。

    展开全文
  • java泛型详解

    万次阅读 多人点赞 2016-04-29 01:02:17
    1、什么是java泛型? 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 2、...

    1、什么是java泛型?

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

    2、为什么需要泛型?

    Java语言引入泛型的好处是安全简单。可以将运行时错误提前到编译时错误

    Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的任意化任意化带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

    示例代码如下:

    package Generics;
    
    class SimpleGen {
    	private Object ob;
    
    	public SimpleGen(Object ob) {
    		this.ob = ob;
    	}
    
    	public Object getOb() {
    		return ob;
    	}
    
    	public void setOb(Object ob) {
    		this.ob = ob;
    	}
    
    	public void showType() {
    		System.out.println(ob.getClass().getName());
    	}
    }
    
    public class SimpleGenDemo1 {
    	public static void main(String[] args) {
    		SimpleGen sg = new SimpleGen(new Integer(99));
    		sg.showType();
    		int i = (Integer) sg.getOb(); //强制类型转换,系统可能会抛一个ClassCastException异常信息
    		System.out.println("value = " + i);
    		SimpleGen sg2 = new SimpleGen("掌上洪城");
    		sg2.showType();               //强制类型转换,系统可能会抛一个ClassCastException异常信息
    		String str = (String) sg2.getOb();
    		System.out.println("value = " + str);
    	}
    }
    /* 输出结果为:
    java.lang.Integer
    value = 99
    java.lang.String
    value = 掌上洪城
    */

    3、什么是元组类库,怎么用?

    3.1、为什么使用元组tuple?

    元组和列表list一样,都可能用于数据存储,包含多个数据;但是和列表不同的是:列表只能存储相同的数据类型,而元组不一样,它可以存储不同的数据类型,比如同时存储int、string、list等,并且可以根据需求无限扩展。

    比如说在web应用中,经常会遇到一个问题就是数据分页问题,查询分页需要包含几点信息:当前页数、页大小;查询结果返回数据为:当前页的数据记录,但是如果需要在前台显示当前页、页大小、总页数等信息的时候,就必须有另外一个信息就是:数据记录总数,然后根据上面的信息进行计算得到总页数等信息。这个时候查询某一页信息的时候需要返回两个数据类型,一个是list(当前也的数据记录),一个是int(记录总数)。当然,完全可以在两个方法、两次数据库连接中得到这两个值。事实上在查询list的时候,已经通过sql查询得到总计录数,如果再开一个方法,再做一次数据库连接来查询总计录数,不免有点多此一举、浪费时间、浪费代码、浪费生命。言重了~在这种情况下,我们就可以利用二元组,在一次数据库连接中,得到总计录数、当前页记录,并存储到其中,简单明了!

    3.2、代码实例

    package Generics;
    
    import java.util.Date;
    
    class TwoTuple<A,B>{
    	public final A first;
    	public final B second;
    
    	public TwoTuple(A a,B b){ //这里是括号,不是中括号
    		first = a;
    		second = b;
    	}
    	public String toString(){
    		return "(" + first + "," + second + ")";
    	}
    }
    
    class ThreeTuple<A,B,C> extends TwoTuple<A,B>{
    	private final C three;
    	public ThreeTuple(A a,B b,C c){
    		super(a,b);
    		three = c;
    	}
    	public String toString(){
    		return "(" + first + "," + second + "," + three + ")";
    	}
    }
    public class TupleTest {
    	public static void main(String[] args) {
    		TwoTuple<Integer,String> twoT = new TwoTuple<Integer,String>(99,"掌上洪城");
    		System.out.println(twoT);
    		System.out.println("======扩展元组类库后======");
    		ThreeTuple<Integer,String,Date> threeT= new ThreeTuple<Integer,String,Date>(99,"掌上洪城",new Date());
    		System.out.println(threeT);
    	}
    }
    /*输出结果为:
     * (99,掌上洪城)
    ======扩展元组类库后======
    (99,掌上洪城,Thu Apr 28 17:59:30 CST 2016)
     * */

    4、怎么自定义泛型接口、泛型类?

    4.1 java泛型接口、泛型类简介

    泛型类中的类型参数几乎可以用于任何可以使用接口名、类名的地方,下面的代码示例展示了 JDK 5.0 中集合框架中的 Map 接口的定义的一部分:

    public interface Map<K, V> {

    public void put(K key, V value);

    public V get(K key);

    }

    当声明或者实例化一个泛型的对象时,必须指定类型参数的值:

    Map<String, String> map = newHashMap<String, String>();

    对于常见的泛型模式,推荐的名称是:

     

    K ——键,比如映射的键。

    V ——值,比如 List 和 Set 的内容,或者 Map 中的值。

    E ——异常类。

    T ——泛型。

    泛型不是协变的

     

    关于泛型的混淆,一个常见的来源就是假设它们像数组一样是协变的。其实它们不是协变的。List<Object> 不是 List<String> 的父类型。

     

    如果 A 扩展 B,那么 A 的数组也是 B 的数组,并且完全可以在需要 B[] 的地方使用 A[]:

     

    Integer[] intArray = new Integer[10];

    Number[] numberArray = intArray;

     

    上面的代码是有效的,因为一个Integer 是 一个 Number,因而一个 Integer 数组是 一个 Number 数组。但是对于泛型来说则不然。下面的代码是无效的:

     

    List<Integer> intList = newArrayList<Integer>();

    List<Number> numberList = intList; //invalid

     

    最初,大多数 Java 程序员觉得这缺少协变很烦人,或者甚至是“坏的(broken)”,但是之所以这样有一个很好的原因。如果可以将List<Integer> 赋给 List<Number>,下面的代码就会违背泛型应该提供的类型安全:

     

    List<Integer> intList = newArrayList<Integer>();

    List<Number> numberList = intList; //invalid

    numberList.add(new Float(3.1415));

     

    因为 intList 和 numberList 都是有别名的,如果允许的话,上面的代码就会让您将不是 Integers 的东西放进 intList 中。

    4.2 代码实例

    package Generics;
    
    import java.util.Random;
    
    interface Generator<T> {
    	public T next();
    }
    
    class Coffee{
    	public String toString(){
    		return getClass().getSimpleName();
    	}
    }
    
    class Mocha extends Coffee{}
    class Cappuccino extends Coffee{}
    class Breve extends Coffee{}
    class Latte extends Coffee{}
    
    class CoffeeGenerator implements Generator<Coffee>{ //T为Coffee
    	private static Random rand = new Random(47);
    	private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Breve.class};
    	public Coffee next(){ //T为Coffee
    		try {
    			return (Coffee)
    					types[rand.nextInt(types.length)].newInstance();
    		} catch (Exception e) {
    			throw new RuntimeException(e);
    		} 
    	}
    }
    
    public class InterfaceGenTest {
    	public static void main(String[] args) {
    		CoffeeGenerator gen = new CoffeeGenerator();
    		for(int i=0; i<4; i++){
    			System.out.println(gen.next());
    		}
    	}
    }
    /*Cappuccino
    Mocha
    Cappuccino
    Latte*/

    5 怎么自定义泛型方法。

    5.1 泛型方法

           泛型方法使得该方法能独立于类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数。所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。

           要定义泛型方法,只需将泛型参数列表置于返回值之前,就像下面这样:

    5.2 代码示例

    package Generics;
    
    public class GenericMethods {
    //当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上
    	public <T> void f(T x){
    		System.out.println(x.getClass().getName());
    	}
    	public static void main(String[] args) {
    		GenericMethods gm = new GenericMethods();
    		gm.f(99);
    		gm.f("掌上洪城");
    		gm.f(new Integer(99));
    		gm.f(18.88);
    		gm.f('a');
    		gm.f(gm);
    	}
    }
    /* 输出结果:
    java.lang.Integer
    java.lang.String
    java.lang.Integer
    java.lang.Double
    java.lang.Character
    Generics.GenericMethods
     */

    5.3 可变参数与泛型方法

    泛型方法与可变参数列表能很好地共存:

    package Generics;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class GenericVarargs {
    	public static <T> List<T> makeList(T... args){
    		List<T> result = new ArrayList<T>();
    		for(T item:args)
    			result.add(item);
    		return result;		 
    	}
    	public static void main(String[] args) {
    		List ls = makeList("A");
    		System.out.println(ls);
    		ls = makeList("A","B","C");
    		System.out.println(ls);
    		ls = makeList("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""));
    		System.out.println(ls);
    	}
    }
    /*
    [A]
    [A, B, C]
    [A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
    */

    静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

        public static<Q> void function(Q t) {

            System.out.println("function:"+t);

        }

    6、怎么构建复杂模型如list元组?

    泛型的一个重要好处是能够简单而安全地创建复杂的模型。如List元组。

    package Generics;
    
    import java.util.ArrayList;
    
    class ThreeTuple2<A,B,C>{
    	public final A first;
    	public final B second;
    	private final C three;
    	public ThreeTuple2(A a,B b,C c){
    		first = a;
    		second = b;
    		three = c;
    	}
    	public String toString(){
    		return "(" + first + "," + second + "," + three + ")";
    	}
    }
    
    public class TupleList<A,B,C> extends ArrayList<ThreeTuple2<A,B,C>> {
    	static ThreeTuple2<Integer,String,Character> h(){
    		return new ThreeTuple2<Integer,String,Character>(99,"掌上洪城",'a');
    	}
    	public static void main(String[] args) {
    		TupleList<Integer,String,Character> ts = new TupleList<Integer,String,Character>();
    		ts.add(h());
    		ts.add(h());
    		for(ThreeTuple2<Integer,String,Character> ttp:ts)
    		System.out.println(ttp);		
    	}
    }
    package Generics;
    
    import java.util.ArrayList;
    
    class ThreeTuple2<A,B,C>{
    	public final A first;
    	public final B second;
    	private final C three;
    	public ThreeTuple2(A a,B b,C c){
    		first = a;
    		second = b;
    		three = c;
    	}
    	public String toString(){
    		return "(" + first + "," + second + "," + three + ")";
    	}
    }
    
    public class TupleList<A,B,C> extends ArrayList<ThreeTuple2<A,B,C>> {
    	static ThreeTuple2<Integer,String,Character> h(){
    		return new ThreeTuple2<Integer,String,Character>(99,"掌上洪城",'a');
    	}
    	public static void main(String[] args) {
    		TupleList<Integer,String,Character> ts = new TupleList<Integer,String,Character>();
    		ts.add(h());
    		ts.add(h());
    		for(ThreeTuple2<Integer,String,Character> ttp:ts)
    		System.out.println(ttp);		
    	}
    }
    /* 输出结果为:
    (99,掌上洪城,a)
    (99,掌上洪城,a)
     */

    7、泛型的擦除

    7.1 代码实例:

    package generics;
    
    import java.util.*;
    
    public class ErasedTypeEquivalence {
    	public static void main(String[] args) {
    		Class c1 = new ArrayList<String>().getClass();
    		Class c2 = new ArrayList<Integer>().getClass();
    		System.out.println(c1 == c2);
    	}
    } /*
    	 * Output: true
    	 */// :~

    在泛型内部,无法获得任何有关泛型参数类型的信息。

    ArrayList<String>和ArrayList<Integer>是相同的类型。

    7.2 擦除的补偿

    要想在表达式中使用类型,需要显式地传递类型的class对象。

    package generics;
    class Building {
    }
    
    class House extends Building {
    }
    
    public class ClassTypeCapture<T> {
    	Class<T> kind;
    
    	public ClassTypeCapture(Class<T> kind) {
    		this.kind = kind;
    	}
    
    	public boolean f(Object arg) {
    		return kind.isInstance(arg);
    	}
    
    	public static void main(String[] args) {
    		ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
    		System.out.println(ctt1.f(new Building()));
    		System.out.println(ctt1.f(new House()));
    		ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
    		System.out.println(ctt2.f(new Building()));
    		System.out.println(ctt2.f(new House()));
    	}
    } /*
    	 * Output: true true false true
    	 */// :~

    8、可以创建泛型数组吗?相应的应用场景怎么处理?

    正如你在下面示例Erased.java中所见,不能创建泛型数组。一般的解决方案是任何想要创建泛型数组的地方都使用ArrayList:


    package generics;
    
    public class Erased<T> {
    	private final int SIZE = 100;
    
    	public static void f(Object arg) {
    		if (arg instanceof T) {
    		} // Cannot make a static reference to the non-static type T
    		T var = new T(); // Error
    		T[] array = new T[SIZE]; // Error
    		T[] array = (T) new Object[SIZE]; // Unchecked warning
    	}
    } /// :~

    使用ArrayList示例

    package generics;
    
    import java.util.*;
    
    public class ListOfGenerics<T> {
    	private List<T> array = new ArrayList<T>();
    
    	public void add(T item) {
    		array.add(item);
    	}
    
    	public T get(int index) {
    		return array.get(index);
    	}
    } /// :~

    9、泛型通配符‘?’怎么用?

    可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

     

    例如Class<?>classType = Class.forName("java.lang.String");

     

    下面我们先看看这些程序:

    //Code list 2

    void TestGen0Medthod1(List l) {

     for (Object o: l)

    System.out.println(o);

    }

    看看这个方法有没有异议,这个方法会通过编译的,假如你传入String,就是这样List<String>。

    接着我们调用它,问题就出现了,我们将一个List<String>当作List传给了方法,JVM会给我们一个警告,说这个破坏了类型安全,因为从List中返回的都是Object类型的,而让我们再看看下面的方法。

    //Code list 3

    void TestGen0Medthod1(List<String> l) {

     for (Object o: l)

    System.out.println(o);

    }

    因为这里的List<String>不是List<Object>的子类,不是String与Object的关系,就是说List<String>不隶属于list<Object>,他们不是继承关系,所以是不行的,这里的extends是表示限制的。

    类型通配符是很神奇的,List<?>这个你能为他做什么呢?怎么都是“?”,它似乎不确定,他总不能返回一个?作为类型的数据吧,是啊他是不会返回一个“?”来问程序员的?JVM会做简单的思考的,看看代码吧,更直观些。

    //code list 4

    List<String> l1 = newArrayList<String>();

    li.add(“String”);

    List<?> l2 = l1;

    System.out.println(l1.get(0));

    这段代码没问题的,l1.get(0)将返回一个Object。

    10、泛型限定(上限和下限)的表达式是怎样的?

    上限:?extends E:可以接收E类型或者E的子类型对象。

    下限:?super E:可以接收E类型或者E的父类型对象。

    上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。

     

    下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

    11、可以将基本类型作为泛型参数吗?

    泛型的类型参数只能是类类型(包括自定义类),不能是简单类型(基本数据类型)。

    12、什么时候用泛型?

    当接口、类及方法中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。

    泛型的细节:

    1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;

    2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致;

        原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;

    3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);

    ArrayList<String>al = new ArrayList<Object>();  //

    //要保证左右两边的泛型具体类型一致就可以了,这样不容易出错。

    ArrayList<?extends Object> al = new ArrayList<String>();

    al.add("aa");  //

    //因为集合具体对象中既可存储String,也可以存储Object的其他子类,所以添加具体的类型对象不合适,类型检查会出现安全问题。 ?extendsObject 代表Object的子类型不确定,怎么能添加具体类型的对象呢?

    public static voidmethod(ArrayList<? extends Object> al) {

    al.add("abc");  //

      //只能对al集合中的元素调用Object类中的方法,具体子类型的方法都不能用,因为子类型不确定。

    13、Java类库中的泛型有那些?

    所有的标准集合接口都是泛型化的—— Collection<V>、List<V>、Set<V> 和 Map<K,V>。类似地,集合接口的实现都是用相同类型参数泛型化的,所以HashMap<K,V> 实现 Map<K,V> 等。

    除了集合类之外,Java 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。

    14、练习

    做完这两个练习:

    练习一:写一个使用泛型跟不使用泛型参数任意化的例子。代码略

    练习二:修改ClassTypeCapture.java,添加一个Map<String,Class<?>>,一个addType(String typeName,Class<?>kind)方法和一个createNew()方法。createNew()将产生一个与其参数字符串关联的新实例,或者产生一条错误信息。

    package Generics;
    
    import java.util.*;
    import static org.greggordon.tools.Print.*;
    
    class Building {}
    class House extends Building {}
    
    public class ClassTypeCapture21<T> {
    	Class<?> kind;
    	Map<String, Class<?>> map;
    
    	public ClassTypeCapture21(Class<?> kind) {
    		this.kind = kind;
    	}
    
    	public ClassTypeCapture21(Class<?> kind, Map<String, Class<?>> map) {
    		this.kind = kind;
    		this.map = map;
    	}
    
    	public boolean f(Object arg) {
    		return kind.isInstance(arg);
    	}
    
    	public void addType(String typename, Class<?> kind) {
    		map.put(typename, kind);
    	}
    
    	public Object createNew(String typename) throws IllegalAccessException, InstantiationException {
    		if (map.containsKey(typename))
    			return map.get(typename).newInstance();
    		System.out.println(typename + " class not available");
    		return null;
    	}
    
    	public static void main(String[] args) {
    		ClassTypeCapture21<Building> ctt1 = new ClassTypeCapture21<Building>(Building.class);
    		println(ctt1.f(new Building()));
    		println(ctt1.f(new House()));
    		ClassTypeCapture21<House> ctt2 = new ClassTypeCapture21<House>(House.class);
    		println(ctt2.f(new Building()));
    		println(ctt2.f(new House()));
    		ClassTypeCapture21<Building> ct = new ClassTypeCapture21<Building>(Building.class,
    				new HashMap<String, Class<?>>());
    		ct.addType("House", House.class);
    		ct.addType("Building", Building.class);
    		println("ct.map = " + ct.map);
    		try {
    			Building b = (Building) ct.createNew("Building");
    			House h = (House) ct.createNew("House");
    			print("b.getClass().getName(): ");
    			println(b.getClass().getName());
    			print("h.getClass().getName(): ");
    			println(h.getClass().getName());
    			print("House h is instance House: ");
    			println(h instanceof House);
    			print("House h is instance of Building: ");
    			println(h instanceof Building);
    			print("Building b is instance of House: ");
    			println(b instanceof House);
    			ct.createNew("String"); // String class not available
    		} catch (IllegalAccessException e) {
    			println("IllegalAccessException in main");
    		} catch (InstantiationException e) {
    			println("InstantiationException in main");
    		}
    	}
    }
    /*
    true
    true
    false
    true
    ct.map = {Building=class Generics.Building, House=class Generics.House}
    b.getClass().getName(): Generics.Building
    h.getClass().getName(): Generics.House
    House h is instance House: true
    House h is instance of Building: true
    Building b is instance of House: false
    String class not available
     */

    15 参考资料

    Java1.5泛型指南中文版(Java1.5 Generic Tutorial)


    展开全文
  • Java 泛型详解

    2018-09-18 17:03:22
    Java泛型详解Java泛型详解Java泛型详解Java泛型详解
  • Java泛型详解三,学以致用1,泛型解析JSON数据封装package com.foen.cloud.utils;import com.alibaba.fastjson.JSONObject;/*** @Description:类说明:后台返回前端请求处理结果基类,泛型应用* @author: gzh* @date:...

    Java泛型详解

    三,学以致用

    1,泛型解析JSON数据封装

    package com.foen.cloud.utils;

    import com.alibaba.fastjson.JSONObject;

    /**

    * @Description:类说明:后台返回前端请求处理结果基类,泛型应用

    * @author: gzh

    * @date: 2018年9月23日下午2:25:45

    */

    public class ObjResultMessage extends BaseResultMessage{

    /**

    * 实体

    */

    private T obj;

    @Override

    public String toString() {

    return JSONObject.toJSONString(this);

    }

    public T getObj() {

    return obj;

    }

    public void setObj(T obj) {

    this.obj = obj;

    }

    }

    展开全文
  • java 泛型详解

    2019-06-04 17:35:10
    java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一 2016年12月30日 11:44:29...本文参考java 泛型详解、Java中的泛型方法、java泛型详解 1. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设...

    java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

    2016年12月30日 11:44:29 VieLei 阅读数:252622

    对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。

    本文参考java 泛型详解Java中的泛型方法、 java泛型详解

    1. 概述

    泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

    什么是泛型?为什么要使用泛型?

    泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

    泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

    2. 一个栗子

    一个被举了无数次的例子:

    List arrayList = new ArrayList();
    arrayList.add("aaaa");
    arrayList.add(100);
    
    for(int i = 0; i< arrayList.size();i++){
        String item = (String)arrayList.get(i);
        Log.d("泛型测试","item = " + item);
    }

    毫无疑问,程序的运行结果会以崩溃结束:

    java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    •  

    ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

    我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

    List<String> arrayList = new ArrayList<String>();
    ...
    //arrayList.add(100); 在编译阶段,编译器就会报错

    3. 特性

    泛型只在编译阶段有效。看下面的代码:

    List<String> stringArrayList = new ArrayList<String>();
    List<Integer> integerArrayList = new ArrayList<Integer>();
    
    Class classStringArrayList = stringArrayList.getClass();
    Class classIntegerArrayList = integerArrayList.getClass();
    
    if(classStringArrayList.equals(classIntegerArrayList)){
        Log.d("泛型测试","类型相同");
    }

    输出结果:D/泛型测试: 类型相同

    通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

    对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

    4. 泛型的使用

    泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

    4.3 泛型类

    泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

    泛型类的最基本写法(这么看可能会有点晕,会在下面的例子中详解):

    class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
      private 泛型标识 /*(成员变量类型)*/ var; 
      .....
    
      }
    }

    一个最普通的泛型类:

    //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
    //在实例化泛型类时,必须指定T的具体类型
    public class Generic<T>{ 
        //key这个成员变量的类型为T,T的类型由外部指定  
        private T key;
    
        public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
            this.key = key;
        }
    
        public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
            return key;
        }
    }
    //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
    //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
    Generic<Integer> genericInteger = new Generic<Integer>(123456);
    
    //传入的实参类型需与泛型的类型参数类型相同,即为String.
    Generic<String> genericString = new Generic<String>("key_vlaue");
    Log.d("泛型测试","key is " + genericInteger.getKey());
    Log.d("泛型测试","key is " + genericString.getKey());
    12-27 09:20:04.432 13063-13063/? D/泛型测试: key is 123456
    12-27 09:20:04.432 13063-13063/? D/泛型测试: key is key_vlaue

    定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

    看一个例子:

    Generic generic = new Generic("111111");
    Generic generic1 = new Generic(4444);
    Generic generic2 = new Generic(55.55);
    Generic generic3 = new Generic(false);
    
    Log.d("泛型测试","key is " + generic.getKey());
    Log.d("泛型测试","key is " + generic1.getKey());
    Log.d("泛型测试","key is " + generic2.getKey());
    Log.d("泛型测试","key is " + generic3.getKey());
    D/泛型测试: key is 111111
    D/泛型测试: key is 4444
    D/泛型测试: key is 55.55
    D/泛型测试: key is false

    注意:

      1. 泛型的类型参数只能是类类型,不能是简单类型。
      1. 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

        if(ex_num instanceof Generic<Number>){   
        } 

    4.4 泛型接口

    泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

    //定义一个泛型接口
    public interface Generator<T> {
        public T next();
    }

    当实现泛型接口的类,未传入泛型实参时:

    /**
     * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
     * 即:class FruitGenerator<T> implements Generator<T>{
     * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
     */
    class FruitGenerator<T> implements Generator<T>{
        @Override
        public T next() {
            return null;
        }
    }

    当实现泛型接口的类,传入泛型实参时:

    /**
     * 传入泛型实参时:
     * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
     * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
     * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
     * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
     */
    public class FruitGenerator implements Generator<String> {
    
        private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
    
        @Override
        public String next() {
            Random rand = new Random();
            return fruits[rand.nextInt(3)];
        }
    }

    4.5 泛型通配符

    我们知道IngeterNumber的一个子类,同时在特性章节中我们也验证过Generic<Ingeter>Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实例传入呢?在逻辑上类似于Generic<Number>Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?

    为了弄清楚这个问题,我们使用Generic<T>这个泛型类继续看下面的例子:

    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }
    Generic<Integer> gInteger = new Generic<Integer>(123);
    Generic<Number> gNumber = new Generic<Number>(456);
    
    showKeyValue(gNumber);
    
    // showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer> 
    // cannot be applied to Generic<java.lang.Number>
    // showKeyValue(gInteger);

    通过提示信息我们可以看到Generic<Integer>不能被看作为`Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

    回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时Generic<Integer>Generic<Number>父类的引用类型。由此类型通配符应运而生。

    我们可以将上面的方法改一下:

    public void showKeyValue1(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。

    可以解决当具体类型不确定的时候,这个通配符就是 ?  ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

    4.6 泛型方法

    在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

    尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

    泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

    /**
     * 泛型方法的基本介绍
     * @param tClass 传入的泛型实参
     * @return T 返回值为T类型
     * 说明:
     *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
     *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
      IllegalAccessException{
            T instance = tClass.newInstance();
            return instance;
    }
    Object obj = genericMethod(Class.forName("com.test.test"));
    • 1

    4.6.1 泛型方法的基本用法

    光看上面的例子有的同学可能依然会非常迷糊,我们再通过一个例子,把我泛型方法再总结一下。

    public class GenericTest {
       //这个类是个泛型类,在上面已经介绍过
       public class Generic<T>{     
            private T key;
    
            public Generic(T key) {
                this.key = key;
            }
    
            //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
            //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
            //所以在这个方法中才可以继续使用 T 这个泛型。
            public T getKey(){
                return key;
            }
    
            /**
             * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
             * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
            public E setKey(E key){
                 this.key = keu
            }
            */
        }
    
        /** 
         * 这才是一个真正的泛型方法。
         * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
         * 这个T可以出现在这个泛型方法的任意位置.
         * 泛型的数量也可以为任意多个 
         *    如:public <T,K> K showKeyName(Generic<T> container){
         *        ...
         *        }
         */
        public <T> T showKeyName(Generic<T> container){
            System.out.println("container key :" + container.getKey());
            //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
            T test = container.getKey();
            return test;
        }
    
        //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
        public void showKeyValue1(Generic<Number> obj){
            Log.d("泛型测试","key value is " + obj.getKey());
        }
    
        //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
        //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
        public void showKeyValue2(Generic<?> obj){
            Log.d("泛型测试","key value is " + obj.getKey());
        }
    
         /**
         * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
         * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
         * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
        public <T> T showKeyName(Generic<E> container){
            ...
        }  
        */
    
        /**
         * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
         * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
         * 所以这也不是一个正确的泛型方法声明。
        public void showkey(T genericObj){
    
        }
        */
    
        public static void main(String[] args) {
    
    
        }
    }

    4.6.2 类中的泛型方法

    当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下

    public class GenericFruit {
        class Fruit{
            @Override
            public String toString() {
                return "fruit";
            }
        }
    
        class Apple extends Fruit{
            @Override
            public String toString() {
                return "apple";
            }
        }
    
        class Person{
            @Override
            public String toString() {
                return "Person";
            }
        }
    
        class GenerateTest<T>{
            public void show_1(T t){
                System.out.println(t.toString());
            }
    
            //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
            //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
            public <E> void show_3(E t){
                System.out.println(t.toString());
            }
    
            //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
            public <T> void show_2(T t){
                System.out.println(t.toString());
            }
        }
    
        public static void main(String[] args) {
            Apple apple = new Apple();
            Person person = new Person();
    
            GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
            //apple是Fruit的子类,所以这里可以
            generateTest.show_1(apple);
            //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
            //generateTest.show_1(person);
    
            //使用这两个方法都可以成功
            generateTest.show_2(apple);
            generateTest.show_2(person);
    
            //使用这两个方法也都可以成功
            generateTest.show_3(apple);
            generateTest.show_3(person);
        }
    }

    4.6.3 泛型方法与可变参数

    再看一个泛型方法和可变参数的例子:

    public <T> void printMsg( T... args){
        for(T t : args){
            Log.d("泛型测试","t is " + t);
        }
    }
    printMsg("111",222,"aaaa","2323.4",55.55);
    • 1

    4.6.4 静态方法与泛型

    静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

    即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

    public class StaticGenerator<T> {
        ....
        ....
        /**
         * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
         * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
         * 如:public static void show(T t){..},此时编译器会提示错误信息:
              "StaticGenerator cannot be refrenced from static context"
         */
        public static <T> void show(T t){
    
        }
    }

    4.6.5 泛型方法总结

    泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

    无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

    4.6 泛型上下边界

    在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

    • 为泛型添加上边界,即传入的类型实参必须是指定类型的子类型

       

    public void showKeyValue1(Generic<? extends Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }
    Generic<String> generic1 = new Generic<String>("11111");
    Generic<Integer> generic2 = new Generic<Integer>(2222);
    Generic<Float> generic3 = new Generic<Float>(2.4f);
    Generic<Double> generic4 = new Generic<Double>(2.56);
    
    //这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
    //showKeyValue1(generic1);
    
    showKeyValue1(generic2);
    showKeyValue1(generic3);
    showKeyValue1(generic4);

    如果我们把泛型类的定义也改一下:

    public class Generic<T extends Number>{
        private T key;
    
        public Generic(T key) {
            this.key = key;
        }
    
        public T getKey(){
            return key;
        }
    }
    //这一行代码也会报错,因为String不是Number的子类
    Generic<String> generic1 = new Generic<String>("11111");

    再来一个泛型方法的例子:

    //在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
    //public <T> T showKeyName(Generic<T extends Number> container),编译器会报错:"Unexpected bound"
    public <T extends Number> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        T test = container.getKey();
        return test;
    }

    通过上面的两个例子可以看出:泛型的上下边界添加,必须与泛型的声明在一起 。

    4.7 关于泛型数组要提一下

    看到了很多文章中都会提起泛型数组,经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。

    也就是说下面的这个例子是不可以的:

    List<String>[] ls = new ArrayList<String>[10];  
    •  

    而使用通配符创建泛型数组是可以的,如下面这个例子:

    List<?>[] ls = new ArrayList<?>[10];  

    这样也是可以的:

    List<String>[] ls = new ArrayList[10];

    下面使用Sun的一篇文档的一个例子来说明这个问题:

    List<String>[] lsa = new List<String>[10]; // Not really allowed.    
    Object o = lsa;    
    Object[] oa = (Object[]) o;    
    List<Integer> li = new ArrayList<Integer>();    
    li.add(new Integer(3));    
    oa[1] = li; // Unsound, but passes run time store check    
    String s = lsa[1].get(0); // Run-time error: ClassCastException.

    这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。

    而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。

    下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。

    List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
    Object o = lsa;    
    Object[] oa = (Object[]) o;    
    List<Integer> li = new ArrayList<Integer>();    
    li.add(new Integer(3));    
    oa[1] = li; // Correct.    
    Integer i = (Integer) lsa[1].get(0); // OK 

    5. 最后

    本文中的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性。另外,一提到泛型,相信大家用到最多的就是在集合中,其实,在实际的编程过程中,自己可以使用泛型去简化开发,且能很好的保证代码质量。

     

    转载地址:https://blog.csdn.net/s10461/article/details/53941091

    展开全文
  • Java泛型详解

    2019-01-06 19:54:00
    JAVA泛型详解 参考源:JavaSE(九)之泛型(Cenerics);java泛型详解 目录: 一、泛型简介 1.1泛型的概述 1.2泛型的特性 二、泛型类 三...
  • 本文参考java 泛型详解、Java中的泛型方法、 java泛型详解 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”。一提到...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,923
精华内容 1,169
关键字:

java泛型详解

java 订阅