集合
集合概述
由于数组的灵活性不够,Java提供一些特殊的集合类,可以存储任意类型的对象,并且长度可以变化。
分为单列集合Collection和多列集合Map;
Collection
Collection:单列集合的根接口,用来储存符合某些要求的元素。
List集合主要存储有序可重复的元素。
Set集合主要存储无序不可重复的元素。
ArrayList 实现类;
List 子接口: LinkedList 实现类;
Vector 实现类;
Collection 接口:
HashSet 实现类; --> 子类 LinkedHashSet;
Set 子接口:
TreeSet 实现类;
Map
Map:双列集合的根接口,用来存储 具有 键(key)和 值(value)映射关系的元素;
Hashtable 实现类; --> 子类 Prorerties
Map 接口: HashMap 实现类; --> 子类 LinkedHashMap
TreeMap 实现类;
List接口
List接口 继承 Collection接口,实现了List接口的对象成为List集合。
List集合中允许出现重复的元素,元素以线性方式进行存储,可以通过索引的方式访问元素。
List集合中的元素有序,即 元素的存入顺序和取出顺序一致。
ArrayList集合
ArrayList集合是List接口的一个实现类, 可以看作是一个长度可变得数组,它的内部的数组存储结构是数组;
在对集合进行某位置的删除和增加操作时会创建一个新的数组,不适合大量的增删操作,但是方便利用索引取出某元素,方便遍历和查找元素。
ArrayList的大部分方法都是从 Collection 和 List 接口中继承过来的;
一些方法举例:
ArrayList list = new ArrayList();
list.add("str1"); //向 list集合中增加元素
list.add("str2");
System.out.println(list.size()); // .size() 集合的大小
System.out.println(list.get(1)); // .get(index) 取出 索引为index的元素
ArrayList 好似 线性表;
LinkedList集合
LinkedList集合是List接口的一个实现类,好似数据接口中的链表,方便增删元素;
该集合 内部 有 两个 Node 类型 的 fist 和 last 属性 维护 一个双向循环链表;
LinkedList的方法除了从Collection 和 List接口中继承过来的 还有 一些专门处理增删查改的一些方法;
一些方法的举例:
LinkedList link = new LinkedList();
link.add("1"); //向集合中添加元素
link.add("2");
link.offer("offer"); //向集合尾部增加元素;
link.push("push"); //向集合头部增加元素;
System.out.println(link);
Object obj = link.getFirst(); //得到第一个元素
link.removeFirst(); //删除集合中的第一个元素
link.pollLast(); //删除集合中的最后一个元素
System.out.println(link);
link.add(1,"center"); //在 索引为 1 的位置上 添加 center 元素;
System.out.println(link);
Collection集合遍历
Iterator遍历集合
Iterator接口是java集合框架中的一员, Collection 和 Map 主要用来存储元素,而 Iterator 主要用来 遍历 Collection集合中的元素;所以 Iterator对象也叫迭代器;
通过下面例子 介绍 Iteractor迭代器的使用:
LinkedList link = new LinkedList();
link.add("1"); //向集合中添加元素
link.add("2");
Iterator iterator = link.iterator(); //通过 Collection集合中的iterator()方法获取Iterator对象
while (iterator.hasNext()){ // .hasNext()判断是否有下一个元素
System.out.println(iterator.next()); // .next()获取下一个元素;
}
foreach遍历集合
从JDK5开始提供了foreach循环。 用于遍历数组或者集合中的元素; 就是增强 for 循环;
语法如下:
for( 容器中元素类型 临时变量 : 容器变量) {
执行语句;
}
foreach循环注意点: 在循环中不能够改变 集合或数组元素的值,只能对其进行访问;
JDK8 中的 forEach 遍历集合
list.forEach(obj -> System.out.println(obj)); // list 集合;
Set接口
Set 和 List 接口类似,方法和 Collection中的方法基本一致, 只是比 Collection 接口更加严格;
与 List 不同的是,Set接口中的元素无序, 并且以某种规则保证存入的元素不重复;
Set接口的两个实现类:HsahSet (根据对象的哈希值来确定元素在集合中的存储位置) 和 TreeSet ( 以二叉树的方式存储数据 )
HashSet集合
Set 接口的一个实现类,所存储的元素不可重复,且无序;
为什么 HashSet 中的 元素 不会重复?
答:当向 HashSet 集合中 添加 一个元素时,首先会 调用 该元素的 hashCode() 方法获取该元素的 哈希值。,通过 哈希值 来确定元素的存储位置,如果该存储位置上为空,就直接存入。如果不为空就 调用该 元素的 equal() 方法 与 该位置上的值进行比较,若为false就存入,若为true 就将该元素舍去;
因此,由于在判断存入的元素是否 重复时 需要用到 hashCode() 和 equal()方法 ,所以需要 在存入对象是,在此对象属于的类中 重写 Object类中的 hashCode()和 equal() 方法;
TreeSet集合
内部采用平衡二叉树的存储结构; 具有排序功能;
平衡二叉树:任意两个节点的左右子树的深度差的绝对值不超过1,也叫自平衡二叉查找树;
任意结点的左子树上的每个节点的值都比该节点小,右子树上的每个节点的值比该节点大;
TreeSet也不会存储重复的元素,他在继承Set接口的基础上有一些特有的方法;
TreeSet类中的方法举例:
TreeSet ts = new TreeSet();
ts.first(); //获取首元素
ts.last(); //获取尾元素
ts.lower(10); //获取集合中小于 10 的 最大元素 ,没有返回 null;
ts.floor(10); //获取集合中小于或等于 10 的 最大元素 ,没有返回 null;
ts.higher(10); //获取集合中大于 10 的 最小元素 ,没有返回 null;
ts.ceiling(10); //获取集合中大于或等于 10 的 最小元素 ,没有返回 null;
ts.pollFirst(); //删除第一个元素并返回;
ts.pollLast() ; //删除最后一个元素并返回;
为什么TreeSet中的元素会自动排序?
不论向TreeSet集合中添加的元素的顺序如何,这些元素都能够自动排序; 这是因为添加过程中 都会对集合中的元素进行比较。比较时会调用compareTo()方法,该方法在Comparable接口中定义的。
因此,要想对集合中的元素进行排序,就必须实现Comparable接口和compareTo()方法,在Java中的大部分类都实现了Comparable接口,并且实现了 compareTo()方法,如 Integer,,Double,String类;
也可以向 TreeSet 集合中 存入 自定义的 数据类型。 自定义的类型没有实现 comparable接口, 因此无法进行排序操作; Java提供两种排序规则解决这个问题。
-
自然排序‘
TreeSet 集合中存储的对象所属的类必须实现 Compearable 接口,并重写 compareTo()方法;
-
定制排序
可以 通过实现 Comparator接口 自定义一个 比较器类;
在实例化 TreeSet 时,将 比较器的对象 作为 构造方法 的 参数;
Map接口
Map接口是一种双列集合,每一个元素都有 一个键对象 Key 和一个 值对象 Value; Key 和 Value 之间存在一种一对一的映射关系; 两个对象的数据类型可以任意,Key不能重复,这样就可以通过 Key 来找到唯一的Value;
HashMap集合
HashMap是Map接口的一个实体类,底层由哈希表构成,实际上就是一个 数组+链表 的组合体 。集合的主体部分是一个 Entry数组,用来存储键和值 Entry<E,V>; 数组的长度成为HashMap的容量capacity;每一个 数组 中又 可以存有一个链表,一般称数组为水平的,链表是竖直的;每个竖直部分成为一个桶bucket;引入链表是为了解决 哈希冲突;
其结构约如下:
// 竖着的 链表 bucket 桶
Entry<K,V> Entry<K,V> Entry<K,V> Entry<K,V> Entry<K,V> Entry<K,V> //横向的数组 ↓ ↓ ↓ ↓ Entry<K,V> Entry<K,V> Entry<K,V> Entry<K,V> ↓ ↓ Entry<K,V> Entry<K,V>
向 HashMap 添加元素时,先调用 添加元素的 key对象 的 hash(k) 方法,找到要存储的桶位置(在数组中的位置),然后判断该位置上是否为空,若为空,则直接添加元素;若不为空,则在链表中挨个比较 是否 集合中的元素 的键值 equal(k);若相等,则将 新值替换旧值 ,并返回原来的旧值;若不相同,则在链表的头部添加 一个新的节点插入新添加的元素;
HashMap 一些方法举例:
Map map = new HashMap(); // 多态的应用 , map 只能调用 Map接口中存在的方法
// 由于 HashMap类必须重写Map中的方法,所以map调用的方法都是HashMap中重写后的方法;
map.put(1, "d"); // 向 Map 中存储数据
map.containsKey(1); //是否存在 Key==1 的 元素,存在返回true,否则返回 false;
map.get(1); //获得指定 k对象 的 值;
map.keySet(); //以 Set集合的形式 得到key的集合并返回;
map.values(); //以 Collection 集合的形式 得到 Value的集合并返回
map.replace(1,"e"); //将 key=1 的 值 替换为 “e”;
map.remove(1); //删除 key=1 的 键值对元素;
当添加元素Key的值 已经存在时,则会会新的value值替换旧的Value值;
Map集合遍历
Map集合的遍历和Collection集合的遍历基本相同。
Iterator遍历jMap集合
将Map集合转换为 Iterator接口对象,有两种方式:keySet()方法 和 entrySet()方法;
-
keySet()方法
先将 Map集合中的 键 存到 Set集合中,然后生成 Iterator接口对象;
代码举例:
Map map = new HashMap();
Set set = map.keySet(); // 将 键key 存入 set 集合;
Iterator it = set.iterator(); // 生成set 集合的 迭代器;(一个键值迭代器)
while (it.hasNext()){
Object key = it.next();
Object value = map.get(key); //得到 key 对应的 value;
System.out.println(key + "=" + value);
}
-
entrySet()方法。
该方法是将 Map中的 键值对 作为 一个整体返回 到 Set集合中, 然后 生成 Set集合的 迭代器。 Entry 是 Map接口内部类,可以通过Entry 类型的元素 取出Map 中元素的 键 和 值(通过 getKey()方法 和 getValue() 方法);
代码举例:
Map map = new HashMap(); Set set = map.entrySet(); // entrySet 将键值对作为整体 返回给set Iterator it = set.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); //Map.Entry entry类型 Object key = entry.getKey(); Object value = entry.getValue(); System.out.println(key + "=" + value); }
forEach遍历Map集合
map.forEach((key, value) ->System.out.println(key+"="+value));
注意点:HashMap 集合 不能保证存入和取出的元素的顺序;若想要 存入和取出的顺序相同,Java提供一个 LinkedHashMap集合,它是HashMap的子类。和LinkedList 类似,用双向链表维护内部元素的关系;
TreeMap集合
存放键值之间的映射,不允许键重复;与HashMap集合的区别:TreeMap中的元素都是按照顺序排列的,内部存储结构是二叉树;
该集合 和 TreeSet集合 类似, 存储过程中都需要 进行元素比较,实现Comparable接口。也可以实现自己的比较器Comparator
Properties集合
Map中的另一个实现类HashTable实现类,与HashMap类似,主要区别在于 HashTable 是线程安全的,但是其效率不如HashMap,现在基本已经被HashMap代替;但 Properties类 是 HashTable的一个子类,该类经常用来存储应用的配置项;
泛型
泛型就是 规定 存入集合的数据类型;使用方法就是 在集合类型名后面加一个<参数化类型>,里面放入要存入的数据类型;
使用举例代码如下:
ArrayList<String> list = new ArrayList<>(); // <Srting> 只能往集合中传入 String 类型 的元素 list.add("String"); list.add(1); // 报错 , 因为 list集合 的泛型 为字符串;
为什么使用泛型?
答:因为可以向集合中存入任意类型的对象元素,当存入一个对象元素,在取出时,集合会忘记 该对象的数据类型,于是会将元素编译成Object 类型;若在取出时进行强制类型转化,容易出现错误(因为不同类型之间可能不能强制转化);