泛型 订阅
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。 展开全文
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。
信息
适用范围
计算机
类    别
程序设计语言的一种特性
中文名
泛型
外文名
genericity
泛型定义分类
泛型的定义主要有以下两种:1.在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象。(这是当今较常见的定义)2.在程序编码中一些包含参数的类。其参数可以代表类或对象等等。(人们大多把这称作模板)不论使用哪个定义,泛型的参数在真正使用泛型时都必须作出指明。一些强类型编程语言支持泛型,其主要目的是加强类型安全及减少类转换的次数,但一些支持泛型的编程语言只能达到部分目的。
收起全文
精华内容
参与话题
问答
  • 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。 本文参考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泛型

    千次阅读 2020-05-17 09:50:09
    概念:泛型是一个未知的数据类型 E e:Element 元素 T t:Type 类型等 使用泛型和不使用泛型的区别 不适用泛型 好处:集合不使用泛型,默认类型是Object类型,可以存储任意类型 弊端:不安全,会引发类型转换异常 ...
    1. 概念:泛型是一个未知的数据类型
      E e:Element 元素
      T t:Type 类型等
    2. 使用泛型和不使用泛型的区别
      • 不适用泛型
        好处:集合不使用泛型,默认类型是Object类型,可以存储任意类型
        弊端:不安全,会引发类型转换异常
      • 使用泛型
        好处:避免的转换的麻烦,存储什么类型,取出什么类型;把运行期异常提醒到编译器
        弊端:泛型是什么类型只能存储什么类型
    3. 泛型的定义和使用
      1. 泛型类
        定义格式:修饰符 class 类名<泛型>{}
        class ArrayList<E> {
            public boolean add(E e) {}
            public E get(int index) {}
        }
        
      2. 泛型方法
        定义格式:修饰符 <泛型> 返回值类型 方法名(参数(使用泛型)){}
        public <M> void fx(M m) {
           System.out.println(m);
        }
        
      3. 泛型接口
        1. 第一种方法:定义接口的实现类,实现接口,指定接口的泛型
        2. 第二种方法:定义接口的实现类,接口是什么类型,指定实现类也是相同的接口,类跟着接口走,相当于确定了一个泛型类
      4. 泛型的通配符
        • 泛型的通配符:?,代表任意数据类型
        • 使用方式:不能通过创建对象使用;只能作为方法的参数使用
      5. 泛型的限定
        • 泛型上限限定:? extend E,表示使用的泛型只能是E类的子类或者本身
        • 泛型下限限定:? super E,表示使用的泛型只能是E类的父类或者本身
    展开全文
  • 泛型是什么?为什么要使用泛型

    万次阅读 多人点赞 2016-08-07 00:06:21
    我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int型数据,另一个是处理...泛型的出现就是专门解决这个问题的。 为什么要使用泛型 为了了解这个问题,我们先看下面的代码,代码省略了一

    我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int型数据,另一个是处理String类型数据,或者其它自定义类型数据,但是我们没有办法,只能分别写多个方法处理每种数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。


    为什么要使用泛型

    为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:

    public class Stack

    {

         private int[] m_item;

         public int pop(){...}

         public void Push(int item){...}

         public Stack(int i)

         {

              this.m_item = new int[i];

         }

    }

    上面的代码运行得很好,但是,当我们需要一个栈来保存String类型时,该怎么办呢?很多人就想到把上面的代码复制一份,把int改成String不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序员是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎么做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型Object来实现这个栈:


    public class Stack

        {

            private object[] m_item;

            public object Pop(){...}

            public void Push(object item){...}

            public Stack(int i)

            {

                this.m_item = new[i];

            }

          }

    这个栈写得不错,它非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:

    当Stack处理值类型时,会出现装箱、拆箱操作,但将用到的数据类型的强制转换操作,增加处理器的负担。

    在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):
    Node1 x = new Node1();

                stack.Push(x);

             Node2 y = (Node2)stack.Pop();

    上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。


    针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。


    使用泛型
    下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:

    public class Stack<T>

    {

        private T[] m_item;

        public T Pop(){...}

        public void Push(T item){...}

        public Stack(int i){

             this.m_item = new T[i];

        }

    }

    类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:

    //实例化只能保存int类型的类

    Stack<int> a = new Stack<int>(100);

       a.Push(10);

       a.Push("8888");//这行编译不通过,因为类a只接收int类型的数据

       int x = a.Pop();

    Stack<String> b = new Stack<String>(100);

        b.Push(10);//这行编译不通过,因为类b只接收String类型的数据

       String y = b.Pop();


    这个类和Object实现的类有截然不同的区别:

    1.它是类型安全的。实例化了int类型的栈,就不能处理String类型的数据,其他的数据类型也一样。

    2.无需装箱和拆箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和拆箱。

    3.无需类型转换。

    展开全文
  • Java泛型深入理解

    万次阅读 多人点赞 2016-07-21 23:24:28
    Java泛型,包括Java泛型的实现,泛型擦除以及相关面试题,通配符理解

    泛型之前

     

    在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数,这样的方法将会更具有通用性。此外,如果将方法参数声明为接口,将会更加灵活。

    通过继承设计通用程序

    在Java增加泛型类型之前,通用程序的设计就是利用继承实现的,例如,ArrayList类只维护一个Object引用的数组,Object为所有类基类

    public class BeforeGeneric {
        /**
         * 泛型之前的通用程序设计
         */
        static class ArrayList{
            private Object[] elements=new Object[0];
            public Object get(int i){
                return elements[i];
            }
            /**
             * 这里的实现,只是为了演示,不具有任何参考价值
             * */
            public void add(Object o){
                int length = elements.length;
                Object[] newElements = new Object[length+1];
                for(int i=0; i<length; i++){
                    newElements[i] = elements[i];
                }
                newElements[length] = o;
                elements = newElements;
            }
        }
    
        public static void main(String[] args) {
            ArrayList stringValues = new ArrayList();
            //可以向数组中添加任何类型的对象
            stringValues.add(1);
            //问题1——获取值时必须强制转换
            String str = (String) stringValues.get(0);
            //问题2——上述强制转型编译时不会出错,而运行时报异常java.lang.ClassCastException
            System.out.println(str);
        }
    }

    面临的问题

    • 当我们获取一个值的时候,必须进行强制类型转换
    • 当我们插入一个值的时候,无法约束预期的类型。假定我们预想的是利用stringValues来存放String集合,因为ArrayList只是维护一个Object引用的数组,我们无法阻止将Integer类型(Object子类)的数据加入stringValues。然而,当我们使用数据的时候,需要将获取的Object对象转换为我们期望的类型(String),如果向集合中添加了非预期的类型(如Integer),编译时我们不会收到任何的错误提示。但当我们运行程序时却会报异常:

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at generic.BeforeGeneric.main(BeforeGeneric.java:24)

     

    这显然不是我们所期望的,如果程序有潜在的错误,我们更期望在编译时被告知错误,而不是在运行时报异常。

    泛型

    针对利用继承来实现通用程序设计所产生的问题,泛型提供了更好的解决方案:类型参数。例如,ArrayList类用一个类型参数来指出元素的类型。

    ArrayList<String> stringValues=new ArrayList<String>();

    这样的代码具有更好的可读性,我们一看就知道该集合用来保存String类型的对象,而不是仅仅依赖变量名称来暗示我们期望的类型。

    public class GenericType {
    	public static void main(String[] args) {  
    		ArrayList<String> stringValues=new ArrayList<String>();
    		stringValues.add("str");
    		stringValues.add(1); //编译错误
    	} 
    }

    现在,如果我们向ArrayList<String>添加Integer类型的对象,将会出现编译错误。

    Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method add(int, String) in the type ArrayList<String> is not applicable for the arguments (int)
    at generic.GenericType.main(GenericType.java:8)

    编译器会自动帮我们检查,避免向集合中插入错误类型的对象,从而使得程序具有更好的安全性

    总之,泛型通过类型参数使得我们的程序具有更好的可读性安全性
     

    Java泛型的实现原理

    擦除

    public class GenericType {
        public static void main(String[] args) {  
            ArrayList<String> arrayString=new ArrayList<String>();   
            ArrayList<Integer> arrayInteger=new ArrayList<Integer>();   
            System.out.println(arrayString.getClass()==arrayInteger.getClass());  
        }  
    }

    输出:

    true

    在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型,只能存储字符串。一个是ArrayList<Integer>泛型类型,只能存储整型。最后,我们通过arrayString对象和arrayInteger对象的getClass方法获取它们的类信息并比较,发现结果为true。

    这是为什么呢,明明我们定义了两种不同的类型?因为,在编译期间,所有的泛型信息都会被擦除,List<Integer>和List<String>类型,在编译后都会变成List类型(原始类型)。Java中的泛型基本上都是在编译器这个层次来实现的,这也是Java的泛型被称为“伪泛型”的原因。

    原始类型

    原始类型就是泛型类型擦除了泛型信息后,在字节码中真正的类型。无论何时定义一个泛型类型,相应的原始类型都会被自动提供。原始类型的名字就是删去类型参数后的泛型类型的类名。擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换)。
    //泛型类型
    class Pair<T> {  
        private T value;  
        public T getValue() {  
            return value;  
        }  
        public void setValue(T  value) {  
            this.value = value;  
        }  
    }
    //原始类型
    class Pair {  
        private Object value;  
        public Object getValue() {  
            return value;  
        }  
        public void setValue(Object  value) {  
            this.value = value;  
        }  
    }  

    因为在Pair<T>中,T是一个无限定的类型变量,所以用Object替换。如果是Pair<T extends Number>,擦除后,类型变量用Number类型替换。

    突破泛型约束

    public class ReflectInGeneric {
        public static void main(String[] args) throws IllegalArgumentException, 
                            SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {  
            ArrayList<Integer> array=new ArrayList<Integer>();  
            // 这样调用add方法只能存储整形,因为泛型类型的实例为Integer 
            array.add(1); 
            // 通过泛型可以突破泛型类型约束
            array.getClass().getMethod("add", Object.class).invoke(array, "asd");  
            for (int i=0;i<array.size();i++) {  
                System.out.println(array.get(i));  
            }  
        }  
    }

    输出:

    1
    asd

    为什么呢?我们在介绍泛型时指出向ArrayList<Integer>中插入String类型的对象,编译时会报错。现在为什么又可以了呢?

    • 我们在程序中定义了一个ArrayList<Integer>泛型类型,如果直接调用add方法,那么只能存储整形的数据。
    • 不过当我们利用反射调用add方法的时候,却可以存储字符串。

    这说明ArrayList<Integer>泛型信息在编译之后被擦除了,只保留了原始类型,类型变量(T)被替换为Object,在运行时,我们可以行其中插入任意类型的对象。

    再次应证:Java中的泛型基本上都是在编译器这个层次来实现的伪泛型”。

    但是,并不推荐以这种方式操作泛型类型,因为这违背了泛型的初衷(减少强制类型转换以及确保类型安全)。当我们从集合中获取元素时,默认会将对象强制转换成泛型参数指定的类型(这里是Integer),如果放入了非法的对象这个强制转换过程就会出现异常。

    泛型方法的类型推断

    在调用泛型方法的时候,可以指定泛型类型,也可以不指定。

    在不指定泛型类型的情况下,泛型类型为该方法中的几种参数类型的共同父类的最小级,直到Object。

    在指定泛型类型的时候,该方法中的所有参数类型必须是该泛型类型或者其子类。

    public class Test { 
        public static void main(String[] args) {  
            /**不指定泛型的时候*/  
            int i=Test.add(1, 2); //这两个参数都是Integer,所以T替换为Integer类型  
            Number f=Test.add(1, 1.2);//这两个参数一个是Integer,另一个是Float,所以取同一父类的最小级,为Number  
            Object o=Test.add(1, "asd");//这两个参数一个是Integer,另一个是String,所以取同一父类的最小级,为Object
      
            /**指定泛型的时候*/  
            int a=Test.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类  
            int b=Test.<Integer>add(1, 2.2);//编译错误,指定了Integer,不能为Float  
            Number c=Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float  
        }  
          
        //这是一个简单的泛型方法  
        public static <T> T add(T x,T y){  
            return y;  
        } 
    }

    正确的运转

    既然说类型变量会在编译的时候擦除掉,那为什么定义了ArrayList<Integer>泛型类型,而不允许向其中插入String对象呢?不是说泛型变量Integer会在编译时候擦除变为原始类型Object吗,为什么不能存放别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
    java是如何解决这个问题的呢?java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译的。以如下代码为例:

            Pair<Integer> pair=new Pair<Integer> ();
            pair.setValue(3);
            Integer integer=pair.getValue();
            System.out.println(integer);

    擦除getValue()的返回类型后将返回Object类型,编译器自动插入Integer的强制类型转换。也就是说,编译器把这个方法调用翻译为两条字节码指令

    1. 对原始方法Pair.getValue的调用
    2. 将返回的Object类型强制转换为Integer

    此外,存取一个泛型域时,也要插入强制类型转换。因此,我们说Java的泛型是在编译器层次进行实现的,被称为“伪泛型”,相对于C++

    泛型相关问题

    1、泛型类型引用传递问题

    在Java中,像下面形式的引用传递是不允许的:

    ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误  
    ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误 

    我们先看第一种情况,将第一种情况拓展成下面的形式:

    ArrayList<Object> arrayList1=new ArrayList<Object>();  
    arrayList1.add(new Object());  
    arrayList1.add(new Object());  
    ArrayList<String> arrayList2=arrayList1;//编译错误  

    实际上,在第4行代码处,就会有编译错误。那么,我们先假设它编译没错。那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是String类型的对象,可是它里面实际上已经被我们存放了Object类型的对象,这样,就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。(这也是泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷)。
    在看第二种情况,将第二种情况拓展成下面的形式:

    ArrayList<String> arrayList1=new ArrayList<String>();  
    arrayList1.add(new String());  
    arrayList1.add(new String());  
    ArrayList<Object> arrayList2=arrayList1;//编译错误  

    没错,这样的情况比第一种情况好的多,最起码,在我们用arrayList2取值的时候不会出现ClassCastException,因为是从String转换为Object。可是,这样做有什么意义呢,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以java不允许这么干。再说,你如果又用arrayList2往里面add()新的对象,那么到时候取得时候,我怎么知道我取出来的到底是String类型的,还是Object类型的呢?
    所以,要格外注意泛型中引用传递问题。

    2、泛型类型变量不能是基本数据类型

    就比如,没有ArrayList<double>,只有ArrayList<Double>。因为当类型擦除后,ArrayList的原始类中的类型变量(T)替换为Object,但Object类型不能存储double值。
     

    3、运行时类型查询

    举个例子:

    ArrayList<String> arrayList=new ArrayList<String>();    

    因为类型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了。那么,运行时进行类型查询的时候使用下面的方法是错误的

    if( arrayList instanceof ArrayList<String>)    

    java限定了这种类型查询的方式,?为通配符,也即非限定符

    if( arrayList instanceof ArrayList<?>)    

    4、泛型在静态方法和静态类中的问题

    泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

    public class Test2<T> {    
        public static T one;   //编译错误    
        public static  T show(T one){ //编译错误    
            return null;    
        }    
    }  

    因为泛型类中的泛型参数的实例化是在定义泛型类型对象(例如ArrayList<Integer>)的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的

    但是要注意区分下面的一种情况:

    public class Test2<T> {    
        public static <T >T show(T one){//这是正确的    
            return null;    
        }    
    }  

    因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是泛型类中的T。

    泛型相关面试题

    1. Java中的泛型是什么 ? 使用泛型的好处是什么?
    泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。

    泛型是一种编译时类型确认机制。它提供了编译期类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。

    2、Java的泛型是如何工作的 ? 什么是类型擦除 ?
    泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。

    编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List<String>在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀

    3. 什么是泛型中的限定通配符和非限定通配符 ?
    限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。

    4. List<? extends T>和List <? super T>之间有什么区别 ?
    这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。

    5. 如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?
    编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样:

    public V put(K key, V value) {
        return cache.put(key, value);
    }

    6. Java中如何使用泛型编写带有参数的类?
    这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。
    7. 编写一段泛型程序来实现LRU缓存?
    对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。
    8. 你可以把List<String>传递给一个接受List<Object>参数的方法吗?
    对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List<String>应当可以用在需要List<Object>的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储Strings。

    List<Object> objectList;
    List<String> stringList;
          
    objectList = stringList;  //compilation error incompatible types

    9. Array中可以用泛型吗?
    这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。
    10. 如何阻止Java中的类型未检查的警告?
    如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告
    ,例如List<String> rawList = new ArrayList()
    注意: Hello.java使用了未检查或称为不安全的操作;
    这种警告可以使用@SuppressWarnings("unchecked")注解来屏蔽。

    11、Java中List<Object>和原始类型List之间的区别?
    原始类型和带参数类型<Object>之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。这道题的考察点在于对泛型中原始类型的正确理解。它们之间的第二点区别是,你可以把任何带参数的泛型类型传递给接受原始类型List的方法,但却不能把List<String>传递给接受List<Object>的方法,因为会产生编译错误。

    12、Java中List<?>和List<Object>之间的区别是什么?
    这道题跟上一道题看起来很像,实质上却完全不同。List<?> 是一个未知类型的List,而List<Object>其实是任意类型的List。你可以把List<String>, List<Integer>赋值给List<?>,却不能把List<String>赋值给List<Object>。   

    List<?> listOfAnyType;
    List<Object> listOfObject = new ArrayList<Object>();
    List<String> listOfString = new ArrayList<String>();
    List<Integer> listOfInteger = new ArrayList<Integer>();
          
    listOfAnyType = listOfString; //legal
    listOfAnyType = listOfInteger; //legal
    listOfObjectType = (List<Object>) listOfString; //compiler error - in-convertible types
    

    13、List<String>和原始类型List之间的区别.
    该题类似于“原始类型和带参数类型之间有什么区别”。带参数类型是类型安全的,而且其类型安全是由编译器保证的,但原始类型List却不是类型安全的。你不能把String之外的任何其它类型的Object存入String类型的List中,而你可以把任何类型的对象存入原始List中。使用泛型的带参数类型你不需要进行类型转换,但是对于原始类型,你则需要进行显式的类型转换。

    List listOfRawTypes = new ArrayList();
    listOfRawTypes.add("abc");
    listOfRawTypes.add(123); //编译器允许这样 - 运行时却会出现异常
    String item = (String) listOfRawTypes.get(0); //需要显式的类型转换
    item = (String) listOfRawTypes.get(1); //抛ClassCastException,因为Integer不能被转换为String
          
    List<String> listOfString = new ArrayList();
    listOfString.add("abcd");
    listOfString.add(1234); //编译错误,比在运行时抛异常要好
    item = listOfString.get(0); //不需要显式的类型转换 - 编译器自动转换

    通配符

    通配符上界

    常规使用

    public class Test {
    	public static void printIntValue(List<? extends Number> list) {
    		for (Number number : list) {
    			System.out.print(number.intValue()+" "); 
    		}
    		System.out.println();
    	}
    	public static void main(String[] args) {
    		List<Integer> integerList=new ArrayList<Integer>();
    		integerList.add(2);
    		integerList.add(2);
    		printIntValue(integerList);
    		List<Float> floatList=new ArrayList<Float>();
    		floatList.add((float) 3.3);
    		floatList.add((float) 0.3);
    		printIntValue(floatList);
    	}
    }
    输出:
    2 2 
    3 0 

     

    非法使用

    public class Test {
    	public static void fillNumberList(List<? extends Number> list) {
    		list.add(new Integer(0));//编译错误
    		list.add(new Float(1.0));//编译错误
    	}
    	public static void main(String[] args) {
    		List<? extends Number> list=new ArrayList();
    		list.add(new Integer(1));//编译错误
    		list.add(new Float(1.0));//编译错误
    	}
    }
    List<? extends Number>可以代表List<Integer>或List<Float>,为什么不能像其中加入Integer或者Float呢?
    首先,我们知道List<Integer>之中只能加入Integer。并且如下代码是可行的:
    		List<? extends Number> list1=new ArrayList<Integer>();
    		List<? extends Number> list2=new ArrayList<Float>();
    假设前面的例子没有编译错误,如果我们把list1或者list2传入方法fillNumberList,显然都会出现类型不匹配的情况,假设不成立

    因此,我们得出结论:不能往List<? extends T> 中添加任意对象,除了null。

    那为什么对List<? extends T>进行迭代可以呢,因为子类必定有父类相同的接口,这正是我们所期望的。

    通配符下界

    常规使用

    public class Test {
    	public static void fillNumberList(List<? super Number> list) {
    		list.add(new Integer(0));
    		list.add(new Float(1.0));
    	}
    	public static void main(String[] args) {
    		List<? super Number> list=new ArrayList(); 
    		list.add(new Integer(1));
    		list.add(new Float(1.1));
    	}
    }
    可以添加Number的任何子类,为什么呢?
    List<? super Number>可以代表List<T>,其中T为Number父类,(虽然Number没有父类)。如果说,T为Number的父类,我们想List<T>中加入Number的子类肯定是可以的。

    非法使用

    对List<? superT>进行迭代是不允许的。为什么呢?你知道用哪种接口去迭代List吗?只有用Object类的接口才能保证集合中的元素都拥有该接口,显然这个意义不大。其应用场景略。

    无界通配符

    知道了通配符的上界和下界,其实也等同于知道了无界通配符,不加任何修饰即可,单独一个“?”。如List<?>,“?”可以代表任意类型,“任意”也就是未知类型。
    List<Object>与List<?>并不等同,List<Object>是List<?>的子类。还有不能往List<?> list里添加任意对象,除了null。
     

    常规使用

    1、当方法是使用原始的Object类型作为参数时,如下:
     
    public static void printList(List<Object> list) {
        for (Object elem : list)
            System.out.println(elem + "");
        System.out.println();
    }
    可以选择改为如下实现:
     
    public static void printList(List<?> list) {
        for (Object elem: list)
            System.out.print(elem + "");
        System.out.println();
    }
    这样就可以兼容更多的输出,而不单纯是List<Object>,如下:
     
    List<Integer> li = Arrays.asList(1, 2, 3);
    List<String>  ls = Arrays.asList("one", "two", "three");
    printList(li);
    printList(ls);


     

    参考:

    《Java核心技术 卷一》

    http://blog.csdn.net/lonelyroamer/article/details/7868820

    http://www.oschina.net/translate/10-interview-questions-on-java-generics

    http://www.linuxidc.com/Linux/2013-10/90928.htm

    展开全文
  • java进阶技术:泛型、反射、注解

    千人学习 2018-08-23 10:53:36
    泛型是Java SE 1.5的新特性,好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。 JAVA反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对以后学习框架有很大的帮助...
  • 【java基础】泛型

    千次阅读 多人点赞 2019-10-05 21:47:47
    1、 泛型概述 我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。 大家观察下面代码: ...
  • 泛型

    2020-11-16 23:14:39
    泛型 泛型的意义: 1、自动进行类型的检查 2、自动进行类型的转换 泛型的一些重点: 1、泛型是怎么编译的? 擦除机制 -》 Object 2、泛型的类型 不参与类型的组成 3、泛型的参数 不能是简单类 找最大值 class ...
  • 泛型 以下java.util.ArrayList类部分代码: public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public boolean add(E e...
  • 泛型集合和ArrayList的装箱拆箱、常见的泛型类型、泛型类和泛型方法、泛型约束、 泛型委托泛型很难理解?不然在接触的一个新的概念的时候,总会感觉难以理解,当你掌握并能熟练地使用的时候,发现这个概念其实简单...
  • 什么是泛型

    万次阅读 多人点赞 2018-07-06 12:03:19
    // 泛型:就是一种不确定的数据类型。 // 比如:ArrayList&lt;E&gt; E就是泛型。 这种不确定的数据类型需要在使用这个类的时候才能够确定出来。 // 泛型可以省略,如果省略,默认泛型是Object类型。 // 泛型...
  • Java 泛型,你了解类型擦除吗?

    万次阅读 多人点赞 2017-08-05 22:32:18
    泛型Java 中一个很小巧的概念,但同时也是一个很容易让人迷惑的知识点,它让人迷惑的地方在于它的许多表现有点违反直觉。文章开始的地方,先给大家奉上一道经典的测试题。List&lt;String&gt; l1 = new ...
  • Java 泛型方法/接口、泛型限定

    万次阅读 多人点赞 2019-03-30 21:40:50
    文章目录
  • 总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图。C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型区别的最大原因,后面会介绍...
  • 深入理解 Java 泛型

    万次阅读 多人点赞 2016-12-09 22:14:49
    Java 泛型的作用是什么?泛型擦除是什么?泛型一般用在什么场景? 如果这个问题你答不上来,那这篇文章可能就对你有些价值。 读完本文你将了解到: 什么是泛型 为什么引入泛型 泛型的使用方式 泛型泛型接口 ...
  • java泛型

    千次阅读 2018-05-20 13:30:00
    fds
  • Java 泛型方法

    万次阅读 2019-06-09 20:18:33
    Java 7 的“菱形”语法与泛型构造器 设定通配符·下限 泛型方法与方法重载 Java 8 改进的类型推断 1. 定义泛型方法 假设需要实现这样一个方法:该方法负责将一个 Object 数组的所有元素添加到一个 Collection 集合...
  • Java 1.5 之前没有泛型,通常需要使用强制类型转换的方式将一种数据类型转换为另一种数据类型,这种转换要求开发者对实际参数的类型具有可预知性。对于强制类型转换错误的情况,编译器可能不会提示错误,但是在...
  • java泛型详解

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

    千次阅读 2016-05-02 15:39:29
    在网上发现这篇文章写得不错,地址:http://build.cthuwork.com:8081/wordpress/category/java教程/java再谈泛型/ 首先本文假定读者对Java泛型有基础的了解,若需要请参考其他资料配合阅读。 泛型的泛参(type...
  • Java泛型常见面试题

    千次阅读 2017-08-05 20:15:45
    泛型常见面试题
  • java泛型的基本使用

    千次阅读 2018-04-21 08:45:44
    本文参考java 泛型详解、Java中的泛型方法、 java泛型详解1. 概述泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用泛型?泛型,即“参数化类型”。一提到...
  • Java泛型05 : 泛型使用的8个限制

    千次阅读 2018-02-23 15:48:29
    超级通道: Java泛型学习系列-绪论 本章主要对Java泛型使用的8个限制进行说明。 1.Java泛型不能使用基本类型 java的基本类型:boolean,char,float,double,byte,int,short,long。 使用基本类型的泛型会编译报错...
  • JAVA泛型加减乘除

    2014-04-09 21:48:50
    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...
  • JAVA 泛型

    2020-03-09 17:19:56
    能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型泛型方法 (<E> ) 你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数 类型...
  • Java 泛型

    千次阅读 多人点赞 2018-05-26 13:28:03
    泛型包括泛型类,泛型接口和泛型方法
  • 浅谈下java泛型

    2015-08-31 08:56:40
    关于泛型,大家也许在java中经常听到这个词语,上周在面试百度的时候问到了这个术语,却发现了解的少之又少,居然连定义都给不出来,于是下来恶补了下,准备面试果然自我提高的一种方法。什么是泛型呢?我的理解是...
  • java 泛型详解

    2019-01-20 00:47:22
    本文参考java 泛型详解、Java中的泛型方法、 java泛型详解 1. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”...
  • java泛型的用法和详细的解释

    千次阅读 2019-03-27 10:58:48
    Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 1,定义...
  • [超级通道:Java泛型学习系列-绪论] 本章主要对Java泛型类型的命名规范和有界泛型类型进行学习。 1.泛型相关概念 首先通过泛型类与泛型方法的语法,对几个概念进行说明: 泛型类与泛型方法示例 /** * 泛型类...
  • JAVA泛型简单排序实例

    2018-04-22 10:21:40
    JAVA泛型源代码实现以下功能:返回数组元素的最大值/最小值下标;判断数组元素是否按升序排列;T对象数组排序;二分法查找key元素;

空空如也

1 2 3 4 5 ... 20
收藏数 303,771
精华内容 121,508
关键字:

泛型