作者 | 沉默王二
本文经授权转载自沉默王二(ID:cmower)
故事的起源“二哥,要不我上大学的时候也学习编程吧?”有一天,三妹突发奇想地问我。
“你确定要做一名程序媛吗?”
“我觉得女生做程序员,有着天大的优势,尤其是我这种长相甜美的。”三妹开始认真了起来。
“好像是啊,遇到女生提问,我好像一直蛮热情的。”
“二哥,你不是爱好写作嘛,还是一个 Java 程序员,不妨写个专栏,名字就叫《教妹学 Java》。
“真的很服气你们零零后,蛮有想法的。刚好我最近在写 Java 系列的专栏,不妨试一试!”
PS:亲爱的读者朋友们,我们今天就从晦涩难懂的“泛型”开始吧!(子标题是三妹提出来的,内容由二哥我来回答)
二哥,为什么要设计泛型啊?三妹啊,听哥慢慢给你讲啊。
Java 在 5.0 时增加了泛型机制,据说专家们为此花费了 5 年左右的时间(听起来很不容易)。有了泛型之后,尤其是对集合类的使用,就变得更规范了。
看下面这段简单的代码。
ArrayList list = new ArrayList();
list.add("沉默王二");
String str = list.get(0);
但在没有泛型之前该怎么办呢?
首先,我们需要使用 Object 数组来设计 Arraylist 类。
class Arraylist {
private Object[] objs;
private int i = 0;
public void add(Object obj) {
objs[i++] = obj;
}
public Object get(int i) {
return objs[i];
}
}
然后,我们向 Arraylist 中存取数据。
Arraylist list = new Arraylist();
list.add("沉默王二");
list.add(new Date());
String str = (String)list.get(0);
你有没有发现两个问题:
-
Arraylist 可以存放任何类型的数据(既可以存字符串,也可以混入日期),因为所有类都继承自 Object 类。
-
从 Arraylist 取出数据的时候需要强制类型转换,因为编译器并不能确定你取的是字符串还是日期。
对比一下,你就能明显地感受到泛型的优秀之处:使用类型参数解决了元素的不确定性——参数类型为 String 的集合中是不允许存放其他类型元素的,取出数据的时候也不需要强制类型转换了。
二哥,怎么设计泛型啊?三妹啊,你一个小白只要会用泛型就行了,还想设计泛型啊?!不过,既然你想了解,那么哥义不容辞。
首先,我们来按照泛型的标准重新设计一下 Arraylist 类。
class Arraylist {
private Object[] elementData;
private int size = 0;
public Arraylist(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
E elementData(int index) {
return (E) elementData[index];
}
}
一个泛型类就是具有一个或多个类型变量的类。Arraylist 类引入的类型变量为 E(Element,元素的首字母),使用尖括号 括起来,放在类名的后面。
然后,我们可以用具体的类型(比如字符串)替换类型变量来实例化泛型类。
Arraylist list = new Arraylist();
list.add("沉默王三");
String str = list.get(0);
Date 类型也可以的。
Arraylist list = new Arraylist();
list.add(new Date());
Date date = list.get(0);
其次,我们还可以在一个非泛型的类(或者泛型类)中定义泛型方法。
class Arraylist {
public T[] toArray(T[] a) {
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
}
不过,说实话,泛型方法的定义看起来略显晦涩。来一副图吧(注意:方法返回类型和方法参数类型至少需要一个)。
现在,我们来调用一下泛型方法。
Arraylist list = new Arraylist(4);
list.add("沉");
list.add("默");
list.add("王");
list.add("二");
String [] strs = new String [4];
strs = list.toArray(strs);
for (String str : strs) {
System.out.println(str);
}
最后,我们再来说说泛型变量的限定符 extends。在解释这个限定符之前,我们假设有三个类,它们之间的定义是这样的。
class Wanglaoer {
public String toString() {
return "王老二";
}
}
class Wanger extends Wanglaoer{
public String toString() {
return "王二";
}
}
class Wangxiaoer extends Wanger{
public String toString() {
return "王小二";
}
}
我们使用限定符 extends 来重新设计一下 Arraylist 类。
class Arraylist {
}
当我们向 Arraylist 中添加 Wanglaoer 元素的时候,编译器会提示错误:Arraylist 只允许添加 Wanger 及其子类 Wangxiaoer 对象,不允许添加其父类 Wanglaoer。
Arraylist list = new Arraylist(3);
list.add(new Wanger());
list.add(new Wanglaoer());
// The method add(Wanger) in the type Arraylist is not applicable for the arguments
// (Wanglaoer)
list.add(new Wangxiaoer());
也就是说,限定符 extends 可以缩小泛型的类型范围。
二哥,听说虚拟机没有泛型?三妹,你功课做得可以啊,连虚拟机都知道了啊。哥可以肯定地回答你,虚拟机是没有泛型的。
啰嗦一句哈。我们编写的 Java 代码(也就是源码,后缀为 .java 的文件)是不能够被操作系统直接识别的,需要先编译,生成 .class 文件(也就是字节码文件)。然后 Java 虚拟机(JVM)会充当一个翻译官的角色,把字节码翻译给操作系统能听得懂的语言,告诉它该干嘛。
怎么确定虚拟机没有泛型呢?我们需要把泛型类的字节码进行反编译——强烈推荐超神反编译工具 Jad !
现在,在命令行中敲以下代码吧(反编译 Arraylist 的字节码文件 Arraylist.class)。
jad Arraylist.class
命令执行完后,会生成一个 Arraylist.jad 的文件,用文本编辑工具打开后的结果如下。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Arraylist.java
package com.cmower.java_demo.fanxing;
import java.util.Arrays;
class Arraylist
{
public Arraylist(int initialCapacity)
{
size = 0;
elementData = new Object[initialCapacity];
}
public boolean add(Object e)
{
elementData[size++] = e;
return true;
}
Object elementData(int index)
{
return elementData[index];
}
private Object elementData[];
private int size;
}
类型变量 消失了,取而代之的是 Object !
既然如此,那如果泛型类使用了限定符 extends,结果会怎么样呢?我们先来看看 Arraylist2 的源码。
class Arraylist2 {
private Object[] elementData;
private int size = 0;
public Arraylist2(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
E elementData(int index) {
return (E) elementData[index];
}
}
字节码文件 Arraylist2.class 使用 Jad 反编译后的结果如下。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Arraylist2.java
package com.cmower.java_demo.fanxing;
// Referenced classes of package com.cmower.java_demo.fanxing:
// Wanger
class Arraylist2
{
public Arraylist2(int initialCapacity)
{
size = 0;
elementData = new Object[initialCapacity];
}
public boolean add(Wanger e)
{
elementData[size++] = e;
return true;
}
Wanger elementData(int index)
{
return (Wanger)elementData[index];
}
private Object elementData[];
private int size;
}
类型变量 不见了,E 被替换成了 Wanger。
通过以上两个例子说明,Java 虚拟机会将泛型的类型变量擦除,并替换为限定类型(没有限定的话,就用 Object)。
二哥,类型擦除会有什么问题吗?三妹啊,你还别说,类型擦除真的会有一些“问题”。
我们来看一下这段代码。
public class Cmower {
public static void method(Arraylist list) {
System.out.println("Arraylist list");
}
public static void method(Arraylist list) {
System.out.println("Arraylist list");
}
}
在浅层的意识上,我们会想当然地认为 Arraylist list 和 Arraylist list 是两种不同的类型,因为 String 和 Date 是不同的类。
但由于类型擦除的原因,以上代码是不会通过编译的——编译器会提示一个错误(这正是类型擦除引发的那些“问题”):
Erasure of method method(Arraylist) is the same as another method in type Cmower
Erasure of method method(Arraylist) is the same as another method in type Cmower
大致的意思就是,这两个方法的参数类型在擦除后是相同的。
也就是说,method(Arraylist list) 和 method(Arraylist list) 是同一种参数类型的方法,不能同时存在。类型变量 String 和 Date 在擦除后会自动消失,method 方法的实际参数是 Arraylist list。
有句俗话叫做:“百闻不如一见”,但即使见到了也未必为真——泛型的擦除问题就可以很好地佐证这个观点。
二哥,听说泛型还有通配符?三妹啊,哥突然觉得你很适合作一枚可爱的程序媛啊!你这预习的功课做得可真到家啊,连通配符都知道!
通配符使用英文的问号(?)来表示。在我们创建一个泛型对象时,可以使用关键字 extends 限定子类,也可以使用关键字 super 限定父类。
为了更好地解释通配符,我们需要对 Arraylist 进行一些改进。
class Arraylist {
private Object[] elementData;
private int size = 0;
public Arraylist(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
public E get(int index) {
return (E) elementData[index];
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录