目录
- 集合输出
- Iterator迭代器
- ListIterator
- Enumeration
- foreach
- Map 接口
- HashMap
- Hashtable
- HashMap 与 Hashtable 的区别
- TreeMap
- 关于 Map 集合的输出
- Map.Entry
- Collections 类
- 分析 equals、hashCode 与内存泄露
- 总结
集合输出
我们已经知道了如何使用集合,那么集合如何输出呢?
集合输出,有以下四种方式:
-
Iteratoor
迭代输出(90%) -
ListIterator
(5%) - Enumeration(1%)
- foreach(4%)
在集合输出时,我们应该优先采用Iterator
进行输出
Iterator迭代器
Iterator
属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
Iterator
为接口,我们进行使用时,需要调用Collection
接口的 iterator() 方法获取实例化的Iterator
对象
该接口拥用以下方法:
No. | 方法名称 | 描述 |
1 | boolean hasNext() | 是否有下一个元素 |
2 | E next() | 取出内容 |
3 | void remove() | 删除当前内容 |
注意:
- 在使用Iterator对象时,我们应该先使用hasNext()进行判断,该方法不仅仅是判断是否存在下一个元素,其还会将Iterator中的操作指针指向下一条元素。
- 而在Iterator对象未调用hasNext()方法时,其初始指针位于集合的"-1"位置上,如果我们直接使用next()方法取出内容,将会直接抛出异常。如下图所示
- 示例代码:
public class IteratorDemo01 {
public static void main(String[] args) {
Collection<String> all = new ArrayList<String>();
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
Iterator<String> iter = all.iterator();
while (iter.hasNext()) {// 判断是否有下一个元素
String str = iter.next(); // 取出当前元素
System.out.print(str + "、");
}
}
}
控制台输出:
进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。
public class IteratorDemo02 {
public static void main(String[] args) {
Collection<String> all = new ArrayList<String>();
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
Iterator<String> iter = all.iterator();
while (iter.hasNext()) {// 判断是否有下一个元素
String str = iter.next(); // 取出当前元素
if (str.equals("C")) {
all.remove(str); // 错误的,调用了集合中的删除
} else {
System.out.print(str + "、");
}
}
}
}
应将while循环中的代码改为:
while (iter.hasNext()) {// 判断是否有下一个元素
String str = iter.next(); // 取出当前元素
if (str.equals("C")) {
iter.remove(); // 正确的,调用了Iterator中的删除
} else {
System.out.print(str + "、");
}
}
控制台输出如下:
但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素的操作。
Iterator
接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须 使用其子接口 —— ListIterator
ListIterator
ListIterator
是可以进行双向输出的迭代接口
- 其为
Iterator
的子接口,在其父接口Iterator
的基础上增加了以下操作方法
No. | 方法名称 | 描述 |
1 | void add(E e) | 增加元素 |
2 | boolean hasPrevious() | 判断是否有前一个元素 |
3 | E previous() | 取出前一个元素 |
4 | void set(E e) | 修改元素的内容 |
5 | int previousIndex() | 前一个索引位置 |
6 | int nextIndex() | 下一个索引位置 |
- 如果要想使用
ListIterator
接口,则必须使用List
接口的 listIterator() 获取其实例化对象 - 代码示例:
public class ListIteratorDemo {
public static void main(String[] args) {
List<String> all = new ArrayList<String>();
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
ListIterator<String> iter = all.listIterator();
System.out.print("从前向后输出:");
while (iter.hasNext()) {
System.out.print(iter.next() + "、");
}
System.out.print("\n从后向前输出:");
while (iter.hasPrevious()) {
System.out.print(iter.previous() + "、");
}
}
}
控制台输出:
但是,此处有一点需要注意的是,如果要想进行由后向前的输出,则首先必须先进行由前向后的输出。
Enumeration
Enumeration
是一个非常古老的输出接口,其也是一个元老级的输出接口,最早的动态数组使用Vector
完成,那么只 要是使用了 Vector
则就必须使用 Enumeration
进行输出。
- 常用方法如下:
No. | 方法名称 | 描述 |
1 | boolean hasMoreElements() | 判断是否有下一个元素 |
2 | E nextElement() | 取出当前元素 |
- 是如果要想使用
Enumeration
接口,则必须使用Vector
接口的 elements() 获取其实例化对象 - 代码示例:
public class EnumerationDemo01 {
public static void main(String[] args) {
Vector<String> v = new Vector<String>();
v.add("A");
v.add("B");
v.add("C");
Enumeration<String> enu = v.elements();
while (enu.hasMoreElements()) {
System.out.println(enu.nextElement());
}
}
}
控制台输出:
需要注意的是,在大部分的情况下,此接口都不再使用了,但是对于一些古老的类库,本身依然要使用此接口进行操作,所以此接口我们也需要掌握。
foreach
foreach 是一种for循环的简化方式,可以用来输出数组的内容,那么也可以输出集合中的内容。
代码示例:
public class ForeachDemo {
public static void main(String[] args) {
Collection<String> all = new ArrayList<String>();
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
for (String str : all) {
System.out.println(str) ;
}
}
}
控制台输出:
在遍历集合时,其内部实现自动选择效率最高的方式,即 Iterator
迭代
在使用 foreach 输出的时候一定要注意的是,里面的操作泛型要指定具体的类型,这样在输出的时候才会更加有针对性
Map 接口
在Collection
中,每次我们都只能操作一个对象,如果我们需要操作一对对象时,那我们就必须使用Map时,类似于以下这种情况:
- 张三 ----> 123456
- 李四 ----> 123456
那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key→value 的形式保存,也称为键值对。
此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下:
No. | 方法名称 | 描述 |
1 | void clear() | 清空 Map 集合中的内容 |
2 | boolean containsKey(Object key) | 判断集合中是否存在指定的 key |
3 | boolean containsValue(Object value) | 判断集合中是否存在指定的 value |
4 | Set> entrySet() | 将 Map 接口变为 Set 集合 |
5 | V get(Object key) | 根据 key 找到其对应的value |
6 | boolean isEmpty() | 判断是否为空 |
7 | Set keySet() | 将全部的 key 变为 Set 集合 |
8 | Collection values() | 将全部的 value 变为 Collection 集合 |
9 | V put(K key,V value) | 向集合中增加内容 |
10 | void putAll(Map m) | 增加一组集合 |
11 | V remove(Object key) | 根据 key 删除内容 |
Map
本身是一个接口,所以一般会使用以下的几个子类:HashMap
、TreeMap
、Hashtable
HashMap
HashMap
为Map
的子类,是属于无序存放的
- 代码示例:向集合中增加内容
public class HashMapDemo01 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "张三A");
map.put(1, "张三B"); // 新的内容替换掉旧的内容
map.put(2, "李四");
map.put(3, "王五");
String val = map.get(6);
System.out.println(val);
}
}
控制台输出:
以上的操作是 Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到 了则返回具体的内容。
- 代码示例:得到全部的 key 或 value
public class HashMapDemo02 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "张三A");
map.put(2, "李四");
map.put(3, "王五");
Set<Integer> set = map.keySet(); // 得到全部的key
Collection<String> value = map.values(); // 得到全部的value
Iterator<Integer> iter1 = set.iterator();
Iterator<String> iter2 = value.iterator();
System.out.print("全部的key:");
while (iter1.hasNext()) {
System.out.print(iter1.next() + "、");
}
System.out.print("\n全部的value:");
while (iter2.hasNext()) {
System.out.print(iter2.next() + "、");
}
}
}
控制台输出:
- 代码示例:循环输出
Map
中的全部内容
public class HashMapDemo03 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("ZS", "张三");
map.put("LS", "李四");
map.put("WW", "王五");
map.put("ZL", "赵六");
map.put("SQ", "孙七");
Set<String> set = map.keySet(); // 得到全部的key
Iterator<String> iter = set.iterator();
while (iter.hasNext()) {
String i = iter.next(); // 得到key
System.out.println(i + " --:> " + map.get(i));
}
}
}
控制台输出:
Hashtable
Hashtable
是一个最早的 key→value 的操作类,本身是在 JDK 1.0 的时候推出的。其基本操作与 HashMap
是类似的。
public class HashtableDemo01 {
public static void main(String[] args) {
Map<String, Integer> numbers = new Hashtable<String, Integer>();
numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);
Integer n = numbers.get("two");
if (n != null) {
System.out.println("two = " + n);
}
}
}
控制台输出:
操作时基本上与HashMap
并无区别,都是以Map
为操作标准,但是
Hashtable
中是不能向集合中插入 null 值的。
HashMap 与 Hashtable 的区别
-
Hashtable
是JDK1.0时推出,比较旧,HashMap
是JDK1.2推出的,是新的操作类 -
Hashtable
是线程安全的,性能较低,HashMap
是线程不安全的,性能较高 -
Hashtable
不允许设置,否则将出现空指向异常,HashMap
允许设置为 null
TreeMap
TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以为任意的对象,但是要求对象所在的类必须实现 Comparable 接口
- 代码示例:
public class TreeMapDemo01 {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>();
map.put("ZS", "张三");
map.put("LS", "李四");
map.put("WW", "王五");
map.put("ZL", "赵六");
map.put("SQ", "孙七");
Set<String> set = map.keySet(); // 得到全部的key
Iterator<String> iter = set.iterator();
while (iter.hasNext()) {
String i = iter.next(); // 得到key
System.out.println(i + " --:> " + map.get(i));
}
}
}
控制台输出:
此时的结果已经排序成功了,但是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类只需要知道其特点即可。
关于 Map 集合的输出
Map接口没有提供实例Iterator
接口的方法,不能直接使用Iterator
进行输出
如果我们需要使用 Iterator 进行输出的话,步骤如下:
- 使用 Map 接口中的 entrySet()方法将 Map 接口的全部内容变为 Set 集合
- 可以使用 Set 接口中定义的 iterator()方法为 Iterator 接口进行实例化
- 之后使用 Iterator 接口进行迭代输出,每一次的迭代都可以取得一个 Map.Entry 的实例
- 通过 Map.Entry 进行 key 和 value 的分离
Map.Entry
Map.Entry
本身是一个接口。此接口是定义在 Map
接口内部的,是 Map
的内部接口。此内部接口使用 static 进行定义, 所以此接口将成为外部接口。
实际上来讲,对于每一个存放到 Map
集合中的 key 和 value 都是将其变为了 Map.Entry
,并且将 Map.Entry
保存在了 Map 集合之中。
Map.Entry
的常用方法如下:
No. | 方法名称 | 描述 |
1 | K getKey() | 得到 key |
2 | V getValue() | 得到 value |
- 代码示例:
public class MapOutDemo01 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("ZS", "张三");
map.put("LS", "李四");
map.put("WW", "王五");
map.put("ZL", "赵六");
map.put("SQ", "孙七");
Set<Map.Entry<String, String>> set = map.entrySet();// 变为Set实例
Iterator<Map.Entry<String, String>> iter = set.iterator();
while (iter.hasNext()) {
Map.Entry<String, String> me = iter.next();
System.out.println(me.getKey() + " --> " + me.getValue());
}
}
}
控制台输出:
Map 集合中每一个元素都是 Map.Entry 的实例,只有通过 Map.Entry 才能进行 key 和 value 的分离操作。
除了以上的做法之外,在 JDK 1.5 之后也可以使用 foreach 完成同样的输出,只是这样的操作基本上不使用。
- 代码示例:
public class MapOutDemo02 {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("ZS", "张三");
map.put("LS", "李四");
map.put("WW", "王五");
map.put("ZL", "赵六");
map.put("SQ", "孙七");
for (Map.Entry<String, String> me : map.entrySet()) {
System.out.println(me.getKey() + " --> " + me.getValue());
}
}
}
控制台输出:
Collections 类
Collections
是一个操作集合的工具类
- 代码示例:返回空的 List 集合
public class CollectionsDemo01 {
public static void main(String[] args) {
List<String> all = Collections.emptyList() ;// 空的集合
all.add("A") ;
}
}
控制台输出:
使用 Collections
类返回的空的集合对象,本身是不支持任何的修改操作的,因为所有的方法都没实现。
- 代码示例:使用
Collections
进行增加元素的操作
public class CollectionsDemo02 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>();
Collections.addAll(all, "A", "B", "C");// 向集合增加元素
System.out.println(all);
}
}
控制台输出:
但是,从实际考虑,使用此类操作并不是很方便,最好的做法就是使用各个接口的直接操作的方法完成。
分析 equals、hashCode 与内存泄露
- equals 的作用:比较两个对象的地址值是否相等
equals()方法在 object 类中定义如下:
public boolean equals(Object obj) {
return (this == obj);
}
但是我们必需清楚,当 String 、Math、还有 Integer、Double。。。。等这些封装类在使用 equals()方法时,已经覆盖了 object 类的 equals()方法,不再是地址的比较而是内容的比较。
我们还应该注意,Java 语言对 equals()的要求如下,这些要求是必须遵循的:
- 对称性:如果 x.equals(y)返回是“true”,那么 y.equals(x)也应该返回是“true”。
- 反射性:x.equals(x)必须返回是“true”。
- 类推性:如果 x.equals(y)返回是“true”,而且 y.equals(z)返回是“true”,那么 z.equals(x)也应该返回是“true”。
- 还有一致性:如果 x.equals(y)返回是“true”,只要 x 和 y 内容一直不变,不管你重复 x.equals(y)多少次,返回都是 “true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和 x 不同类型的对象)永远返回是“false”。
以上这五点是重写 equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。
- hashcode() 方法,在 object 类中定义如下:
public native int hashCode();
说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖 hashcode()方法,比如 String、 Integer、Double。。。。等等这些类都是覆盖了 hashcode()方法的。
java.lnag.Object 中对 hashCode 的约定(很重要):
- 在一个应用程序执行期间,如果一个对象的 equals 方法做比较所用到的信息没有被修改的话,则对该对象调用 hashCode 方法多次,它必须始终如一地返回同一个整数。
- 如果两个对象根据 equals(Object o)方法是相等的,则调用这两个对象中任一对象的 hashCode 方法必须产生相同的整 数结果。
- 如果两个对象根据 equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的 hashCode 方法,不要求产生 不同的整数结果。但如果能不同,则可能提高散列表的性能。
- 在 java 的集合中,判断两个对象是否相等的规则
- 判断两个对象的 hashCode 是否相等
- 如果不相等,认为两个对象也不相等,完毕
- 如果相等,转入 2 (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。)
- 判断两个对象用 equals 运算是否相等
- 如果不相等,认为两个对象也不相等
- 如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
提示贴: 当一个对象被存进
HashSet
集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈 希值与最初存储进HashSet
集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为 的参数去HashSet
集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet
集合中删除当前对象,从而 造成内存泄露。
总结
- 类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
-
List
接口中是允许有重复元素的,Set
接口中是不允许有重复元素。 - 所有的重复元素依靠 hashCode 和 equals 进行区分
- List 接口的常用子类:
ArrayList
、Vector
- Set 接口的常用子类:
HashSet
、TreeSet
-
TreeSet
是可以排序,一个类的对象依靠 Comparable 接口排序 -
Map
接口中允许存放一对内容,key → value -
Map
接口的子类:HashMap
、Hashtable
、TreeMap
-
Map
使用Iterator
输出的详细步骤