0. 前言
我们在前面的学习中,常用数据进行同类型数据的存放。但在JAVA中,数组有个局限性:数组创建出来之后就是定长的,资源的利用不够灵活。
本篇文章我们学习集合与泛型,解决数组局限性的问题。
1. 基本概念与入门
集合是一些已经封装好的API,又称为容器,它的长度是可变的。
常用的集合有:继承 Collection 的 List 、 Set ,如ArrayList、HashSet 以及实现Map接口的 Map , 如HashMap。
1.1入门案例:
定义实验所需要的类:
public class CollDemo {
//Attributes
private int age;
private String name;
public void sayHello(){
System.out.println("Hello");
}
//篇幅有限,省略掉Getter、Setter、构造方法等
}
用集合实现与数组类似的装入数据功能:
public static void main(String[] args) {
//定义ArrayList ,插入数据并查看长度
ArrayList al = new ArrayList();
System.out.println(al.size());
al.add(new CollDemo());
System.out.println(al.size());
//取出 ArrayList 中 index为0的数据
System.out.println(al.get(0));
}
1.2 集合接收的对象
默认情况下,集合接收的都是Object对象。因为集合类中容纳的都是指向Object类对象的指针,所以在默认情况下,任何对象都可以插入集合。同时,在默认情况下取出集合中的数据时,取出的数据类型都是Object,这意味着需要取出来进行运算时都需要进行类型转换,举个例子:
public static void main(String[] args) {
//插入不同类型的数据,然后分别取出进行运算
ArrayList al = new ArrayList();
al.add(new Integer(1));
al.add(new Double(1.1));
//取出数据需要强转
Integer i = (Integer) al.get(0);
Double d = (Double) al.get(1);
System.out.println(i);
System.out.println(d);
}
从上面的例子以及集合的一些基本概念我们发现,集合比数组要更灵活:它是容器,长度可变;默认情况下它能装入和取出的数据类型都是Object,不受类型的限制。
但是在实际使用情况下,默认情况下的集合由于接收的数据类型是Obejct,过于灵活,一来难以进行统一的类型管理,二来取出数据的时候需要频繁的类型转换,效率比较底。针对这个问题,我们使用泛型来对集合进行数据类型的规范管理。
2. 泛型
泛型本质是一种语法糖,在JDK1.5后出现。现在集合的使用常常配合着泛型对数据类型进行规范管理。
2.1 入门案例
public static void main(String[] args) {
//给ArrayList 加入 Integer 泛型
ArrayList<Integer> al1 = new ArrayList<Integer>();
//省略的表示方式:省略掉new后面的泛型,少一丢丢代码
ArrayList<Integer> al2 = new ArrayList<>();
al1.add(new Integer(1));
//下面被注释的代码会报错,因为泛型已规定了能插入的数据
//al1.add(new Double(1.1));
//有了泛型,不需要再进行类型转换即可取出
Integer i = al1.get(0);
}
2.2 支持子类的插入
泛型规定的集合也可兼容该类型的子类插入,保留了灵活性,但是会默认向上造型,举个例子:
public static void main(String[] args) {
//数字类型的包装类都继承自Number类
ArrayList<Number> al = new ArrayList<>();
al.add(new Integer(1));
al.add(new Double(1.1));
//存入数据时向上造型,所以此处不需要类型转换
Number n = al.get(0);
//此处取出数据时向下转型了,需要类型转换
Integer i = (Integer) al.get(0);
Double d = (Double) al.get(1);
}
3. 常用集合
本节我们详细介绍常用集合与这些集合的常用方法。
3.1 List 集合
List 集合指的是 实现了 List 接口的集合。常见的List集合有 ArrayList、 LinkedList 等。本小节我们主要学习ArrayList 与 LinkedList。
实现的接口:
//截取自ArrayList的源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3.1.1 ArrayList
ArrayList 是顺序结构,有序,可重复。
在上面章节的学习中,我们都以ArrayList为例子,已经初步接触了它的常用方法:add、get方法,接下来我们复习一遍,再接着学习、尝试:
(1)add、get 插入、获取数据
public static void main(String[] args) {
ArrayList<CollDemo> al = new ArrayList<>();
//CollDemo 是一开始就自定义好的类,下面进行插入、取出
al.add(new CollDemo());
al.add(new CollDemo());
CollDemo cd1 = al.get(0);
System.out.println(cd1);
CollDemo cd2 = al.get(1);
System.out.println(cd2);
}
(2)remove 移除对象、 size 集合长度
public static void main(String[] args) {
ArrayList<CollDemo> al = new ArrayList<>();
//CollDemo 是一开始就自定义好的类,下面进行移除
al.add(new CollDemo());
al.add(new CollDemo());
CollDemo cd1 = al.get(0);
boolean isRemove = al.remove(cd1);
System.out.println("移除第一个元素是否成功:"+isRemove);
System.out.println("移除后的长度为:"+al.size());
}
(3)toArray 将集合转化为数组
提供转换为数组的方法,更显集合框架的灵活性。
public static void main(String[] args) {
ArrayList<Integer> al = new ArrayList<Integer>();
//插入数据,再转换为数组
for(int i = 0 ; i < 9 ; i++) {
al.add(i);
}
Integer [] i = al.toArray(new Integer[] {});
System.out.println(i.length);
System.out.println(i[0]);
}
3.2.2 LinkedList
LinkedList 是链表结构,插入、删除效率较ArrayList要高。有序、可重复。
下面以LinkedList为例,接着介绍一些List常用方法:
(1)isEmpty判断是否为空
public static void main(String[] args) {
LinkedList<Integer> ll = new LinkedList<>();
boolean isEmpty = ll.isEmpty();
System.out.println("刚创建的LinkedList是否为空:"+isEmpty);
ll.add(1);
System.out.println("已插入数据的LinkedList是否为空:"+ll.isEmpty());
}
(2)contains判断是否包含指定元素
public static void main(String[] args) {
LinkedList<Integer> ll = new LinkedList<>();
ll.add(3);
ll.add(4);
System.out.println("是否包含3:"+ll.contains(3));
System.out.println("是否包含5:"+ll.contains(5));
}
(3)set替换某个下标的元素
public static void main(String[] args) {
LinkedList<Integer> ll = new LinkedList<>();
ll.add(3);
ll.add(4);
System.out.println("没有替换时是"+ll.get(0));
ll.set(0, 1);
System.out.println("替换后的0位元素是"+ll.get(0));
}
(4)遍历迭代器
public static void main(String[] args) {
LinkedList<Integer> ll = new LinkedList<>();
ll.add(1);
ll.add(1);
ll.add(2);
ll.add(2);
//传统遍历方式:
for(int i = 0 ; i < ll.size(); i++) {
System.out.println(ll.get(i));
}
//迭代器:
Iterator<Integer> it = ll.iterator();
while(it.hasNext()) {
Integer i = it.next();
System.out.println(i);
}
}
3.2.3 List集合特性总结
通过对ArrayList 、 LinkedList 的学习,我们了解到了一些List集合的常用方法,注意,上面提到的方法在举例时虽然分别用的是ArrayList 、LinkedList 进行举例,但它们在List里都是通用的。
(1)ArrayList 与 LinkedList 的异同
相同之处:都实现了List接口,都是有序、可重复的表结构。
不同之处:
ArrayList 是顺序表,类似于数组,但长度可变。定位快,适合多查找、少增删的情况。
LinkedList 是链表,查找相对ArrayList比较慢,但插入快,适合需要频繁更新操作的情况。这种更新主要针对的是对中间数据的更新,至于头尾数据的更新,两者的效率会因不同情况而不同。
举个例子,就新增插入(尾部操作)来说:
public static void main(String[] args) {
//插入效率比较
LinkedList<Integer> ll = new LinkedList<>();
ArrayList<Integer> al = new ArrayList<>();
Long linkTimeBegin = System.currentTimeMillis();
insert(ll);
Long linkTimeEnd = System.currentTimeMillis();
System.out.println("linked增添用时"+(linkTimeEnd-linkTimeBegin)+"ms");
Long ArrTimeBeging = System.currentTimeMillis();
insert(al);
Long ArrTimeEnd = System.currentTimeMillis();
System.out.println("arr增添用时"+(ArrTimeEnd-ArrTimeBeging)+"ms");
}
public static void insert(List<Integer> l) {
for(int i = 0 ; i < 1000000 ; i ++) {
l.add(i);
}
}
在测试中,我们发现两种List更新效率比较不是绝对的,因为链表在插入的时候,本身是存在一定的开销。至于其他情况的测试,留给读者拓展。
(2)LinkedList的简单拓展
LinkedList 是链表结构,除了实现List接口,还实现了双向链表、队列接口,以下是例子
public static void main(String[] args) {
LinkedList<Integer> ll = new LinkedList<Integer>();
ll.add(1);
//在双向链表尾部插入数据
ll.addLast(2);
//在双向链表首部插入数据
ll.addFirst(0);
//查看
System.out.println(ll.getFirst());
System.out.println(ll.getLast());
}
public static void main(String[] args) {
//队列
Queue<Integer> ql = new LinkedList<Integer>();
//队列先入先出
ql.offer(1);
ql.offer(2);
ql.offer(3);
//查看
System.out.println(ql.peek());
//取出
System.out.println(ql.poll());
//再取一个
System.out.println(ql.poll());
}
3.2 Map集合
Map集合是指实现了Map 接口的集合,是由键值对存储的数据结构,也就是哈希表。
Map是无序不重复(key不能重复)的集合,常见的Map主要由HashMap、TreeMap等。
在结合泛型声明Map时,需要同时定义 键 和 值 。
3.2.1 HashMap
我们通过HashMap 来详细学习Map的用法。
(1)常用方法
插入键值对put、根据key取出值get(key)、清空clear(),以下是例子:
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
//使用put 方法插入键值对
hm.put("No.1", "第一个字符串值");
hm.put("No.2", "猪八戒");
hm.put("No.3", "喜羊羊");
//键是唯一的,插入已存在的键的数据会覆盖
hm.put("No.1", "武松");
//根据key取出值
System.out.println(hm.get("No.1"));
System.out.println(hm.get("No.3"));
//清空
hm.clear();
System.out.println(hm);
}
}
(2)数据结构
HashMap 是一种哈希表,也是一种散列表,具体通过数组结合链表或者红黑树实现。
关于该数据结构的具体学习交给读者拓展。
3.2.2 TreeMap
TreeMap是一种红黑树,也实现了put、get、remove等方法。具体实现方式需要参考红黑树的实现方式,要详细搞清楚其中的原理,篇幅有限,请读者移步至红黑树的学习。
3.3 Set集合
Set集合实现了Set接口。
Set中的元素不能重复,同时它也是无序的。
常见的Set有HashSet 、 TreeSet,这里主要介绍HashSet。
3.3.1 HashSet
(1)无序且不重复
我们使用HashSet 来测试 Set的无序且不重复的特性:
public static void main(String[] args) {
HashSet<Integer> hs = new HashSet<Integer>();
hs.add(1);
hs.add(5);
hs.add(3);
//输出,发现并不是按照输入顺序来进行排序,具体排序方式根据不同的JVM而定
System.out.println(hs);
//插入相同数据,发现并没有任何改变
hs.add(5);
hs.add(1);
System.out.println(hs);
}
(2)遍历
遍历Set集合需要用到迭代器或者增强for循环
public static void main(String[] args) {
HashSet<Integer> hs = new HashSet<Integer>();
for (int i = 0; i < 5; i++) {
hs.add(i);
}
//迭代器iterator
for (Iterator<Integer> iterator = hs.iterator(); iterator.hasNext();) {
Integer i = (Integer) iterator.next();
System.out.println(i);
}
//增强for循环
for (Integer i : hs) {
System.out.println(i);
}
}
3.3.2 HashMap与HashSet的关系
HashSet实际上是基于HashMap的,其内部实现了一个HashMap,我们可以通过观察源码得知:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
.......以下源码省略
当然,更深层的实现方式读者可自行再进一步探究,此处不赘述。
4. 总结与补充
泛型还支持通配符、泛型转型等操作,且并不是所有的类都支持泛型。这些关于泛型的详细学习,读者可移步自行学习。
最后,我们简单总结以下本章的知识:我们先学习了集合与泛型的基本概念,然后通过有序的List集合进行入门学习,接着我们学习了集合框架中更多的集合:无序且key不重复的Map、无序且值不重复的Set。 我们下一篇见!