精华内容
下载资源
问答
  • 本文参考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-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的过程当中,我们就肯定会接触到java泛型,那么你知道java泛型是什么吗,有关java的使用方法又有哪些呢?今天小编就从java泛型的使用方法来了解一下java泛型这一知识。java泛型java泛型加通配符的用法...

    在学习java的过程当中,我们就肯定会接触到java泛型,那么你知道java泛型是什么吗,有关java的使用方法又有哪些呢?今天小编就从java泛型的使用方法来了解一下java泛型这一知识。

    java泛型—java泛型加通配符的用法

    泛型可以用””代表,任意类型的。

    解释:“”是泛型的默认值,可以被任意类型所代替,如:

    Listlist=newArayList();这个就定义了一个String类型的”泛型“集合,那么T的类型就是字符串。

    Listlist=newArayList();

    可以赋值给list:list.add(“StringBatch”);

    可以获取到list的值:list.get(0),结果就是”StringBatch“;

    这个时候T的类型也是String。也就是说T是动态的,可以被任意指定类型。

    3c5ca7799685fc80a8f0160373e2fe88.png

    java泛型引入方法

    java泛型的应用可以提高的代码的复用性,同时泛型提供了类型检查,减少了数据的类型转换,同时保证了类型安全。下面看一下,泛型如何保证了类型安全:

    Listlist=newArrayList();

    list.add(“abc”);

    list.add(newInteger(1));//可以通过编译

    for(Objectobject:list){

    System.out.println((String)object);//抛出ClassCastException异常

    }

    上面的代码会在运行时抛出ClassCastException,因为它尝试将一个Integer转换为String。接着,来看一下从java5开始,Collection的用法:

    Listlist=newArrayList<>();

    list.add(“abc”);

    //list.add(newInteger(1));//编译错误

    for(Stringstring:list){

    System.out.println(string);//无需任何强制类型转换

    }

    注意到,List的创建增加了类型参数String,因此只能向list添加String类型对象,添加其他对象会抛出编译异常;同样可以注意到,foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。

    以上就是有关java泛型的所有内容,在学习java发型的过程当中,大家可以去网上搜索更多的视频,可以购买一些资料,当然大家也可以帮助我们

    推荐阅读

    在excel中移动和复制数据 excel中如何复制数据 | 文军营销如何快速收录 新站如何让百度快速收录 len函数的使用方法 excel表中如何用len函数 电脑快捷键使用大全 电脑常见快捷键有哪些 java泛型 Java泛型的入门知识产品...

    展开全文
  • java泛型

    2019-08-13 21:05:47
    java泛型泛型概述泛型的应用总结普通泛型通配符受限泛型Java泛型无法向上转型Java泛型接口Java泛型方法通过泛型方法返回泛型类型实例使用泛型统一传入的参数类型Java泛型数组Java泛型的嵌套设置 泛型概述 泛型的...

    泛型概述

    • 泛型的目的: 为了保证类型安全
    • “?” 是泛型的通配符
    • 我们用大写字母T、E、K、V等形式的参数常用于表示泛型形参,用以在实际使用中接受泛型的实参

    泛型的应用总结

    普通泛型

    class Point<T> { // 此处可以随便写标识符号,T是type的简称
        private T var; // var的类型由T指定,即:由外部指定
        public T getVar() { // 返回值的类型由外部决定
            return var;
        }
    
        public void setVar(T var) { // 设置的类型也由外部决定
            this.var = var;
        }
    }
    
    public class GenericsDemo06 {
        public static void main(String[] args) {
            Point<String> p = new Point<String>(); // 里面的var类型为String类型
            p.setVar("it"); // 设置字符串
            System.out.println(p.getVar().length()); // 取得字符串的长度
        }
    }
    ----------------------------------------------------------
    class Notepad<K, V> { // 此处指定了两个泛型类型
    
        private K key; // 此变量的类型由外部决定
        private V value; // 此变量的类型由外部决定
    
        public K getKey() {
            return this.key;
        }
    
        public V getValue() {
            return this.value;
        }
    
        public void setKey(K key) {
            this.key = key;
        }
    
        public void setValue(V value) {
            this.value = value;
        }
    }
    
    public class GenericsDemo09 {
        public static void main(String[] args) {
            Notepad<String, Integer> t = null; // 定义两个泛型类型的对象
            t = new Notepad<String, Integer>(); // 里面的key为String,value为Integer
            t.setKey("汤姆"); // 设置第一个内容
            t.setValue(20); // 设置第二个内容
            System.out.print("姓名;" + t.getKey()); // 取得信息
            System.out.print(",年龄;" + t.getValue()); // 取得信息
        }
    }
    

    通配符

    	class Info<T> {
    	    private T var; // 定义泛型变量
    	    public void setVar(T var) {
    	        this.var = var;
    	    }
    	
    	    public T getVar() {
    	        return this.var;
    	    }
    	
    	    public String toString() { // 直接打印
    	        return this.var.toString();
    	    }
    	}
    	
    	
    	public class GenericsDemo14 {
    	    public static void main(String[] args) {
    	        Info<String> i = new Info<String>(); // 使用String为泛型类型
    	        i.setVar("it"); // 设置内容
    	        fun(i);
    	    }
    	
    	    public static void fun(Info<?> temp) { // 可以接收任意的泛型对象
    	        System.out.println("内容:" + temp);
    	    }
    	}
    

    受限泛型

    	class Info<T> {
    	    private T var; // 定义泛型变量
    	    public void setVar(T var) {
    	        this.var = var;
    	    }
    	
    	    public T getVar() {
    	        return this.var;
    	    }
    	
    	    public String toString() { // 直接打印
    	        return this.var.toString();
    	    }
    	}
    	
    	public class GenericsDemo17 {
    	    public static void main(String[] args) {
    	        Info<Integer> i1 = new Info<Integer>(); // 声明Integer的泛型对象
    	        Info<Float> i2 = new Info<Float>(); // 声明Float的泛型对象
    	        i1.setVar(30); // 设置整数,自动装箱
    	        i2.setVar(30.1f); // 设置小数,自动装箱
    	        fun(i1);
    	        fun(i2);
    	    }
    	
    	    public static void fun(Info<?extends Number> temp) { // 只能接收Number及其Number的子类
    	        System.out.print(temp + "、");
    	    }
    	}
    -------------------------------------------------
    class Info<T> {
        private T var; // 定义泛型变量
        public void setVar(T var) {
            this.var = var;
        }
    
        public T getVar() {
            return this.var;
        }
    
        public String toString() { // 直接打印
            return this.var.toString();
        }
    }
    
    public class GenericsDemo21 {
        public static void main(String[] args) {
            Info<String> i1 = new Info<String>(); // 声明String的泛型对象
            Info<Object> i2 = new Info<Object>(); // 声明Object的泛型对象
            i1.setVar("hello");
            i2.setVar(new Object());
            fun(i1);
            fun(i2);
        }
    
        public static void fun(Info<?super String> temp) { // 只能接收String或String父类的泛型
            System.out.print(temp + "、");
        }
    }
    

    Java泛型无法向上转型

    class Info<T> {
        private T var; // 定义泛型变量
        public void setVar(T var) {
            this.var = var;
        }
    
        public T getVar() {
            return this.var;
        }
    
        public String toString() { // 直接打印
            return this.var.toString();
        }
    }
    
    public class GenericsDemo23 {
        public static void main(String[] args) {
            Info<String> i1 = new Info<String>(); // 泛型类型为String
            Info<Object> i2 = null;
            i2 = i1; // 这句会出错 incompatible types
        }
    }
    

    Java泛型接口

    interface Info<T> { // 在接口上定义泛型
    	public T getVar(); // 定义抽象方法,抽象方法的返回值就是泛型类型
    }
    	
    class InfoImpl<T> implements Info<T> { // 定义泛型接口的子类
    	private T var; // 定义属性
    
    	public InfoImpl(T var) { // 通过构造方法设置属性内容
    		this.setVar(var);
    	}
    		
    	public void setVar(T var) {
    		this.var = var;
    	}
    	
    	 public T getVar() {
    	    return this.var;
    	 }
    }
    	
    public class GenericsDemo24 {
    	 public static void main(String[] arsg) {
    	 	Info<String> i = null; // 声明接口对象
    	    i = new InfoImpl<String>("汤姆"); // 通过子类实例化对象
    	    System.out.println("内容:" + i.getVar());
    	 }
    }
    -------------------------------------------------------
    interface Info<T> { // 在接口上定义泛型
        public T getVar(); // 定义抽象方法,抽象方法的返回值就是泛型类型
    }
    
    class InfoImpl implements Info<String> { // 定义泛型接口的子类
        private String var; // 定义属性
    
        public InfoImpl(String var) { // 通过构造方法设置属性内容
            this.setVar(var);
        }
    
        public void setVar(String var) {
            this.var = var;
        }
    
        public String getVar() {
            return this.var;
        }
    }
    
    public class GenericsDemo25 {
        public static void main(String[] arsg) {
            Info i = null; // 声明接口对象
            i = new InfoImpl("汤姆"); // 通过子类实例化对象
            System.out.println("内容:" + i.getVar());
        }
    }
    

    Java泛型方法

    class Demo {
        public <T> T fun(T t) { // 可以接收任意类型的数据
    
            return t; // 直接把参数返回
        }
    }
    
    public class GenericsDemo26 {
        public static void main(String[] args) {
            Demo d = new Demo(); // 实例化Demo对象
            String str = d.fun("汤姆"); // 传递字符串
            int i = d.fun(30); // 传递数字,自动装箱
            System.out.println(str); // 输出内容
            System.out.println(i); // 输出内容
        }
    }
    

    通过泛型方法返回泛型类型实例

    	class Info<T extends Number> { // 指定上限,只能是数字类型
    	    private T var; // 此类型由外部决定
    	
    	    public T getVar() {
    	        return this.var;
    	    }
    	
    	    public void setVar(T var) {
    	        this.var = var;
    	    }
    	
    	    public String toString() { // 覆写Object类中的toString()方法
    	        return this.var.toString();
    	    }
    	}
    	public class GenericsDemo27 {
    	    public static void main(String[] args) {
    	        Info<Integer> i = fun(30);
    	        System.out.println(i.getVar());
    	    }
    	
    	    public static <T extends Number> Info<T> fun(T param) { // 方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
    	
    	        Info<T> temp = new Info<T>(); // 根据传入的数据类型实例化Info
    	        temp.setVar(param); // 将传递的内容设置到Info对象的var属性之中
    	
    	        return temp; // 返回实例化对象
    	    }
    	}
    

    使用泛型统一传入的参数类型

    class Info<T> { // 指定上限,只能是数字类型
        private T var; // 此类型由外部决定
    
        public T getVar() {
            return this.var;
        }
    
        public void setVar(T var) {
            this.var = var;
        }
    
        public String toString() { // 覆写Object类中的toString()方法
            return this.var.toString();
        }
    }
    
    public class GenericsDemo28 {
        public static void main(String[] args) {
            Info<String> i1 = new Info<String>();
            Info<String> i2 = new Info<String>();
            i1.setVar("HELLO"); // 设置内容
            i2.setVar("汤姆"); // 设置内容
            add(i1, i2);
        }
    
        public static <T> void add(Info<T> i1, Info<T> i2) {
            System.out.println(i1.getVar() + " " + i2.getVar());
        }
    }
    

    Java泛型数组

    	public class GenericsDemo30 {
    	    public static void main(String[] args) {
    	        Integer[] i = fun1(1, 2, 3, 4, 5, 6); // 返回泛型数组
    	        fun2(i);
    	    }
    	    public static <T> T[] fun1(T... arg) { // 接收可变参数
    	        return arg; // 返回泛型数组
    	    }
    	
    	    public static <T> void fun2(T[] param) { // 输出
    	        System.out.print("接收泛型数组:");
    	        for (T t : param) {
    	            System.out.print(t + "、");
    	        }
    	    }
    	}
    

    Java泛型的嵌套设置

    class Info<T, V> { // 接收两个泛型类型
        private T var;
        private V value;
    
        public Info(T var, V value) {
            this.setVar(var);
            this.setValue(value);
        }
    
        public void setVar(T var) {
            this.var = var;
        }
    
        public void setValue(V value) {
            this.value = value;
        }
    
        public T getVar() {
            return this.var;
        }
    
        public V getValue() {
            return this.value;
        }
    }
    
    class Demo<S> {
        private S info;
    
        public Demo(S info) {
            this.setInfo(info);
        }
    
        public void setInfo(S info) {
            this.info = info;
        }
    
        public S getInfo() {
            return this.info;
        }
    }
    
    public class GenericsDemo31 {
        public static void main(String[] args) {
            Demo<Info<String, Integer>> d = null; // 将Info作为Demo的泛型类型
            Info<String, Integer> i = null; // Info指定两个泛型类型
            i = new Info<String, Integer>("汤姆", 30); // 实例化Info对象
            d = new Demo<Info<String, Integer>>(i); // 在Demo类中设置Info类的对象
            System.out.println("内容一:" + d.getInfo().getVar());
            System.out.println("内容二:" + d.getInfo().getValue());
        }
    }
    
    展开全文
  • Java泛型

    2017-08-08 11:17:55
    Java泛型
  • java 泛型

    2019-01-08 11:10:10
    Java泛型详解:和Class的使用。泛型类,泛型方法的详细使用实例 Java泛型深入理解 java泛型你需要知道的一切 Java 泛型教程 以下基于毕向东java教学中有关泛型的内容进行概述: 1、泛型:generic,简单理解为...
  • Java 泛型

    千次阅读 2020-09-08 12:46:35
    Java 泛型 Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数...
  • JAVA泛型

    千次阅读 2019-09-19 21:15:47
    JAVA泛型1 概念2 泛型方法( )3 泛型类 4 类型通配符 ?5 类型擦除 1 概念 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定...
  • java泛型详解

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

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 56,132
精华内容 22,452
热门标签
关键字:

java泛型

java 订阅