Java集合
集合与数组的区别,缓存的管理,Collection 、List、Set 接 口的作用及相关的子类
类集设置的目的
Collection 接口
List 接口
ArrayList
ArrayList 是 List 接口的子类,此类的定义如下:
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable 此类继承了 AbstractList 类。
AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。
范例:增加及取得元素
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo01 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>();
// 实例化List对象,并指定泛型类型 all.add("hello ");
// 增加内容,此方法从Collection接口继承而来 all.add(0, "LAMP ");
// 增加内容,此方法是List接口单独定义的 all.add("world");
// 增加内容,此方法从Collection接口继承而来 System.out.println(all);
// 打印all对象调用toString()方法
}
}
以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是 List 接口单独定义的。随后进行输出的时候, 实际上调用的是 toString()方法完成输出的。
可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。
范例:进一步操作
- 使用 remove()方法删除若干个元素,并且使用循环的方式输出。
- 根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义。
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo02 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型
all.add("hello "); // 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的
all.add("world"); // 增加内容,此方法从Collection接口继承而来
all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的
}
}
}
但是,这里需要注意的是,对于删除元素的操作,后面还会有更加清楚的讲解,此处只是简单的理解一下元素删除 的基本操作即可。具体的原理可以暂时不进行深入掌握。
Vector
与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
package org.listdemo.vectordemo;
import java.util.List;
import java.util.Vector;
public class VectorDemo01 {
public static void main(String[] args) {
List<String> all = new Vector<String>(); // 实例化List对象,并指定泛型类型
all.add("hello "); // 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的
all.add("world"); // 增加内容,此方法从Collection接口继承而来
all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的
}
}
}
链表操作类:LinkedList
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable
此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:
import java.util.LinkedList;
import java.util.Queue;
public class TestDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<String>();
queue.add("A");
queue.add("B");
queue.add("C");
int len=queue.size();//把queue的大小先取出来,否则每循环一次,移除一个元素,就少 一个元素,那么queue.size()在变小,就不能循环queue.size()次了。
for (int x = 0; x <len; x++) {
System.out.println(queue.poll());
}
System.out.println(queue);
}
}
结果输出为:ABC
Set 接口
Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法使用循环进行输出。
那么在此接口中有两个常用的子类:HashSet、TreeSet
散列存放:HashSet
package org.listdemo.hashsetdemo;
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo03 {
public static void main(String[] args) {
Set<String> all = new HashSet<String>(); // 实例化Set接口对象
all.add("A");
all.add("B");
all.add("C");
all.add("D");
all.add("E");
String[] str = all.toArray(new String[] {});// 变为指定的泛型类型数组
for (int x = 0; x < str.length; x++) {
System.out.print(str[x] + "、");
}
}
}
排序的子类:TreeSet
与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
package org.listdemo.treesetdemo01;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01 {
public static void main(String[] args) {
Set<String> all = new TreeSet<String>(); // 实例化Set接口对象
all.add("D");
all.add("X");
all.add("A");
System.out.println(all);
}
}
虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。
排序的说明
package org.listdemo.treesetdemo03;
public class Person implements Comparable<Person> {
private String name;
private int age;
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return this.name.compareTo(per.name);
}
}
public Person() { }
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
package org.listdemo.treesetdemo02;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new TreeSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
Person 类如果假设年龄相等的话,按字符串进行排序。
关于重复元素的说明
如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。 从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:
- 第一种判断两个对象的编码是否一致,这个方法需要通过 hashCode()完成,即:每个对象有唯一的编码
- 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。 所以此时需要覆写 Object 类中的 hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
Person per = (Person) obj;
if (per.name.equals(this.name) && per.age == this.age) {
return true;
} else {
return false;
}
}
public int hashCode() {
return this.name.hashCode() * this.age;
}
小结:
关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。
换句话说要添加到 TreeSet集合中的对象的类型必须实现了 Comparable 接口。 不过 TreeSet 的集合因为借用了 Comparable接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。
集合输出
只要是碰到了集合,则输出的时候想都不想就使用 Iterator 进行输出。
Iterator
Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
public interface Iterator<E>
package org.listdemo.iteratordemo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
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 接口使用最多的形式,也是一个标准的输出形式。 但是在使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator 接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。
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")) {
iter.remove();
// 错误的,调用了集合中的删除
} else {
System.out.print(str + "、");
}
}
ListIterator
ListIterator 是可以进行双向输出的迭代接口,此接口定义如下:
public interface ListIterator<E> extends Iterator<E>
此接口是 Iterator 的子接口,此接口中定义了以下的操作方法:
package org.listdemo.listiteratordemo;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorDemo01 {
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() + "、");
}
}
}
新的支持:foreach
foreach 可以用来输出数组的内容,那么也可以输出集合中的内容。
package org.lamp.listdemo.foreachdemo;
import java.util.ArrayList;
import java.util.Collection;
public class ForeachDemo01 {
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) ;
}
}
}
Map 接口
新的子类:HashMap(重点)
HashMap 是 Map 的子类,此类的定义如下:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
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() + "、");
}
}
}
旧的子类:Hashtable
操作的时候,可以发现与 HashMap 基本上没有什么区别,而且本身都是以 Map 为操作标准的,所以操作的结果形式 都一样。但是 Hashtable 中是不能向集合中插入 null 值的。
排序的子类:TreeMap
TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以 为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。
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.Entry 本身是一个接口。此接口是定义在 Map 接口内部的,是 Map 的内部接口。此内部接口使用 static 进行定义,所以此接口将成为外部接口。
实际上来讲,对于每一个存放到 Map 集合中的 key 和 value 都是将其变为了 Map.Entry并且将 Map.Entry 保存在了 Map 集合之中。
范例:使用 Iterator 输出 Map 接口
package org.listdemo.mapoutdemo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
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());
}
}
}
也可以使用 foreach 完成同样的输出
package org.listdemo.mapoutdemo;
import java.util.HashMap;
import java.util.Map;
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 实际上是一个集合的操作类,此类的定义如下:
public class Collections extends Object
这个类与 Collection 接口没有任何的关系。是一个单独存在的类。
package org.listdemo.collectionsdemo;
import java.util.Collections;
import java.util.List;
public class CollectionsDemo01 {
public static void main(String[] args) {
List<String> all = Collections.emptyList() ;// 空的集合
all.add("A") ;
}
}
使用 Collections 进行增加元素的操作
package org.listdemo.collectionsdemo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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 的作用:比较两个对象的地址值是否相等
- 但是我们必需清楚,当 String 、Math、还有 Integer、Double。等这些封装类在使用 equals()方法时,已经覆盖了 object 类的 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 的集合中,判断两个对象是否相等的规则是:
(1)判断两个对象的 hashCode 是否相等。如果不相等,认为两个对象也不相等,完毕。如果相等,转入 2 (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们 这里将其做为必需的)
(2)判断两个对象用 equals 运算是否相等 如果不相等,认为两个对象也不相等 如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
TIPS:当一个对象被存进 HashSet 集合后,就不能修改这个对象中的那些参与计算的哈希值的字段了,否则,对象被修改后的哈 希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为 的参数去 HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中删除当前对象,从而造成内存泄露
总结
1、 类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
2、 List 接口中是允许有重复元素的,Set 接口中是不允许有重复元素。
3、 所有的重复元素依靠 hashCode()和 equals 进行区分
4、 List 接口的常用子类:ArrayList、Vector
5、 Set 接口的常用子类:HashSet、TreeSet
6、 TreeSet 是可以排序,一个类的对象依靠 Comparable 接口排序
7、 Map 接口中允许存放一对内容,key ,value
8、 Map 接口的子类:HashMap、Hashtable、TreeMap
9、 Map 使用 Iterator 输出的详细步骤