博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
泛型与容器连载(二)泛型的基本概念和原理
阅读量:6215 次
发布时间:2019-06-21

本文共 5794 字,大约阅读时间需要 19 分钟。

1.容器类

泛型类最常见的用途是作为容器类。所谓的容器类就是指容纳并管理多项数据的类。数组就是用来管理多项数据的,但数组有很多限制,比如长度固定,插入、删除操作效率比较低。有一门课程叫作数据结构,专门讨论管理数据的各种方式。

现在先实现一个简单的动态数组容器。所谓动态数组,就是长度可变的数组。底层数组的长度当然不可变的,但下面提供的一个类,对使用者而言好像就是一个长度可变的数组。Java容器中有一个对应的类ArrayList,我们先来实现一个简化版的。

package com.wang.generic;import java.util.Arrays;public class DynamicList 
{ private static final int DEFAULT_CAPACITY = 10; private int size; private Object [] objectElements; public DynamicList() { this.objectElements = new Object[DEFAULT_CAPACITY]; } public DynamicList(E [] array) { this.objectElements = new Object[DEFAULT_CAPACITY]; if(null != array) { for(int i=0;i< array.length;i++) { add(array[i]); } this.size = array.length; } } private void calcCapacity(int minCapacity) { int oldCapacity = objectElements.length; if(oldCapacity >= minCapacity) { return; } int newCapacity = oldCapacity * 2; if(newCapacity < minCapacity) { newCapacity = minCapacity; } objectElements = Arrays.copyOf(objectElements, newCapacity); } public void add(E e) { calcCapacity(size + 1); objectElements[size++] = e; } public E get(int index) { return (E)objectElements[index]; } public int size() { return size; } public E set(int index,E element) { E oldValue = get(index); objectElements[index] = element; return oldValue; } /** * 泛型方法 * @param arr * @param elem * @return */ public static
int indexOf(T [] arr,T elem) { for(int i=0;i< arr.length;i++) { if(arr[i].equals(elem)) { return i; } } return -1; } /** * 没有上界限制的方法,会导致类型不匹配 * @param list */ public void addAll1(DynamicList
list) { for(int i=0;i< list.size();i++) { add(list.get(i)); } } /** * 有上界限参数的方法 * @param list */ public
void addAll(DynamicList
list) { for(int i=0;i< list.size();i++) { add(list.get(i)); } }}复制代码

DynamicList就是一个动态数组,通过calcCapacity方法根据需要扩展数组。作为一个容器类,它容纳的数据类型是作为参数传递过来的,比如Double类型:

DynamicList
dynamicList = new DynamicList<>();Random rnd = new Random();int size = rnd.nextInt(100) + 1;for(int i =0 ;i< size;i++) { dynamicList.add(Math.random());}Double d = dynamicList.get(rnd.nextInt(size));System.out.println("d:"+d);复制代码

这就是一个简单的容器类,适用于各种数据类型,且类型安全。具体的类型还可以是一个泛型类,比如:

DynamicList
> dynamicList = new DynamicList<>();复制代码

2.泛型方法

除了泛型类,方法也可以是泛型的,而且,一个方法是不是泛型,于它所在的类型是不是泛型没有关系。代码如下:

public static 
int indexOf(T [] arr,T elem) { for(int i=0;i< arr.length;i++) { if(arr[i].equals(elem)) { return i; } } return -1; }复制代码

3.泛型接口

接口也可以是泛型的,比如:

package com.wang.generic;public interface CusComparable 
{ public int compareTo(T o);}复制代码

实现接口时,应该指定具体的类型,比如:Integer类,代码如下:

public final class CusComparableA implements CusComparable
{ @Override public int compareTo(Integer o) { return 0; }}复制代码

4.类型参数的限定

无论是泛型类、泛型方法还是泛型接口,关于类型参数,我们都知之甚少,只能把他当做Object,但Java支持限定这个参数的一个上界,也就是说,参数必须为给定的上界类型或者其子类型,这个限定是通过extends关键字来表示的。这个上界可以是某个具体的类或者某个具体的接口,也可以是其他类型的参数,我们逐个介绍其应用。

1.上界为某个具体类

比如,上面的Person类,可以定义一个子类NumberPerson,限定两个参数的类型必须为Number,代码如下:

package com.wang.generic;public class NumberPerson 	extends Person
{ public NumberPerson(U attr1, V attr2) { super(attr1, attr2); } public double sum() { return getAttr1().doubleValue() + getAttr2().doubleValue(); } /** * 上界限为某个接口 * @param arr * @return */ public static
T max(T [] arr) { T max = arr[0]; for(int i=0;i< arr.length;i++) { if(arr[i].compareTo(max) > 0) { max = arr[i]; } } return max; } public static void main(String[] args) { NumberPerson
numberPerson = new NumberPerson<>(19,22.21); double sum = numberPerson.sum(); System.out.println("sum:"+ sum); }}复制代码

限定类型后,就可以用该类型的方法了。NumberPerson类,attr1和attr2变量就可以当做Number新型处理了。比如上面的求和方法。可以这么用:

NumberPerson
numberPerson = new NumberPerson<>(19,22.21); double sum = numberPerson.sum();复制代码

限定类型后,如果类型使用错误,编译器会提示。指定边界后,类型擦除时就不会转换为Object了,而是会转换为它的边界类型,这也是容易理解的。

2.上界为某个接口

在泛型方法中,一种常见的场景是限定类型必须实现Comparable接口,代码如下:

public static 
> T max(T [] arr) { T max = arr[0]; for(int i=0;i< arr.length;i++) { if(arr[i].compareTo(max) > 0) { max = arr[i]; } } return max; }复制代码

max方法基数按一个泛型数组中的最大值。计算最大值需要进行元素之间的比较,要求元素实现Comparable接口,所以给类型参数设置了一个上边界Comparable,T必须实现Comparable接口。

<T extends Comparable<T>>是一种令人飞机的语法形式,这种形式成为递归类型限制,可以理解:T表示一种数据类型,必须实现Comparable接口,且必须可以与相同类型的元素进行比较。

3.上界为其他类型参数

上面的先动都是制定了一个明确的类或接口,Java支持一个类型参数以另一个类型参数作为上界。为什么需要这样呢?我们看下面这个例子,给DynamicList类增加一个实例方法addAll,这个方法将参数容器中的所有元素都添加到当前容器里来,直觉上,代码可以如下书写:

public void addAll(DynamicList
list) { for(int i=0;i< list.size();i++) { add(list.get(i)); } }复制代码

但这么写有一些局限性,我们看使用它的代码:

DynamicList
dynamicList = new DynamicList<>();DynamicList
ints = new DynamicList<>();ints.add(23);ints.add(33);dynamicList.addAll(ints);//提示编译错误复制代码

dynamicList是一个Number类型的容器,ints是一个Integer类型的容器,我们希望将ints添加到dynamicList中,因为Integer是Number的自雷,可以说这事一个合理等需求和操作。

但Java会在dynamicList。addAll(ints)这行代码上提示变异错误:addAll需要的参数类型为DynamicList<Number>,而传递郭磊的参数类型为DynamicList<Integer>,类型不匹配。我们想让它添加成功,并不报错怎么操作的?

修改方法如下:

public 
void addAll(DynamicList
list) { for(int i=0;i< list.size();i++) { add(list.get(i)); } }复制代码

dynamicList.addAll(ints);就不会报错,满足了我们的需求。

4.总结

泛型是计算机程序中的一种重要的思维方式,它将数据结构和算法与数据类型相分离,似的同一套数据结构和算法能够用用与各种数据类型,二期可以保证类型安全,提高可读性。在Java中,泛型管饭的应用于各种容器类,理解泛型是深刻理解容器的基础。前两篇主要介绍泛型类、泛型方法和泛型接口,关于类型参数,我们介绍了多种上界限定,限定为某个具体类,某具体接口或其他类型参数。泛型类最常见的用途是容器类,我们实现了一个简单的容器类DynamicList来解释泛型概念。

在Java中,泛型是通过类型擦除来实现的,它是Java编译器的概念,Java虚拟机运行时对泛型基本一无所知,裂解这一点很重要的,它有助于我们理解Java泛型的很多局限性。

关于泛型,用用很广泛,但语法非常令人飞溅而且容易混淆,下一篇我们将泛型中通配符的概念!

转载于:https://juejin.im/post/5c4412fee51d4502fd757866

你可能感兴趣的文章
不重装系统修复系统的一些实例
查看>>
异步GEI (2) 线程
查看>>
通过管理控制台和命令行两种方式新建邮箱数据库(exchange2010)
查看>>
网卡设置(设置IP地址、网关、DNS)
查看>>
linux之sed用法
查看>>
HBTC2012 参会感受
查看>>
如何愉快的使用MQ-详述各种功能场景
查看>>
SQL查询语句中的 limit 与 offset 的区别
查看>>
hadoop SequenceFile介绍 大数据 存储
查看>>
手动订制一个基于BusyBox的微型Linux系统
查看>>
TCP/IP协议和Socket编程
查看>>
lnmp(new)
查看>>
使用fastjson时出现$ref: "$.list[2]"的解决办法(重复引用)
查看>>
ZooKeeper观察节点
查看>>
关系图报错"dataIndex undefined"
查看>>
[python] 各种ERROR
查看>>
利用Maven搭建Spring开发环境
查看>>
Swift重写set和get以及willSet和didSet介绍
查看>>
oracle分区表的迁移
查看>>
SpringCloud系列:整合Apollo实现分布式配置中心(一)
查看>>