Created by Marydon on

1.概述

  泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数;

  这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法;

  引入泛型的好处在于:编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

  在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”;

  “任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的;

  对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

  还是不理解什么是泛型?

  定义方法时,我们一般声明具体的入参类型及参数名称,这时,我们称之为:方法的形参,在调用该方法时,传入的参数叫做实参;

  而当我们在定义方法时,方法的入参不明确入参的具体类型,和参数名一样,使用变量声明(如:T),这时,我们称其为:类型形参,

  在调用该方法时,指定实际传入的参数的数据类型(如:String)叫做类型实参。

  再举个例子

// 都是典型的泛型例子
List<String> list = new ArrayList<String>();
Map<String, Object> map = new HashMap<String, Object>();  

2.泛型的特性:类型擦除

类型擦除就是说Java泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息,这样到了运行期间实际上JVM根本就知道泛型所代表的具体类型。这样做的目的是因为Java泛型是1.5之后才被引入的,为了保持向下的兼容性,所以只能做类型擦除来兼容以前的非泛型代码。对于这一点,如果阅读Java集合框架的源码,可以发现有些类其实并不支持泛型。

// List测试
List<String> list = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
System.out.println(list2.equals(list));// true
List<Object> list3 = new ArrayList<Object>();
System.out.println(list3.equals(list2));// true
List<Object> list4 = new ArrayList<Object>();
System.out.println(list4.equals(list3));// true
List<Map<String, String>> list5 = new ArrayList<Map<String, String>>();
System.out.println(list5.equals(list4));// true
List<Map<Object, Object>> list6 = new ArrayList<Map<Object, Object>>();
System.out.println(list6.equals(list5));// true
// Map测试
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> map2 = new HashMap<Object, Object>();
System.out.println(map2.equals(map));// true
Map map3 = new HashMap();
System.out.println(map3.equals(map2));// true
// 赋值后比对
map.put("", "");
map2.put("1", null);
System.out.println(map2.equals(map));// false

  

3.泛型类

  一个泛型类就是具有一个或多个类型变量的类。

  类型变量名称使用大写形式,且比较短,如:T;

  需用尖括号括起来,并放在类名的后面;

public class People <T> {}

  在Java库中,使用变量E表示集合的元素类型,KV分别表示表的关键字与值的类型,T表示“任意类型”;

  泛型类可以有多个类型变量;

public class People <T, O, P> {}

  泛型类定义中的类型变量可以指定为:局部变量、方法的入参、方法的返回值;

/**
* 泛型类
*/
public class People <T> { // 泛型局部变量
private T kind; // 泛型方法-返回值是泛型
public T getKind() {
return kind;
} // 泛型方法-入参是泛型
public void setKind(T kind) {
this.kind = kind;
}
}

  泛型类可以看成是普通类的工厂。

  泛型类的调用

  通过类名调用静态方法时,不能指定类型参数;

// 正确方式
String result2 = People.say("Marydon");
// 错误方式
String result2 = People<String>.say("Marydon");

  实例化对象时,既可以指定类型参数,也可以不指定。(最好指定)

// 指定类型参数
People<String> p = new People<String>();
// 不指定类型参数
People p2 = new People();

4.泛型方法

  泛型方法的构成要素:

  其一,必须添加泛型声明;

  即:

  类型变量名称必须采用使用大写,并且用尖括号括起来;

  类型变量位于修饰符的后面,返回类型的前面。

  其二,返回类型为类型变量或入参包含类型变量;

  当以上2个条件同时满足时,这时,我们就可以称该方法为:泛型方法。

  泛型方法的入参,可以有多个类型变量;

  泛型方法可以定义在普通类中,也可以定义在泛型类中。 

  当泛型方法在普通类中时,

// 普通类
public class People {
// 泛型方法:入参和返回值都是类型变量
static <T> T say(T word) {
T sentence = null;
sentence = (T) ("传入的值是:" + word);
return sentence;
}
}  

  当泛型方法在泛型类中时,只有进行泛型声明的方法才是泛型方法;

  不声明类型变量的方法不是泛型方法,且不能被static修饰。

// 泛型类
public class People <T> {
// 泛型方法
static <T> T say(T word) {
T sentence = null;
sentence = (T) ("传入的值是:" + word);
return sentence;
}
}

  泛型方法的调用

  第一种调用方式:

  在方法名前的尖括号中放入具体的类型;

// 声明类型参数
String result = People.<String>say("Marydon");
System.out.println(result);

  第二种调用方式:

  泛型方法调用中是可以省略<String>类型参数的,编译器会使用类型推断来推断出所调用的方法。

// 省略<String>类型参数
String result2 = People.say("Marydon");
System.out.println(result2);

  泛型方法的几种表现形式

/**
* 泛型测试类
* @explain
* @author Marydon
* @creationTime 2018年11月1日下午3:36:05
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
public class TestGeneric <T> { /**
* 泛型方法之表现形式一:
* @explain 方法入参为泛型
* @param t
*/
<T> void transform1(T t) {
System.out.println(t);
} /**
* 泛型方法之表现形式二:
* @explain 方法入参为泛型,参数个数不固定
* @param t
*/
protected <T> void transform1(T ... args) {
for (T t : args) {
System.out.print(t + "\t");
System.out.println("");
}
} /**
* 泛型方法之表现形式三:
* @explain 入参为泛型,返回类型也为泛型
* @param t
* @return
*/
static <T> T transform3(T t) {
T clazz = (T) ("传入的值是:" + t);
return clazz;
} /**
* 泛型方法之表现形式四:
* @explain 泛型的数量可以为任意多个
* @param t
* @return
*/
public static <T, U> U transform4(T t) {
U clazz = (U) t;
return clazz;
} /**
* 泛型方法之表现形式五:
* @explain 泛型的产生源自方法内部,这样的方法没有实际意义
* 因为泛型的产生只能来自于外部,通常源自入参
* @return
*/
<T> T transform5() {
T t = null;
return t;
}
}

  测试

public static void main(String[] args) {
TestGeneric<String> tg = new TestGeneric<String>();
tg.transform1(new Integer(1));
tg.transform1(new Integer(2),new Integer(3),new Integer(4)); String c = tg.transform3("Marydon");
System.out.println(c); Number n = tg.transform4(1);
System.out.println(n.getClass().getName());
}

  结果

  说明:

  上面的5个例子举的不太恰当,只是为了说明泛型方法的表现形式。

  对泛型方法可能存在的错误认知

  虽然在方法中使用了泛型,但是下面这4个例子并不是一个泛型方法,它们只是类中一个普通的成员方法。

private T clazz;

/**
* 普通方法一
* @explain
* 因为返回值是在声明泛型类已经声明过的泛型,
* 所以该方法才可以继续使用 T 这个泛型。
* @return
*/
public T getClazz() {
return clazz;
} /**
* 普通方法二
* @explain
* 因为返回值是在声明泛型类已经声明过的泛型,
* 所以该方法才可以继续使用 T 这个泛型。
* @return
*/
public void setClazz(T clazz) {
this.clazz = clazz;
} /**
* 普通方法三
* @explain
* 这也是一个普通的方法,只是使用了String类作为这个泛型类List的实参而已
* @return
*/
public void method3(List<String> list) { } /**
* 普通方法四
* @explain
* 这也是一个普通的方法,只是使用了泛型通配符?作为这个泛型类List的实参而已
* @return
*/
public void method4(List<?> list) { } 

  证明:

  在泛型类中声明的泛型方法,即使泛型类和泛型方法的类型变量名称一样,两者所代表的类的类型也不是同一类型,互不关联,互不干扰;

  该泛型可以为任意类型,泛型方法的变量名所代表的实际类型与泛型类的变量名所代表的实际类型可以相同,也可以不同;  

/**
* 泛型测试类
* @explain
* @author Marydon
* @creationTime 2018年11月1日下午3:36:05
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
public class TestGeneric <T> { // 泛型变量修饰成员变量
private T clazz; public void setClazz(T clazz) {
this.clazz = clazz;
} /**
* 获取TestGeneric的泛型类型
* @explain
*/
public String getGenericType() {
return clazz.getClass().getName();
} /**
* @explain
* 在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,与泛型类中声明的T不是同一种类型。
* @param t
*/
public static <T> String method1(T t) {
return t.getClass().getName();
} /**
* method2和method1,这个两个方法没有区别
* @explain
* 由于泛型方法在声明的时候会声明泛型<U>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型
* @param u
* @return
*/
public static <U> String method2(U u) {
return u.getClass().getName();
}
}

  测试

public static void main(String[] args) {
TestGeneric<String> tg = new TestGeneric<String>();
tg.setClazz("String");
// 编译报错
// tg.setClazz(new Integer(1));
System.out.println("实例化对象时,指定的泛型类型为:" + tg.getGenericType());
System.out.println("你传入的泛型类型是:" + TestGeneric.method1(new Integer(1)));
System.out.println("你传入的泛型类型是:" + TestGeneric.method2(new Character('a')));
System.out.println("你传入的泛型类型是:" + TestGeneric.method1("Marydon"));
}

  结果

5.泛型接口

  泛型接口与泛型类的定义及使用基本相同;

  泛型接口常被用在各种类的生产器中;

  泛型接口里面既可以定义普通方法,也可以定义泛型方法。

/**
* 水果类
* @explain 泛型接口
* @author Marydon
* @creationTime 2018年11月6日上午11:06:32
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
interface Fruit<T> { /**
* 泛型接口定义普通方法
* @explain 产生一个泛型类的实例
* @return
*/
public T newInstance(); /**
* 泛型接口定义泛型方法
* @explain
* @return
*/
public <T> T newInstance2();
}

  泛型接口的调用

  第一种调用方式:

  实现泛型接口时,传入泛型实参

  从接口实现的方法、变量,凡是涉及到类型变量的地方都需要替换成实际参入的实参类型;

  接口实现类可以有自己独立的泛型方法。

/**
* 苹果类
* @explain 实现泛型接口,传入泛型实参
* 从接口实现的方法、变量,凡是涉及到泛型的地方都需要替换成实际参入的实参类型
* @author Marydon
* @creationTime 2018年11月6日上午11:07:51
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class Apple implements Fruit<Apple> { /**
* 重写普通方法
*/
@Override
public Apple newInstance() {
try {
return Apple.class.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
} /**
* 重写泛型方法
*/
@Override
public <T> T newInstance2() {
return null;
} /**
* Apple类的独有泛型方法
* @explain 这里只作展示,用来说明
* 与泛型接口无关
* @param t
* @return
*/
<T> String getVariety(T t) {
return "";
} }

  第二种调用方式:

  实现泛型接口时,未传入泛型实参;

  该类需声明成泛型类,类型变量名称与接口的类型变量名称必须保持一致。

/**
* 梨类
* @explain 实现泛型接口,未传入泛型实参
* 该类需声明成泛型类,类型变量与接口的类型变量必须保持一致
* @author Marydon
* @creationTime 2018年11月6日上午11:09:46
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class Pear<T> implements Fruit<T> { /**
* 重写普通方法
*/
@Override
public T newInstance() {
return null;
} /**
* 重写泛型方法
*/
@Override
public <T> T newInstance2() {
return null;
}
}

  

6.类型通配符

7.泛型上下边界

   泛型的上下边界添加,必须与泛型的声明在一起 。

  上限:使用"extends";

/**
* 泛型上限示例一
* @explain T类继承了Throwable
* 同时实现了接口Comparable和Serializable
* @author Marydon
* @creationTime 2018年11月6日下午4:58:44
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class GenericExtends<T extends Throwable & Comparable<?> & Serializable> { } /**
* 泛型上限示例二
* @explain T类实现了接口Comparable和Serializable
* @author Marydon
* @creationTime 2018年11月6日下午4:58:44
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class GenericExtends2<T extends Comparable & Serializable> { } /**
* 用于测试关系泛型声明边界
* @explain
* @author Marydon
* @creationTime 2018年11月6日下午5:17:43
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class Ttest extends Throwable implements Comparable, Serializable { @Override
public int compareTo(Object o) {
return 0;
} }

  

  只有在泛型声明时,使用extends关键字后可以跟一个类A、多个接口(B、C、D、...),类放在最前面表示继承关系,接口之间使用"&"进行连接;

  表示的意思是:泛型T类继承了A类,并且实现了B接口、C接口,等等,并不是多继承的关系;

  类A可以不存在,extends后直接跟接口;

  在Java中只允许存在单继承关系,而且,只允许泛型声明可以以这样的格式存在。

/**
* 泛型上限示例一
* @explain T类继承了Throwable
* 同时实现了接口Comparable和Fruit
* @author Marydon
* @creationTime 2018年11月6日下午4:58:44
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class GenericExtends<T extends Throwable & Comparable<?> & Fruit<?>> { } /**
* 泛型上限示例二
* @explain T类实现了接口Comparable和Fruit
* @author Marydon
* @creationTime 2018年11月6日下午4:58:44
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class GenericExtends2<T extends Comparable & Fruit> { } /**
* 用于测试关系泛型声明边界
* @explain 不能加泛型控制,否则报错
* @author Marydon
* @creationTime 2018年11月6日下午5:17:43
* @version 1.0
* @since
* @email marydon20170307@163.com
*/
class Ttest extends Throwable implements Comparable, Fruit { @Override
public int compareTo(Object o) {
return 0;
} @Override
public Object newInstance() {
return null;
} @Override
public Object newInstance2() {
return null;
} }

  调用

public static void main(String[] args) {
// 调用
new GenericExtends<Ttest>();
new GenericExtends2<Ttest>();
}

  下限:使用"super"。

本文中的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性。

相关推荐:

 

java 泛型 精析的更多相关文章

  1. java 类名.class、object.getClass()和Class.forName()的区别 精析

        1.介绍 getClass()介绍 java是面向对象语言,即万物皆对象,所有的对象都直接或间接继承自Object类: Object类中有getClass()方法,通过这个方法就可以获得一个实 ...

  2. Java泛型的历史

    为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...

  3. MVVM大比拼之AngularJS源码精析

    MVVM大比拼之AngularJS源码精析 简介 AngularJS的学习资源已经非常非常多了,AngularJS基础请直接看官网文档.这里推荐几个深度学习的资料: AngularJS学习笔记 作者: ...

  4. MVVM大比拼之knockout.js源码精析

    简介 本文主要对源码和内部机制做较深如的分析,基础部分请参阅官网文档. knockout.js (以下简称 ko )是最早将 MVVM 引入到前端的重要功臣之一.目前版本已更新到 3 .相比同类主要有 ...

  5. 浅析Java 泛型

    泛型是JavaSE5引入的一个新概念,但是这个概念在编程语言中却是很普遍的一个概念.下面,根据以下内容,我们总结下在Java中使用泛型. 泛型使用的意义 什么是泛型 泛型类 泛型方法 泛型接口 泛型擦 ...

  6. Java:泛型基础

    泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的 ...

  7. java泛型基础

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法.  Ja ...

  8. 使用java泛型设计通用方法

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...

  9. 关于Java泛型的使用

    在目前我遇到的java项目中,泛型应用的最多的就属集合了.当要从数据库取出多个对象或者说是多条记录时,往往都要使用集合,那么为什么这么使用,或者使用时有什么要注意的地方,请关注以下内容. 感谢Wind ...

随机推荐

  1. 【windows socket+HTTPserverclient】

    Windows Socket+HTTPserverclient      Winsock是 Windows下套接字标准.                 1.HTTP协议:          HTTP ...

  2. SQL:(转)数据库中的锁机制(数据库中有哪些锁)

    数据库中的锁机制 锁是网络数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性.各种大型数 据库所采用的锁的基本理论是一致的,但在具体实现上各有差别.目前,大多数数据库管理系统 ...

  3. Hadoop-2.2.0中文文档——Common-Hadoop HTTP web控制台认证

    简单介绍 此文档描写叙述了怎样配置Hadoop HTTP web控制台,去要求用户认证. 默认地,Hadoop HTTP web控制台(JobTracker, NameNode, TaskTracke ...

  4. Spring+Quartz的版本问题

    使用Spring配置管理Quartz的时候会遇到下面的异常: Caused by: java.lang.IncompatibleClassChangeError: class org.springfr ...

  5. 使用 SVWebViewController 推出浏览器控制器

    SVWebViewController 简单翻译 https://github.com/samvermette/SVWebViewController SVWebViewController is a ...

  6. ping + 时间 日志

    :top set PINGIP="192.168.1.236" echo %date% %time%>>%PINGIP%.txt ping -n 1 %PINGIP% ...

  7. ubuntu14.04开启root用户 设置root密码 配置国内镜像源 设置分辨率

    一.Ubuntu 默认是不允许 root 通过 ssh 直接登录的,可以修改 /etc/ssh/sshd_config,设置 1 PermitRootLogin yes 然后重启 ssh 服务即可 1 ...

  8. Windows Server上用于iSCSI的网卡的必备设置

    如下的修改是iSCSI网卡的推荐配置, 新装起来的Host不要忘记改起来哦. 其原理是强制走iSCSI通讯的网卡立即对到来的TCP报文(segment)做出acknowledge, 从而解决iSCSI ...

  9. Copy List with Random Pointer leetcode java

    题目: A linked list is given such that each node contains an additional random pointer which could poi ...

  10. 线程 Timer TimerTask 计时器 定时任务 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...