一. 泛型是什么

“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如List<Integer> iniData = new ArrayList<>()

二. 使用泛型有什么好处

以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。

我们以ArrayList为例,假如我们要将本月截至今天的日期放到ArrayList中,如果我们不使用泛型,此时我们定义一个ArrayList.

List monthDays = new ArrayList();

我们向其中加入1号到4号日期

public static List addMonthDays(){
List monthDays = new ArrayList();
monthDays.add(LocalDate.now().withDayOfMonth(1));
monthDays.add(LocalDate.now().withDayOfMonth(2));
monthDays.add(LocalDate.now().withDayOfMonth(3));
monthDays.add(new Date());
return monthDays;
}

这样有没有问题?大家也看出来了,当然有,虽然都可以表示日期,但却用了Date,LocalDate,我们调用方法直接打印出来,就是这样

public static void main(String[] args) {
List monthDays = addMonthDays();
for(Object day : monthDays){
System.out.println(day);
}
}
2019-08-01
2019-08-02
2019-08-03
Sun Aug 04 10:27:10 CST 2019

我们肯定不想要这样的结果,我们想要的是

2019-08-01
2019-08-02
2019-08-03
2019-08-04

如果存储的元素类型只是这两种(假如我们知道),这个时候我们就手动判断一下

public static void main(String[] args) {
List monthDays = addMonthDays();
for(Object day : monthDays){
if (day instanceof Date){
Date date = (Date) day;
System.out.println(LocalDate.of(date.getYear(), date.getMonth(), date.getDay()));
}else {
System.out.println(day);
}
}
}

这个时候我们就可以达成上述目的了,但大家也知道,这种写法问题问题很大

  1. 我们无法控制存储的元素到底是否和日期相关,如我们存储了“1”,65536等非日期,定义的方法也不会报错,但在调用进行类型转换的时候必然会报错;
  2. 我们不知道集合中到底存储了哪些类型的元素,比如还有“2019/08/04”这种日期字符串、java.sql.Date这种类型呢,我们很难保证可以穷尽;
  3. 代码过于复杂,太难维护;

这时泛型就提供了很好的解决方案,从源头上就制定好规则,只能添加LocalDate类型,那么上述的问题就都得以解决了。

public static List<LocalDate> addFormatMonthDays(){
List<LocalDate> monthDays = new ArrayList<>();
monthDays.add(LocalDate.now().withDayOfMonth(1));
monthDays.add(LocalDate.now().withDayOfMonth(2));
monthDays.add(LocalDate.now().withDayOfMonth(3));
monthDays.add(new Date());//这个代码在编译期间就无法通过了
return monthDays;
}

不仅提高了代码的可读性,一眼即可看出我们存储的是LocalDate类型。同时编译器也可很好的利用该信息,编译期间就可进行类型检查,保证了安全性,在get的时候,无需进行强制类型转换。

三. 泛型类

为使类适应更多的情况,具备更好的扩展性,我们可以将其设置为泛型类,即具有一个或多个类型变量的类,写法如下:

public class ClassName<泛型标识,可以是字母、中文字符等,不过一般用大写英文字符> {
private 泛型标识 property;
}

对于泛型标识符,一般有一些约定俗称的写法,如果是表示集合的元素类型,一般用字母E,如我们常用的ArrayList,public class ArrayList<E>,我们自定义如下:

//车库
public class Garage<E> {
//向车库中添加车
public void add(E car){
//...
}
}

我们还可以用字符K和V表示关键字和值的类型,如我们常用的HashMap,public class HashMap<K,V>,我们也可以自定义如下:

//映射关系
//K,V: 蔬菜,是否有机; 水果,产地; 服装,类型; 汽车,品牌
public class Mapping<K, V> { private K key; private V value;
}

我们还经常用一个字符T表示类型

public class Person<T> {
private T t;
public Person(T t){
this.t = t;
}
public void run(){
System.out.println(t);
}
}

如何使用泛型类,类型如何实例化,我们只需要保证传入的实参类型和泛型参数类型相同即可。

//正常用法
Person<String> s1 = new Person<String>("张三");
//jdk7.0后可以省略后面的参数类型
Person<String> s2 = new Person<>("张三");
s2.run(); //当然泛型的定义是可以帮助我们按照某种规则去做事,如果不做限制,也不会编译错误,但泛型就毫无意义了
Person t = new Person(111);
t.run(); //泛型的类型不能是八大基本类型,下面会编译出错
Person<int> s = new Person<>(1);

四. 泛型接口

泛型接口和泛型类的定义基本一致,定义如下:

public interface Person<T> {
public T parent();
public String eat();
}

当我们定义了一个类要实现该接口时,那么该类的泛型类型必须和接口类的泛型类型一致,未传递实参的情况下,继续使用泛型类型T,传递了实参的情况下,泛型类型必须使用实参类型

public class Teacher<T> implements Person<T> {
@Override
public T parent() {
return null;
} @Override
public String eat() {
return null;
}
}
//Teacher不必再定义类型了,因为泛型类型在Person处已经定义好了
public class Teacher implements Person<Integer> {
//这里的返回类型必须为Integer,否则必须出错
@Override
public Integer parent() {
return null;
} @Override
public String eat() {
return null;
}
}

五. 泛型方法

泛型方法可以定义在普通类和泛型类中,比如泛型类更为常用,一般能用泛型方法解决的问题优先使用泛型方法而不使用泛型类,类型变量放在修饰符的后面,如public static ,public final等的后面。

public class Teacher {
public static <T> T println(T t){
System.out.println(t);
return t;
}
}

调用很简单,很一般方法调用是一样的,更方便的是类型不像一般方法做了限定。

String s = Teancher.println("str");

另外需要说明的是,定义在泛型类中的泛型方法的泛型变量之间是没有关系的,如这样的代码

public class Teacher<T> {
T teacher;
public Teacher(T t){
this.teacher = t;
}
public <T> T println(T t){
System.out.println(t);
return t;
}
}
Teacher<String> teacher = new Teacher<>("张三");
Integer in = teacher.println(123456);

类泛型类型为String,方法的泛型类型为Integer,虽然都是用T来表示的。

同时关于泛型方法需要说明的是:

在修饰符public xx与方法名之间非常重要,有< T >这样的才算是泛型方法;仅仅使用了泛型变量并不算是泛型方法。

六. 限定类型变量

不论是泛型类还是泛型方法,目前来说其实都是没有做类型限定,无论我们传递什么样类型的变量进去都可以,因为我们在处理逻辑中并没有使用到该类型特有的东西(成员变量、方法等)。假如我们想传递的参数类型仅仅是某个大类(父类)下面的一些小类(子类),那么怎么做呢?

public class ArrayFlag {
public static <T> T getMax(T[] array){
if(array == null || array.length == 0){
return null;
}
T maxValue = array[0];
for(int i = 0; i < array.length; i++){
if(array[i].compareTo(maxValue) > 0){
maxValue = array[i];
}
}
return maxValue;
}
}

大家也看到了我们在getMax方法中使用了compareTo方法进行比较,但如果我们传入的类型T没有compareTo方法呢,岂不是要报错,因此我们需要做限定,只要限定了是Comparable接口的必然具备compareTo方法,那么改造后就成了这样

public class ArrayFlag {
public static <T extends Comparable> T getMax(T[] array){
if(array == null || array.length == 0){
return null;
}
T maxValue = array[0];
for(int i = 0; i < array.length; i++){
if(array[i].compareTo(maxValue) > 0){
maxValue = array[i];
}
}
return maxValue;
}
}

同时需要说明的是,此处用的是extends关键字,extends在这里是表示的是绑定了Comparable接口及其子类型,是“绑定、限定”的意思,非“继承”的意思,后面也可以是接口或者类,如果有多个限制,可以使用&分隔,如:

public static <T extends Comparable & Serializable> T getMax(T[] array)

七. 泛型通配符

举个例子,定义了一个书籍类和一个小说类

//定义了一个书籍类
public class Book {}
//定义了一个小说书籍类继承书籍类
public class Novel extends Book {}

我们再定义一个书柜类用来装书籍以及小说

//定义了一个书柜类用来装书
public class Bookcase<T> {
T b;
public Bookcase(T t){
this.b = t;
}
public void set(T t) {
b=t;
}
public T get() {
System.out.println(b.getClass());
return b;
}
}

下面我们就用书柜来装小说

//以前的写法,无法编译通过,提示Incompatible types, Required Book Found Novel
Bookcase<Book> bc = new Bookcase<Novel>(new Novel());

但在jdk7.0后,new Bookcase的时候是可以不用给出泛型类型的,省略的类型可以从变量的类型推断得出,因此如果下面这种写法

Bookcase<Book> bc = new Bookcase<>(new Novel());
System.out.println(bc.getClass());
bc.get();

此时可以编译通过,我们执行后得出的结果是:

class generic.Bookcase
class generic.Novel

当然我们还可以通过通配符来解决该问题,通配符包括以下几种:

上界通配符、下界通配符、无限定通配符

7.1 上界通配符

上界通配符定义方式如下:用extends 关键字,含义是该书柜只能放置小说类书籍(如什么都市小说、爱情小说、玄幻小说都可以),但不能放置父类书籍、其他类如史书、职场类书籍、财经类书籍等,是在使用的时候进行限定,如:

Bookcase<? extends Book> bc = new Bookcase<Novel>(new Novel());

这种定义方式就不会编译错误了。另外关于上界通配符的特点,对上有限制,根据java多态向上造型的原则,不适合频繁插入数据,适合频繁读取数据的场景。

7.2 下界通配符

下界通配符定义方式如下:用super关键字,含义就是书柜放置设置了下限,我们只能放置Book书籍以及Novel书籍,却无法再将细分的都市小说、爱情小说类书籍放进去

Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());

另外关于下界通配符的特点,和上界通配符正好相反,不适合频繁读取数据,适合频繁插入数据的场景。

7.3 无限定通配符

无限定通配符意味着可以使用任何对象,因此使用它类似于使用原生类型。但它是有作用的,原生类型可以持有任何类型,而无限定通配符修饰的容器持有的是某种具体的类型。

举个例子:

List<?> list = new ArrayList<>();
//无法编译通过
list.add(new Object()); //下面这样的却可以添加任何类型
List<Object> list = new ArrayList<>();
list.add(new Object());

再说一下< T > 和< ? >之间的区别,初看好像他们都可以表示泛型变量,都可以extends,但它们确实有不同的使用场景

  • 类型参数< T >声明一个泛型类或泛型方法
  • 无限定通配符< ? >使用泛型类或泛型方法

八. 总结

泛型在java中可以说很常用,我们前面提到的集合类,如ArrayList,HashSet,以及Map都使用到了泛型,泛型也是也是我们再进行一些组件封装经常用到的,本文主要介绍了泛型基本概念,使用泛型的好处,泛型类、接口、方法、通配符的简单介绍以及使用方法,最后泛型一般和反射集合使用,通过泛型可以进行类型的灵活传递,通过反射可获取到实体以及类的数据信息,从而实现一些框架、组件的封装,若有不对之处,请批评指正,望共同进步,谢谢!

Java泛型使用的简单介绍的更多相关文章

  1. Java泛型一:基本介绍和使用

    原文地址http://blog.csdn.net/lonelyroamer/article/details/7864531 现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就 ...

  2. java反射机制的简单介绍

    参考博客: https://blog.csdn.net/mlc1218559742/article/details/52754310 先给出反射机制中常用的几个方法: Class.forName (& ...

  3. Java Linked集合的简单介绍和常用方法的使用

    LinkedList的简单介绍 java.util.LinkedList 集合数据存储的结构是链表结构.LinkedList是一个双向链表在实际开发中,对一个集合元素的添加和删除,经常涉及到首尾操作, ...

  4. Java中NIO的简单介绍

    NIO基本介绍 Java NIO(New IO) 也有人称之为Java non-blocking IO 是从Java1.4版本开始引入的一个新的IO API,可以代替标准的IO API.NIO与原来的 ...

  5. java中数据流的简单介绍

    java中的I/O操作主要是基于数据流进行操作的,数据流表示了字符或者字节的流动序列. java.io是数据流操作的主要软件包 java.nio是对块传输进行的支持 数据流基本概念 “流是磁盘或其它外 ...

  6. Brief introduction to Java String Split 【简单介绍下Java String Split】

    Split is a common function in Java. It split a full string to an array based on delimeter. For examp ...

  7. java.util.HashMap的简单介绍

    1. java.util.HashMap的底层实现是数组+链表. 2. 简介put(key, value)方法的执行过程: 1)通过key值,使用散列算法计算出来一个hash值,用来确定该元素需要存储 ...

  8. Java ArrayList类的简单介绍

    ArrayList类的说明: ArrayList类是List接口的实现类,java.util.ArrayList集合数据存储的结构是数组结构. 特点: 元素增删慢,查找快.(由于日常开发中使用最多的功 ...

  9. Java文件I/O简单介绍

    目录 一.File类 1.1 构造方法 1.2 常用方法 1.3 例子 二.基础I/O:字节流.字符流 2.1 字节流 2.1.1 字节输出流 OutputStream 2.1.2 FileOutpu ...

随机推荐

  1. iOS组件化开发一使用source管理远端库升级(四)

    一.克隆远端库代码到本地选择master分支 1.克隆 2.代码会显示出你所有版本的tag 二.可以在Example目录下验证代码的正确行: cd 到库的文件夹然后 pod install comma ...

  2. Redis在.net 环境下的使用

    Redis概念 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,和Memcached类似,它支持存储的value类型相对更多,包括st ...

  3. flask 请求上下文源码(转)

    本篇阅读目录 一.flask请求上下文源码解读 二.http聊天室(单聊/群聊)- 基于gevent-websocket 回到顶部 转:https://www.cnblogs.com/li-li/p/ ...

  4. InstallShield 2018 打包安装

    关于InstallShield 2018打包安装程序的使用 1. 下载InstallShield2018 建议使用新的版本,毕竟新的版本功能功能全.问题少.用户体验佳. 下载地址:http://www ...

  5. SQL Server 触发器和事务

    一.触发器 1. 定义 --基本语法 create trigger Trigger_Name on TableName for type --type:insert,update,delete as ...

  6. spring的jar包的下载、说明

    spring的jar包官方下载地址:完整链接:https://repo.spring.io/webapp/#/artifacts/browse/tree/General/libs-release-lo ...

  7. Java平台调用Python平台已有算法(附源码及解析)

    1. 问题描述 Java平台要调用Pyhon平台已有的算法,为了减少耦合度,采用Pyhon平台提供Restful 接口,Java平台负责来调用,采用Http+Json格式交互. 2. 解决方案 2.1 ...

  8. 列表 元组 range

    2019 年 7 月 9 日 列表---list------容器 列表:存储数据,支持多个数据类型,比如 :字符串 数字 布尔值 列表 集合 元组 ​ 特点 : 有序 可变 支持索引 (定义一个列表不 ...

  9. 比赛:大奔的方案solution

    分析: 此题是小奔的方案的改进.小奔的方案思路:倒推,每次都从小到大排序并且保证小号在前,然后使每一个人分到的金币都是上一次加一,直到金币分完或者自己可以存活(投票率大于等于所需概率),如果不行就-1 ...

  10. C语言入门5-键盘的输入和屏幕输出

    C程序中的键盘输入和屏幕输出都是通过  调用输入/输出函数  实现的. 一.数据的格式化    屏幕输出 函数printf()的一般格式  (有两种) (1)第一种: printf(格式控制字符串): ...