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. maven进阶:一个多模块项目

    一个多模块项目通过一个父POM 引用一个或多个子模块来定义.父项目,通过以下配置,将子项目关联. <packaging>pom</packaging> <modules& ...

  2. 【leetcode】sort list(python)

    链表的归并排序 超时的代码 class Solution: def merge(self, head1, head2): if head1 == None: return head2 if head2 ...

  3. WebView具体介绍

    PART_0    侃在前面的话 WebView是Android提供给我们用来载入网页的控件.功能非常强大.我们经常使用的手机淘宝.手机京东的Androidclient里面大量使用到了WebView. ...

  4. java变量深入理解

    4,变量:其实就是内存中的一个存储空间,用于存储常量数据. 作用:方便于运算.因为有些数据不确定.所以确定该数据的名词和存储空间. 特点:变量空间可以重复使用. 什么时候定义变量?只要是数据不确定的时 ...

  5. python接口自动化3-自动发帖(session)

    前言 上一篇模拟登录博客园,但这只是第一步,一般登录后,还会有其它的操作,如发帖,评论等,这时候如何保持会话呢? (敲黑板!!!由于博客园最近登录机制变了,登录全部走cookie登录) 一.sessi ...

  6. iOS LaunchScreen设置启动图片 启动页停留时间

    问题:想实现类似微信启动页一样 设置为一个整页面的图片 问题二:iOS启动页面怎样设置多停留一会 新建的iOS 项目启动画面默觉得LaunchScreen.xib 假设想实现一张图片作为启动页,例如以 ...

  7. Easing圆环动画

    Easing圆环动画 效果 源码 https://github.com/YouXianMing/Animations // // CircleView.h // YXMWeather // // Cr ...

  8. Windows Server 2003 IIS设置完全篇

    一.启用Asp支持Windows Server 2003 默认安装,是不安装 IIS 6 的,需要另外安装.安装完 IIS 6,还需要单独开启对于 ASP 的支持. 第一步,启用Asp,进入:控制面板 ...

  9. Mysql/MariaDB的多主集群实现:Galera Cluster

    Galera Cluster是Codership公司开发的一套免费开源的高可用方案,属于multi-master的集群架构,如图所示: 三个实例,组成了一个集群,而这三个节点与普通的主从架构不同,它们 ...

  10. 在Spark上运行WordCount程序

    1.编写程序代码如下: Wordcount.scala package Wordcount import org.apache.spark.SparkConf import org.apache.sp ...