java 泛型 精析
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表示集合的元素类型,K
和V
分别表示表的关键字与值的类型,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;
}
}
2022年2月7日15:35:56
也可以这样使用:
当泛型方法在泛型类中时,只有进行泛型声明的方法才是泛型方法;
不声明类型变量的方法不是泛型方法。
// 泛型类
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.类型通配符
上界通配符(Upper Bounded Wildcards)
? extends Type
即为上界通配符
下界通配符(Lower Bounded Wildcards)
? super Type
即为下界通配符
无界通配符(unBounded Wildcards)
? 即为无界通配符
上界通配符通常代表了只读,而下界通配符表示了可写(当然也可读,但是是 Object)。
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"。
本文中的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性。
写在最后
哪位大佬如若发现文章存在纰漏之处或需要补充更多内容,欢迎留言!!!
相关推荐:
本文来自博客园,作者:Marydon,转载请注明原文链接:https://www.cnblogs.com/Marydon20170307/p/9732769.html