Java泛型定义与用法实例详解

 更新时间:2018年08月27日 10:54:21   作者:喜欢特别冷的冬天下着雪  
这篇文章主要介绍了Java泛型定义与用法,结合实例形式较为详细的分析了Java中泛型的概念、原理、定义、使用方法及相关操作注意事项,需要的朋友可以参考下

本文实例讲述了Java泛型定义与用法。分享给大家供大家参考,具体如下:

1. 泛型的由来

先看如下代码:

import java.util.List;
import java.util.ArrayList;
public class TestGeneric {
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add("1");
        list.add(new Object());
        System.out.println(list);
        // 取值
        Integer var1 = (Integer) list.get(0);
        String var2 = (String) list.get(1);
        Object var3 = list.get(2);
        System.out.println(var1 + " " + var2 + " " + var3);
    }
}

运行结果:

[1, 1, java.lang.Object@1db9742]
1 1 java.lang.Object@1db9742

这段代码很简单,将整形、字符串、对象放进list集合中,然后逐一取出。可以看出,由于List接口在定义时并不知道元素的类型,因此默认为Object,即任意类型元素进入list集合后都会自动装箱。而取值的过程更为复杂,所有取得的值都是装箱后的Object对象,必须得知道每一个元素的初始类型才能拆箱。一般使用集合的时候,集合的元素往往都是具有共同特征的,比如同属于一类的----那么,如果一开始限定了list集合元素的类型,那么就可避免上述不规范操作。代码如下,

import java.util.List;
import java.util.ArrayList;
public class TestGeneric {
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        // list.add(1);//报错
        // list.add(new Object());//报错
        list.add("1");
        // 取值
        String var1 = list.get(0);// 无需转换
    }
}

如此一来,便有了泛型集合的说法。实际上,查阅List接口的Api会发现,List接口正是泛型接口,它可以接受一个类型参数E,若不传递参数,则默认是Object类型。

2. 泛型类型的继承关系

有如下功能的代码,实现打印任意集合的元素:

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric{
    //打印任意集合元素
    public void print(Collection<Object> c){
     System.out.println(c);
    }
    public static void main(String[] args){
     List<String> list=new ArrayList<String>();
     new TestGeneric().print(list);
 }
}

输出:

TestGeneric.java:11: 无法将 TestGeneric 中的 print(java.util.Collection<java.lang.Object>) 应用于 (java.util.List<java.lang.String>)
   new TestGeneric().print(list);
                    ^
1 错误

很明显,意思就是传递的参数类型不匹配。难道String不是继承自Object的吗?没错,String是继承自Object的,但是List<String>List<Object>是截然不同的两个类型,两者之间没有任何继承关系。那如果真的要实现上面的功能,该如何呢?

2.1 类型通配符

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
    // 打印任意集合元素
    public void print(Collection<?> c) {
        System.out.println(c);
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        new TestGeneric().print(list);
    }
}

程序正常执行,这里的?表示一个未知类型,这个未知类型与Object不同,List<?>代表了所有的List<类型>的父类。

2.2 泛型方法

不只有通配符可以解决泛型继承的问题,若将上面的方法定义为泛型方法也具有同样的效果:

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
    // 打印任意集合元素
    public <T> void print(Collection<T> c) {
        System.out.println(c);
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        new TestGeneric().print(list);
    }
}

泛型方法的定义形式如下,

修饰符 <T,E> 返回值 方法名(形参)

其中<T,E>在修饰符的后面做为类型定义,为方法指明形参中需要用到的T,E类型是来自哪里。既然泛型方法和类型通配符都可以实现泛型中的继承,那么有什么区别?

2.3 泛型方法和通配符的区别

看如下代码:

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
    // 打印任意集合元素
    public <E, T extends E> void print(Collection<T> c1, Collection<E> c2) {
        System.out.println(c1);
        System.out.println(c2);
    }
    public static void main(String[] args) {
        List<Father> list1 = new ArrayList<Father>();
        List<Father> list2 = new ArrayList<Father>();
        new TestGeneric().print(list1, list2);// 传2个father类型
        List<Child> list3 = new ArrayList<Child>();
        List<Father> list4 = new ArrayList<Father>();
        new TestGeneric().print(list3, list4);// T为child,E为father
        List<Father> list5 = new ArrayList<Father>();
        List<Child> list6 = new ArrayList<Child>();
        new TestGeneric().print(list5, list6);// T为father,E为child,报错
    }
}
class Father {
}
class Child extends Father {
}
class Other {
}

上述泛型方法在定义T,E时已经指明了关系:T是E的子类,所以在传递参数的时候,T要么是E的子类,要么就是E本身,所以在传递关系不小心变为E exends T时,在第三次调用方法时报错了。而如果把上述代码换成?通配符的话,则不具有如此强的限定关系。

总之,泛型方法和?通配符都可以实现未知类型的继承,但是泛型方法主要强调多个未知类型之间的依赖关系。如果只是单纯用作成为一个通用类型的父类这一功能的话,两者都可以实现,反而?通配符较为简洁明了。

2.4 泛型参数上、下限的注意

看如下代码:

import java.util.List;
import java.util.ArrayList;
import java.util.Collection;
public class TestGeneric {
    // 复制集合并返回原始集合的最后一个元素
    public <T> T copy(Collection<T> des, Collection<? extends T> src) {
        T lastElement = null;
        for (T t : src) {
            lastElement = t;
            des.add(t);
        }
        return lastElement;
    }
    public static void main(String[] args) {
        List<Number> des = new ArrayList<Number>();
        List<Integer> src = new ArrayList<Integer>();
        src.add(new Integer(1));
        Integer lastElement = new TestGeneric().copy(des, src);//
        System.out.println(lastElement.getClass());
    }
}

输出:

TestGeneric.java:18: 不兼容的类型
找到: java.lang.Number
需要: java.lang.Integer
Integer lastElement= new TestGeneric().copy(des,src);//
                                              ^
1 错误

当调用完copy方法后,系统比对发现T类型为Number,?类型为Integer。所以函数返回的T类型是Number了,所以根本不兼容Integer。要修改上面的代码,有俩个办法,

方法1:

改为

Number lastElement=new TestGeneric().copy(des,src);

分析代码可以得出,?为T的子类,在方法中T=lastElement这句表现为多态,虽然返回的是T类型,但是多态的表现为?类型,即Interger类型,调用lastElement.getClass()也可发现返回的是java.lang.Integer类型,说明此处编译类型为T类型,实际运行类型为?类型。这就好比如下多态转换,

Father f=new Child();
Child c=f;//此处一定报错,类型不兼容

虽然f的多态表现为子类Child,但是上面一句连语法检测都过不了。这也就是为什么上面Integer不能兼容Number的原因了。

方法2:

改为

public <T> T copy(Collection<? super T> des,Collection<T> src)

这样一来,?类型变为了父类,T类型变为了子类,于是在方法中返回的T类型对象,即lastElement就不具有多态性了。泛型中的上下限是很有学问的,每次看源码时都会琢磨很久,但还是会在浩瀚的接口+泛型的设计中昏迷,这种设计真的完全是为了突出面向对象的特性,以后慢慢琢磨吧。

从这也再次可以看出?通配符在处理具有依赖关系的泛型方法中,显得过于灵活而会导致一些潜在的隐患。

更多关于java算法相关内容感兴趣的读者可查看本站专题:《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总

希望本文所述对大家java程序设计有所帮助。

相关文章

  • Lombok为啥这么牛逼?SpringBoot和IDEA官方都要支持它

    Lombok为啥这么牛逼?SpringBoot和IDEA官方都要支持它

    Lombok是一款Java代码功能增强库,在Github上已有9.8k+Star。这篇文章主要介绍了Lombok为啥这么牛逼?SpringBoot和IDEA官方都要支持它,需要的朋友可以参考下
    2020-12-12
  • spring源码阅读--aop实现原理讲解

    spring源码阅读--aop实现原理讲解

    这篇文章主要介绍了spring源码阅读--aop实现原理讲解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 自定义一个简单的JDBC连接池实现方法

    自定义一个简单的JDBC连接池实现方法

    下面小编就为大家分享一篇自定义一个简单的JDBC连接池实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • idea 实现git rebase操作应用场景

    idea 实现git rebase操作应用场景

    本文结合idea工具进行rebase的各种场景的操作,借助工具更能直观地观察到分支之间地操作差异,方便我们理解rebase的各种操作以及场景的使用,对idea  git rebase操作知识感兴趣的朋友一起看看吧
    2024-01-01
  • 详解SpringBoot文件上传下载和多文件上传(图文)

    详解SpringBoot文件上传下载和多文件上传(图文)

    本篇文章主要介绍了详解SpringBoot文件上传下载和多文件上传(图文),具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-02-02
  • Java开启新线程并传参方法代码实现

    Java开启新线程并传参方法代码实现

    这篇文章主要介绍了Java开启新线程并传参方法代码实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java趣味练习题之输出两个日期之间的相隔天数

    Java趣味练习题之输出两个日期之间的相隔天数

    本篇文章介绍了我看到的一个趣味小题目,怎么求得两个日期之间相隔的天数,以及解决该题目的过程及思路,通读本篇对大家的学习或工作具有一定的价值,需要的朋友可以参考下
    2021-10-10
  • Java实现文件上传的方法

    Java实现文件上传的方法

    这篇文章主要为大家详细介绍了Java实现文件上传的方法,供大家参考,感兴趣的朋友可以参考一下
    2016-05-05
  • java根据模板导出PDF的详细实现过程

    java根据模板导出PDF的详细实现过程

    前段时间因为相关业务需求需要后台生成pdf文件,所以下面这篇文章主要给大家介绍了关于java根据模板导出PDF的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-02-02
  • Java 创建动态类和查看方法列表信息的实例

    Java 创建动态类和查看方法列表信息的实例

    这篇文章主要介绍了 Java 创建动态类和查看方法列表信息的实例的相关资料,需要的朋友可以参考下
    2017-06-06

最新评论