写在前面
- 什么是泛型,为什么要使用泛型
- 常见常用的T、E、K、V还有那个?都是什么意思
- 还有上界通配符、下界通配符都是个啥?
本文对泛型的介绍其实是很粗浅的,很多复杂的应用场景没有介绍到,这里推荐大家看下《Java编程思想》中有关泛型的介绍,相信大家会有更多的收获。
泛型的介绍
我们先来看下百度百科上关于泛型的介绍:
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。Ada、Delphi、Eiffel、Java、C#、F#、Swift 和 [Visual Basic .NET](https://baike.baidu.com/item/Visual Basic .NET) 称之为泛型(generics);ML、Scala 和 Haskell 称之为参数多态(parametric polymorphism);C++ 和 D称之为模板。具有广泛影响的1994年版的《Design Patterns》一书称之为参数化类型(parameterized type)。
可见泛型并不是Java独有的,而Java 泛型(generics)是JDK5中引入的新特性,这是Java官方介绍。泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型。
为什么要使用泛型
我们先来看一下不使用泛型时
public class Generics {
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList<>());
list.add(1);
list.add("a");
Object params1 = list.get(0);
int params1Value = (int)params1;
Object params2 = list.get(1);
int params2Value = (int)params2;//编译正确,运行报错
System.out.println(params1Value);
System.out.println(params2Value);
}
}
我们创建了一个list,往里面分别放入了int/String类型的两个值。由于没有指定类型,那么我们在使用的时候需要强制转换成我们需要的类型。这里会有一个问题,我们这里只放入了2个值,我们很容易记住我们如的是什么类型的数据,那么如果是10,100个甚至更多呢?ArrayList能放入多少个元素?private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
这是ArrayList源码中给的设定,其中 Integer.MAX_VALUE=231-1。所以当list值过多,开发人员在使用过程中很容易出错,这时候泛型的作用就体现出来了。
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add(1);//编译报错
list.add("a");
当我们为List指定了类型,那么这个list中就只能放入指定类型String的元素,当我们放入非法的参数,比如int类型的1,编译器会直接报错。开发人员在开发阶段就能直接发现问题,“哦,这儿不对”。
- 虽然创建好的对象只能支持一种类型,但对象在创建的时候我们可以选择想要支持的类型
泛型的擦除
前面我们在介绍泛型的时候总会涉及到一个词“编译”,为什么呢?
这就涉及到了Java泛型的擦除,到底什么是泛型的擦除?
public class Generics {
public void test() {
List<String> list = Collections.synchronizedList(new ArrayList<>());
Map<String,String> map = new HashMap<>();
}
}
我们反编译下看看编译器处理后的代码
public class Generics {
public void test() {
List list = Collections.synchronizedList(new ArrayList());
HashMap map = new HashMap();
}
}
泛型其实Java中的一种语法糖,可以参考我的另一篇文章《Java语法糖》,泛型并不能被JVM所识别,所以Java编译器在编译阶段就会把泛型擦除掉。泛型只是编译之前,方便程序员使用,减少程序代码出错的糖衣语法。
落入俗套
刚接触泛型的时候,就遇到一个头大的事,T、E、K、V都是个啥?网上很多人都给出了解释
E – Element (在集合中使用,因为集合中存放的是元素)
T – Type(Java 类)
K – Key(键)
V – Value(值)
N – Number(数值类型)
? – 表示不确定的java类型(无限制通配符类型)
他们说的有问题吗?没有……但是这些我们真的需要去了解并记住这几个字母吗?
在《Java编程思想》中有这么一句话:“有许多原因促成了泛型的出现,而最引人注目的一个原因,就是为了创造容器类”。我们使用泛型是为了,让容器只用来存储一种类型的对象,这种类型可以是元素、类、键、值……
我们可以用任何字母或者字符串来代替这种类型的对象,E、T等只是开发人员的习惯,并非固定语法
public class Generics<A,a123> {
public void test() {
Map<A,a123> map = new HashMap<>();
}
}
我用A设置a123都可以定义泛型,所以抛开哪些别人的习惯,我们自己来理解泛型会更容易些。
泛型使用的几个地方
泛型可以使用在类、接口、方法中,下面我分别来举例
泛型类
先来看一个简单的泛型
public class Generics<A> {
private A a;
public Generics(A a) { this.a = a; }
public void set(A a) { this.a = a; }
public A getA() { return a; }
public static void main(String[] args) {
Generics<String> generics = new Generics<>(new String());
generics.set("LBB1011");
System.out.println(generics.getA());
}
}
泛型接口
interface GenericsInterface<C> {
C getA();
}
public class Generics implements GenericsInterface {
@Override
public Object getA() {
return null;
}
}
泛型在接口中的使用方法跟在泛型类中一样,也比较简单。泛型接口可以应用于生成器中,我们不需要额外的信息就知道如何创建对象了。比如我们在订单系统中,可以根据订单的不同(普通单、砍价单)创建不同的订单服务类。《Java编程思想》中也有一个很有意思的例子: CoffeGenerator,感兴趣的小伙伴可以去看下。
泛型方法
public class Generics {
public <A> void fun(A a) {
System.out.println(a.getClass().getName());
}
public static void main(String[] args) {
Generics generics = new Generics();
generics.fun("a");
generics.fun(1);
generics.fun(1.0);
generics.fun(1.0f);
generics.fun('c');
generics.fun(generics);
}
}
// 输出:~
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
cool.lbb.Generics
- 泛型方法与可变参数能够很好地共存
public class Generics {
public static <A> List<A> fun(A... args) {
List<A> result = new ArrayList<>();
for (A a : args) {
result.add(a);
}
return result;
}
public static void main(String[] args) {
List<String> list = fun("a", "b");
System.out.println(list);
}
}
通配符
无界通配符
<?>
意味着它可以是任意类型,不同于<T>
,后者表示只能是指定的T
类型。
因此<?>
等价于原生类型,List<?>
等价于List
,既然等价于原生,那么<?>
存在的意义是什么?
实际上,它是在声明:“我是想用Java的泛型来编写这段代码,我在这里并不是想要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。”
当我们处理多个泛型参数时,有时允许一个参数可以是任何类型,另外一个参数是其他某种特定类型时,?
的作用就体现出来了
public class Generics {
static Map<String,?> map;
static void newMap(Map<String,?> args) { map = args;};
public static void main(String[] args) {
newMap(new HashMap<String,String>());
newMap(new HashMap<String,Integer>());
newMap(new HashMap<Integer,String>());//编译不通过
}
}
上界通配符
<? extends E>
传入的类型必须是E或者E的子类
<? extends E,F>
传入的类型必须同时E和F的子类
public class Generics {
public static void main(String[] args) {
List<Fruit> list = new ArrayList<>();
autoCompile(list);//编译不通过
List<Apple> list1 = new ArrayList<>();
autoCompile(list1);
List<Jonathan> list2 = new ArrayList<>();
autoCompile(list2);
}
static void autoCompile(List<? extends Apple> flist){ }
}
class Fruit{}
class Apple extends Fruit{}
class Jonathan extends Apple{}
class Orange extends Fruit{}
下界通配符
<? super E>
传入的类型必须是E或者E的父类
public class Generics {
public static void main(String[] args) {
List<Fruit> list = new ArrayList<>();
autoCompile(list);
List<Apple> list1 = new ArrayList<>();
autoCompile(list1);
List<Jonathan> list2 = new ArrayList<>();
autoCompile(list2);//编译不通过
}
static void autoCompile(List<? super Apple> flist){
}
}
泛型的复杂使用实例
未完待续……
- 与动态代理混合
- 异常
- 泛型数组
- 自限定
- ……
参考资料
- 《Java编程思想》
- Java Generics FAQs