一、基本介绍
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

二、提出背景
Java集合(Collection)中元素的类型是多种多样的。例如,有些集合中的元素是Byte类型的,而有些则可能是String类型的,等等。Java允许程序员构建一个元素类型为Object的Collection,其中的元素可以是任何类型在Java SE 1.5之前,没有泛型(Generics)的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要作显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以在预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。因此,为了解决这一问题,J2SE 1.5引入泛型也是自然而然的了。

1、代码实例
package javase.genericity;

import java.util.ArrayList;
import java.util.List;

public class Test {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(“CSDN”);
list.add(“素小暖”);
list.add(29);
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println("泛型测试,str = " + str);
}
}
}
2、控制台输出

崩溃了。

然而为什么呢?

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

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

定义泛型之后,编译都通不过了,要的就是这个效果!

三、泛型的优缺点
1、优点
(1)类型安全
泛型的主要目的是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。没有泛型,这些假设就只能存在于系统开发人员的头脑中。

通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误就可以在编译时被捕获了,而不是在运行时当作ClassCastException展示出来。将类型检查从运行时挪到编译时有助于Java开发人员更早、更容易地找到错误,并可提高程序的可靠性。

(2)消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。尽管减少强制类型转换可以提高使用泛型类的代码的累赞程度,但是声明泛型变量时却会带来相应的累赞程度。在简单的程序中使用一次泛型变量不会降低代码累赞程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低累赞程度。所以泛型消除了强制类型转换之后,会使得代码加清晰和筒洁。

(3)更高的效率
在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。

(4)潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。

四、使用泛型时的注意事项
1、在定义一个泛型类时,在“<>”之间定义形式类型参数,例如:“class TestGen<K,V>”,其中“K”,“V”不代表值,而是表示类型。

2、实例化泛型对象时,一定要在类名后面指定类型参数的值(类型),一共要有两次书写。

3、使用泛型时,泛型类型必须为引用数据类型,不能为基本数据类型,Java中的普通方法,构造方法,静态方法中都可以使用泛型,方法使用泛型之前必须先对泛型进行声明,可以使用任意字母,一般都要大写。

4、不可以定义泛型数组。

5、在static方法中不可以使用泛型,泛型变量也不可以用static关键字来修饰。

6、根据同一个泛型类衍生出来的多个类之间没有任何关系,不可以互相赋值。

7、泛型只在编译器有效

8、instanceof不允许存在泛型参数

以下代码不能通过编译,原因一样,泛型类型被擦除了

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

1、泛型类
package javase.genericity;

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic {
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
//泛型构造方法形参key的类型也为T,T的类型由外部指定
public Generic(T key){
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的类型由外部指定
public T getKey(){
return key;
}

public static void main(String[] args) {//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型//传入的实参类型需与泛型的类型参数类型相同,即为Integer.Generic<Integer> genericInteger = new Generic<Integer>(123456);//传入的实参类型需与泛型的类型参数类型相同,即为String.Generic<String> genericString = new Generic<String>("江疏影");System.out.println("泛型测试,key is "+genericInteger.getKey());System.out.println("泛型测试,key is "+genericString.getKey());
}

}

泛型参数就是随便传的意思!

Generic generic = new Generic(“111111”);
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);
2、泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

//定义一个泛型接口
public interface Generator {
public T next();
}
当实现泛型接口的类,未传入泛型实参时:

/**

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

package javase.genericity;

import java.util.Random;

public class FruitGenerator implements Generator{
String[] fruits = new String[]{“apple”,“banana”,“Pear”};
@Override
public String next() {
Random random = new Random();
System.out.println(fruits[random.nextInt(3)]);
return fruits[random.nextInt(3)];
}

public static void main(String[] args) {FruitGenerator ff = new FruitGenerator();ff.next();
}

}

3、泛型通配符
我们知道integer是number的一个子类,同时Generic和Generic实际上是相同的一种基本类型。那么问题来了,在使用Generic作为形参的方法中,能否使用Generic的实例传入呢?在逻辑上类似于Generic和Generic是否可以看成具有父子关系的泛型类型呢?

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

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

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

类型通配符一般是使用?代替具体的类型参数,注意了,此处?是类型实参,而不是类型形参。此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。

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

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

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

package javase.genericity;

public class Test {
public static void main(String[] args) {
try {
Object CSDN = genericMethod(Class.forName(“javase.genericity.CSDN”));
System.out.println(CSDN);
Object OSCHINA = genericMethod(Class.forName(“javase.genericity.Oschina”));
System.out.println(OSCHINA);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

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

}

泛型方法与可变参数
如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

package javase.genericity;

public class GenericFruit {
//静态方法中使用泛型,必须要将泛型定义在方法上。
public static void printMsg(T…args){
for(T t:args){
System.out.println("泛型测试,it is "+t);
}
}

public static void main(String[] args) {printMsg("1111",2222,"江疏影","0.00",55.55);
}

}

尽量使用泛型方法!

六、泛型上下边界
1、设定通配符上限
首先,我们来看一下设定通配符上限用在哪里…

现在,我想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做???

我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了。因此我们需要用到设定通配符上限

2、设定通配符下限
既然上面我们已经说了如何设定通配符的上限,那么设定通配符的下限也不是陌生的事了。直接来看语法吧

//传递进来的只能是Type或Type的父类

<? super Type> 设定通配符的下限这并不少见,在TreeSet集合中就有....我们来看一下 public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } 那它有什么用呢??我们来想一下,当我们想要创建一个TreeSet类型的变量的时候,并传入一个可以比较String大小的Comparator。 那么这个Comparator的选择就有很多了,它可以是Comparator,还可以是类型参数是String的父类,比如说Comparator.... 这样做,就非常灵活了。也就是说,只要它能够比较字符串大小,就行了 在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super) 带有子类限定的可以从泛型读取【也就是--->(? extend T)】-------->Producer Extends 带有超类限定的可以从泛型写入【也就是--->(? super T)】-------->Consumer Super 七、泛型擦除 泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。 八、兼容性 JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。 当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。 值得注意的是:它保留的就类型参数的上限。 List list = new ArrayList<>(); //类型被擦除了,保留的是类型的上限,String的上限就是Object List list1 = list; 它也不会报错,仅仅是提示“未经检查的转换”。 九、泛型的应用 当我们写网页的时候,常常会有多个Dao,我们要写每次都要写好几个Dao,这样会有点麻烦。 那么我们想要的效果是什么呢??只写一个抽象Dao,别的Dao只要继承该抽象Dao,就有对应的方法了。 要实现这样的效果,肯定是要用到泛型的。因为在抽象Dao中,是不可能知道哪一个Dao会继承它自己,所以是不知道其具体的类型的。而泛型就是在创建的时候才指定其具体的类型。 1、抽象dao package javase.genericity.dao; import javase.genericity.entity.Worker; import javax.jms.Session; import java.lang.reflect.ParameterizedType; public abstract class BaseDao { private Session session; private Class clazz; //哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要) public BaseDao(){ Class clazz = this.getClass(); //拿到的是子类 ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass(); //BaseDao clazz = (Class) pt.getActualTypeArguments()[0]; System.out.println(clazz); } public void add(T t){ System.out.println(t+",增加"); } public T find(String id){ System.out.println("查找"+id); Worker worker = null; return (T)worker; } public void update(T t){ System.out.println(t+",更新"); } public void delete(String id){ System.out.println("删除"+id); } } 2、WorkerDao ,继承抽象DAO 该实现类就有对应的增删改查的方法了。 package javase.genericity.dao; import javase.genericity.entity.Worker; public class WorkerDao extends BaseDao{ public static WorkerDao instance = new WorkerDao(); @Override public void add(Worker worker) { super.add(worker); } @Override public Worker find(String id) { return super.find(id); } public static void main(String[] args) { WorkerDao.instance.add(new Worker(1,"素小暖")); } } package javase.genericity.dao; import javase.genericity.entity.Teacher; public class TeacherDao extends BaseDao { } 3、Worker 实体类  package javase.genericity.entity; public class Worker { private int id; private String name; public Worker(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Worker{" + "id=" + id + ", name='" + name + '\'' + '}'; } } 4、控制台输出  ———————————————— 版权声明:本文为CSDN博主「GooReey」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/guorui_java/article/details/106808115

Java知识点详解 4 泛型相关推荐

  1. Java知识点详解 6 注解

    一.注解简介 Java注解用于为Java代码提供元数据. 元数据是指用来描述数据的数据,通俗一点,就是描述代码间关系,或者代码与其它资源(例如数据库表)之间内在联系的数据.在一些技术框架中,如Stru ...

  2. 【Java知识点详解 10,如何在面试中通过工厂模式来给自己加分

    (1)设置参数 path环境变量的作用就是告诉系统,当要求系统运行一个程序而没有告诉它程序所在的完整路径时,系统除了在当前目录下面寻找此程序外,还应到哪些目录下去寻找.而我们通常情况下配置的path变 ...

  3. 【Java知识点详解 8】缓存

  4. 【Java知识点详解 1】缓存

    为了缓解这个问题,就需要通过"预读取"来解决. 可能你会玩,哪怕用了缓存还是扛不住吗?那就是做横向扩展和负载均衡的时候了,这不是本文讨论的内容,有机会再专门分享吧. 如果说&quo ...

  5. java中流_Java中流的有关知识点详解

    Java中流的有关知识点详解 发布时间:2020-09-17 03:50:59 来源:脚本之家 阅读:103 作者:mumu1998 什么是流? 流:程序和设备之间连接起来的一根用于数据传输的管道,流 ...

  6. Java虚拟机详解----JVM常见问题总结

    [正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考本人之前的系列文章,尤其是那篇:Java虚拟机详解04----GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾 ...

  7. Java集合详解4:HashMap和HashTable

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

  8. java 7 反射_【7】java 反射详解

    [7]java 反射详解 获取Class对象的方式: 1. Class.forName("全类名"); 将字节码加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件 ...

  9. Apache Thrift - java开发详解

    2019独角兽企业重金招聘Python工程师标准>>> Apache Thrift - java开发详解 博客分类: java 架构 中间件 1.添加依赖 jar <depen ...

最新文章

  1. Puppet客户端自动安装脚本
  2. APUE-文件和目录(二)函数access,mask,chmod和粘着位
  3. 《嵌入式C编程:PIC单片机和C编程技术与应用》一1.2 注释
  4. P4450-双亲数,P5221-Product,P6055-[RC-02]GCD【莫比乌斯反演,杜教筛】
  5. docker 镜像选择_为什么选择Docker?
  6. 基于Silverlight+WCF的SAAS开发平台TAP(二)之核心技术
  7. Mybatis经验总结
  8. 计算机系统结构计算题
  9. 2010年程序员的最后一天!
  10. 删除链表的倒数第K个结点
  11. cNode------路由设置以及项目基本框架搭建
  12. win10共享打印机怎么设置_怎样设置打印机共享?
  13. excel姓名转拼音
  14. 图片加密信息(16进制)
  15. 手机端 19FPS 的实时目标检测算法:YOLObile
  16. Verilog中parameter使用
  17. 如何在网页点击按钮显示的图片
  18. 达特茅斯学院计算机科学专业,2020年达特茅斯学院排名TFE Times美国最佳计算机科学硕士专业排名第42...
  19. linux执行系统命令卡死问题解决
  20. DHCP、PXE自动化部署操作系统、DNS正向解析、NFC共享目录服务--(程序员心碎的一天,碎了的自行粘上)

热门文章

  1. java 拆箱 类型不对,Java基本类型于对象类型的拆箱和装箱
  2. ubuntu安装php7-mysql,ubuntu上安装php7.0+nginx+mysql
  3. mysql安装之后怎么进入_MySQL常规练习 .MySQL安装成功后的进入方式
  4. OpenShift 4 - Istio-Tutorial (2) 部署三个微服务
  5. 边缘设备上的实时AI虫害消除:入门
  6. 使用TensorFlow.js进行AI在网络摄像头中翻译手势和手语
  7. Tableau中的行级数据安全性——第1部分
  8. VS 2019 for Mac 第 3 个预览版发布,新编辑器、Xamarin 开发改进
  9. java流被关闭后怎样重新打开,java – 如何停止MediaPlayer流然后重新启动它? Android的...
  10. hql取满足条件最新一条记录_数据仓库怎么做拉链表记录数据变化情况,看看这篇文章 就明白了...