泛型与容器连载(二)泛型的基本概念和原理

1.容器类

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

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

package com.wang.generic;

import java.util.Arrays;

public class DynamicList <E>{
	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 <T> 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<E> list) {
		for(int i=0;i< list.size();i++) {
			add(list.get(i));
		}
	}
	
	/**
	 * 有上界限参数的方法
	 * @param list
	 */
	public <T extends E> void addAll(DynamicList<T> list) {
		for(int i=0;i< list.size();i++) {
			add(list.get(i));
		}
	}
}

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

DynamicList<Double> 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<Person<String,Integer>> dynamicList = new DynamicList<>();

2.泛型方法

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

	public static <T> 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 <T>{

	public int compareTo(T o);
}

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

public final class CusComparableA implements CusComparable<Integer>{

	@Override
	public int compareTo(Integer o) {
		return 0;
	}

}

4.类型参数的限定

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

1.上界为某个具体类

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

package com.wang.generic;

public class NumberPerson<U extends Number,V extends Number> 
	extends Person<U, V>{

	public NumberPerson(U attr1, V attr2) {
		super(attr1, attr2);
	}
	public double sum() {
		
		return getAttr1().doubleValue() + getAttr2().doubleValue();
		
	}
	
	/**
	 * 上界限为某个接口
	 * @param arr
	 * @return
	 */
	public static <T extends Comparable> 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<Integer,Double> numberPerson = 
				new NumberPerson<>(19,22.21);
		double sum = numberPerson.sum();
		System.out.println("sum:"+ sum);
	}
}

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

NumberPerson<Integer,Double> numberPerson = 
				new NumberPerson<>(19,22.21);
		double sum = numberPerson.sum();

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

2.上界为某个接口

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

public static <T extends Comparable<T>> 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<E> list) {
		for(int i=0;i< list.size();i++) {
			add(list.get(i));
		}
	}

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

DynamicList<Number> dynamicList = new DynamicList<>();
DynamicList<Integer> 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 <T extends E> void addAll(DynamicList<T> list) {
		for(int i=0;i< list.size();i++) {
			add(list.get(i));
		}
	}

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

4.总结

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

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

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

 

推荐阅读:

泛型与容器连载(三)解析通配符

泛型与容器连载(二)泛型的基本概念和原理

泛型与容器连载(一)泛型的基本概念和原理

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不安分的猿人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值