一、 集合框架
1.1、集合的概念
计算机的优势在于处理大量的数据,在编程开发中,为存储、处理大量的数据,必须具备相应的存储结构,数组也是可以用来存储处理大量类型相同的数据,但是数组有长度、数据类型的限制,操作不方便。在实际开发过程中,为了使大量数据的存储和处理更加方便,便衍生了集合这一概念。集合是一种存储数据个数不受限制、操作方式更灵活的数据存储结构。集合内部持有若干其他对象的引用,并对外提供访问接口。对开发人员来说,集合就像一个容器,内部存放了若干对象,这些集合中的对象也被称作元素。
Java中集合使用Collection接口和Map接口表示,其中
Collection接口下还有两个子接口,分别是List
和set
.
1.2、编程实现简易集合
通过编程实现一个简易的集合。
MyList
接口:
/**
* 简易的List接口
*/
public interface MyList {
/**
* 1、向集合中添加元素
* @param o object
* @return 添加成功则返回true
*/
boolean add(Object o);
/**
* 2、获取指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
Object get(int index);
/**
* 3、移除并返回集合中指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
Object remove(int index);
/**
* 4、获取集合中元素的个数
* @return 集合中元素的个数
*/
int size();
}
MyArrayList
类:
import java.util.Arrays;
/**
* 简易的ArrayList类
*/
public class MyArrayList implements MyList {
private Object[] elementData = {}; // 用来存储元素的数组
private int size = 0; // 容器大小
/**
* 1、向集合中添加元素
* @param o object
* @return 添加成功则返回true
*/
@Override
public boolean add(Object o) {
int capacity = size + 1;
if (capacity - elementData.length > 0){
elementData = Arrays.copyOf(elementData, capacity);
}
elementData[size++] = o;
return true;
}
/**
* 2、获取指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
@Override
public Object get(int index) {
return elementData[index];
}
/**
* 3、移除并返回集合中指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
@Override
public Object remove(int index) {
Object oldValue = elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return oldValue;
}
/**
* 4、获取集合中元素的个数
* @return 集合中元素的个数
*/
@Override
public int size() {
return size;
}
}
Test
类:
package com.codeke.java.test;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
// 1、实例化一个自定义实现的集合对象
MyList myList = new MyArrayList();
// 2、添加元素
myList.add(1);
myList.add(2);
myList.add(3);
myList.add(4);
// 3、打印集合的尺寸
System.out.println("myList.size() = " + myList.size());
// 4、移除下标为2的元素
Integer num = (Integer) myList.remove(2);
// 5、打印刚才移除的元素
System.out.println("num = " + num);
// 6、再次打印集合的尺寸
System.out.println("myList.size() = " + myList.size());
// 7、遍历集合中的元素并打印
for(int i = 0; i < myList.size(); i ++){
Integer n = (Integer) myList.get(i);
System.out.println(n);
}
}
}
解释:
本例通过编程实现了一个简易版的集合,包含MyList接口
和MyArrayList类
,基本实现了数据的存储、获取、删除,判断集合大小等功能。但该简易版的集合只是实现了最基本、最简单的功能,没有考虑在内存开销、计算性能等方面进行优化。
在本例测试类中的代码myList.add(1)
,实际上进行了自动装箱
,虚拟机执行的代码为myList.add(Integer.valueOf(1))
,在之前章节中介绍包装类时,提到包装类的作用之一便是作为和基本数据类型对应的引用类型存在,方便涉及到对象的操作,这里便是一个典型的场景。
在本例简易版集合的实现过程中,使用了java.lang.System类
中的arraycopy(Object src, int srcPos, Object dest, int destPos, int length)方法
,及java.util.Arrays类
中的copyOf(T[] original, int newLength)方法
。
java.lang.System
类中的arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
方法可以将固定数量的数组元素从原数组中的指定位置拷贝到目标数组的指定位置;java.util.Arrays
类中的copyOf(T[] original, int newLength)
方法可以将原数组中的元素拷贝到一个指定长度的新数组中,事实上,该方法底层仍然是在调用java.lang.System
类中的arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
方法,另外,该方法涉及到泛型的知识,关于泛型及java.util.Array
类等内容.
1.3、简单了解泛型
观察前面小节中简易版集合的案例,虽然该简易版集合不再有元素个数的限制,但是该简易集合中存储的元素类型都是Object
,在使用时不可避免的需要进行强制类型转换
,这在使用时仍然不方便,而且存在发生类型转换异常的风险。在Java
中,使用泛型这一特性便可以避免这些问题,这里先来简单了解一下泛型。
1.3.1、泛型的概念及作用
泛型,即参数化类型,泛型是一种代码模板技术。它允许开发人员在编写代码时将类
、接口
或方法
中要操作的类型参数化,到使用时再指明要操作的具体类型。
使用泛型带来的作用大致有以下几点:
- 可以
统一数据类型
,便于操作; - 避免了强制类型转换,降低出现类型转换
异常
的风险; - 实现代码的
模板化
,把数据类型当作参数传递,提高了代码重用性。
1.3.2、编写泛型代码
在编写代码的过程中使用泛型稍显抽象但仍然比较容易,这里介绍一些最基本的泛型的使用方式,关于泛型更复杂的内容这里暂不涉及。
首先,开发人员可以在编写一个方法时应用泛型。语法格式如下:
[修饰符] <T1[, T2, ..., Tn]> 返回值类型 方法名称([参数列表]) {
// 方法体
}
示例:
MyArrayUtils
类源码:
public class MyArrayUtils {
/**
* 打印数组元素
* @param array 需要被打印元素的数组
* @param separator 打印时元素间的分割符
* @param <E> 类型参数
*/
public static <E> void printArray(E[] array, String separator)
{
System.out.print("{ ");
for (int i = 0; i < array.length; i++) {
E element = array[i];
System.out.print(element + " " + (i == array.length - 1 ? "" : separator+ " "));
}
System.out.println("}");
}
}
Test
类源码:
public class Test {
public static void main(String[] args) {
// 实例化一些不同类型的数组
Integer[] integerArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] characterArray = { 'H', 'E', 'L', 'L', 'O' };
// 打印数组
MyArrayUtils.printArray(integerArray, ",");
MyArrayUtils.printArray(doubleArray, "|");
MyArrayUtils.printArray(characterArray, "^");
}
}
解释:
声明方法时,在方法返回类型之前增加类型参数声明部分(由尖括号分隔,如本例中的<E>
),这样的方法便成为一个泛型方法
。表示类型参数的标识符一般使用一个大写的英文字母。
类型参数声明部分,可以包含一个或多个类型参数 ,中间使用“ ,
”隔开。
类型参数能作为泛型方法得到的形参数据类型的占位符
,在方法被调用时,传入的实际参数类型决定了类型参数代表的具体类型,如果形参数据类型中没有使用方法的某类型参数,那么,该类型参数代表的具体类型为java.lang.Object
。
除了被用作方法形参的数据类型外,类型参数也可以被用来声明返回值类型
,也可以被用来声明方法中局部变量
的类型等。
注意类型参数只能代表引用类型,不能代表基本数据类型。类型参数不能和new关键字一起用来构造对象,即new T()
是不合法的。
注意泛型方法要防止重复定义方法,例如:public <E> boolean equals(E obj)
方法,因为可能和基类或接口中的方法声明冲突。
另外,开发人员也可以针对类
或接口
应用泛型。
语法格式如下:
class 类名<T1[, T2, ..., Tn]> {
// 成员变量声明部分,即属性
// 成员方法声明部分,即行为
}
示例:
MyArrayUtils
类源码:
public class MyArrayUtils<T> {
/**
* 打印数组元素
* @param array 需要被打印元素的数组
* @param separator 打印时元素间的分割符
*/
public void invertPrintArray(T[] array, String separator) {
System.out.print("{ ");
for (int i = array.length - 1; i >= 0; i--) {
T element = array[i];
System.out.print(element + " " + (i == 0 ? "" : separator + " "));
}
System.out.println("}");
}
}
Test
类源码:
public class Test {
public static void main(String[] args) {
// 实例化一些不同类型的数组
Integer[] integerArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] characterArray = { 'H', 'E', 'L', 'L', 'O' };
// 打印数组
MyArrayUtils<Integer> myArrayUtils1 = new MyArrayUtils<>();
myArrayUtils1.invertPrintArray(integerArray, ",");
MyArrayUtils<Double> myArrayUtils2 = new MyArrayUtils<>();
myArrayUtils2.invertPrintArray(doubleArray, "|");
MyArrayUtils<Character> myArrayUtils3 = new MyArrayUtils<>();
myArrayUtils3.invertPrintArray(characterArray, "^");
}
}
接口泛型的例子
MyArraysUtils1
接口:
public interface MyArrayUtils1<T> {
T PrintArray(T[] array, String separator);
}
interfaceMyList
泛型类继承泛型接口:
public class interfaceMyList<E> implements MyArrayUtils1<E> {
@Override
public E PrintArray(E[] array, String separator) {
System.out.print("{ ");
for (int i = 0; i < array.length; i++) {
E element = array[i];
System.out.print(element + " " + (i == array.length - 1 ? "" : separator + " "));
}
System.out.println("}");
return null;
}
}
Test
测试类源码:
public class Test {
public static void main(String[] args) {
Integer[] a = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
MyArrayUtils1<Integer> integer = new interfaceMyList<Integer>();
integer.PrintArray(a, "|");
}
}
解释:
声明类时,在类名后增加类型参数声明部分(由尖括号分隔,如本例中的<T>
),这样的类便成为一个泛型类。
泛型类
和泛型方法
一样,泛型类的类型参数声明部分,可以包含一个或多个类型参数 ,中间使用,
隔开。
泛型类在使用时,一般需要指定类型参数代表的具体类型;如果不指定,类型参数代表的类型即是java.lang.Object
。
泛型类的类型参数可以被用来声明实例成员变量的类型,可以被用来声明实例成员方法的参数类型、返回值类型,可以被用来声明局部变量的类型,等等。
泛型类中的类型参数无法应用到类中的静态成员上。请注意本例中的方法和上例中的泛型方法之间的区别。
和泛型方法一样,类型参数只能代表引用类型,不能代表基本数据类型;类型参数不能和new
关键字一起用来构造对象;泛型类中也要注意防止重复定义方法。
泛型接口和泛型类基本相同,即在声明接口时,在接口名后增加类型参数声明部分,形成泛型接口,这里不再赘述。
1.3.3、在简易集合中应用泛型
下面,在前文中编程实现的简易集合里应用泛型的知识。
下面是一个示例:
MyList接口
修改后的源码:
package com.bennett.test;
/**
* 简易的List接口
*/
public interface MyList<T> {
/**
* 向集合中添加元素
* @param t 要添加的元素
* @return 添加成功则返回true
*/
boolean add(T t);
/**
* 获取指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
T get(int index);
/**
* 移除并返回集合中指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
T remove(int index);
/**
* 获取集合中元素的个数
* @return 集合中元素的个数
*/
int size();
}
MyArrayList
类修改后的源码:
package com.bennett.test;
import java.util.Arrays;
/**
* 简易的ArrayList类
*/
public class MyArrayList<E> implements MyList<E> {
private Object[] elementData = {}; // 用来存储元素的数组
private int size = 0; // 容器大小
/**
* 向集合中添加元素
* @param e 要添加的元素
* @return 添加成功则返回true
*/
@Override
public boolean add(E e) {
int capacity = size + 1;
if (capacity - elementData.length > 0){
elementData = Arrays.copyOf(elementData, capacity);
}
elementData[size++] = e;
return true;
}
/**
* 获取指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
@Override
public E get(int index) {
return (E) elementData[index];
}
/**
* 移除并返回集合中指定下标处的元素
* @param index 下标
* @return 指定下标处的元素
*/
@Override
public E remove(int index) {
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return oldValue;
}
/**
* 获取集合中元素的个数
* @return 集合中元素的个数
*/
@Override
public int size() {
return size;
}
}
测试类Test
类修改后的源码:
package com.bennett.test;
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
// 实例化一个自定义实现的集合对象
MyList<Integer> myList = new MyArrayList<Integer>();
// MyList<Integer> myList = new MyArrayList<>(); 这句代码和上
// 添加元素
myList.add(1);
myList.add(2);
myList.add(3);
myList.add(4);
// myList.add(5.0); 这句代码会报错,myList中只能添加Integer类型的对象
// 打印集合的尺寸
System.out.println("myList.size() = " + myList.size());
// 移除下标为2的元素
Integer num = myList.remove(2);
// 打印刚才移除的元素
System.out.println("num = " + num);
// 再次打印集合的尺寸
System.out.println("myList.size() = " + myList.size());
// 遍历集合中的元素并打印
for(int i = 0; i < myList.size(); i ++){
Integer n = myList.get(i);
System.out.println(n);
}
}
}
解释:
本例在简易集合中应用了泛型,在本例的测试类中,代码MyList<Integer> myList = new MyArrayList<Integer>()
指定了类型参数代表的具体类型是Integer
,于是,变量myList
所引用的简易集合对象中便只能保存Integer
类型的对象,从该集合对象中取出的元素自然也是Integer
类型的,不用再进行强制类型转换。