Java集合

it2025-01-14  3

Java集合

对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。 在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。 类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在 java.util 包中。

java集合结构

1.Collection接口

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。 此接口定义如下:

public interface Collection<E> extends Iterable<E>

此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。 此接口的常用方法如下所示。 本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。 但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。 之所以有这样的明文规定,也是在 JDK 1.2 之后才有的。一开始在 EJB 中的最早模型中全部都是使用 Collection 操作 的,所以很早之前开发代码都是以 Collection 为准,但是后来为了更加清楚的区分,集合中是否允许有重复元素所以 SUN 在其开源项目 —— PetShop(宠物商店)中就开始推广 List 和 Set 的使用。

1.1List接口

在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。 List 子接口的定义:

public interface List<E> extends Collection<E>

此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:

1.2ArrayList

ArrayList 是 List 接口的子类,此类的定义如下:

public classArrayList<E> extendsAbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。

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 接口才有定义,其他的任何接口都没有任何的定义。

public static void main(String[] args) { //ArrayList :使用的是数组结构,对于增加删除慢,查找快,可以根据下标查找 ArrayList<Integer> data = new ArrayList<>(); //增加两个个数据 data.add(100); data.add(100); System.out.println(data.get(0)+data.get(1)); System.out.println(data.size()); //删除下标是1的元素 data.remove(1); //获取数组的有效长度 System.out.println(data.size()); //修改元素 data.set(1,200); System.out.println(data.get(1)); //返回子集合,例如这个集合里有两个元素,都要输出,则范围是[0,2) System.out.println(data.subList(0,2)); //Vector :使用的是数组结构,对于增加删除慢,查找快,可以根据下标查找 Vector<Integer> data1 = new Vector<>(); //增加两个个数据 data1.add(111); data1.add(222); System.out.println(data1.get(0)); System.out.println(data1.get(1)); System.out.println(data1.subList(0,2)); for (int x = 0; x < data1 .size(); x++) { // size()方法从Collection接口继承而来 System.out.print(data1 .get(x) + "、"); // 此方法是List接口单独定义的 } }

1.3Vector

与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:

public class Vector<E> extendsAbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。

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接口单独定义的 } }

以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。 但是 Vector 属于 Java 元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此 类的使用,只是后来在 JDK 1.2 之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK 1.2 之后将 Vector 类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。

1.4Vector 类和 ArrayList 类的区别

这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别,为了方便大家笔试,列出此内容:

1.5LinkedList

此类的使用几率是非常低的,但是此类的定义如下:

public class LinkedList<E> extendsAbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable

此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法

public static void main(String[] args) { //LinkedList: 使用的是双向链表结构,对于增加删除快,查找慢 LinkedList<Integer> data = new LinkedList<>(); //从首部添加数据 data.addFirst(100); data.addFirst(200); //删除第一个元素并且输出 Integer i = data.removeFirst(); System.out.println(i); //删除第一个元素 data.removeFirst(); //删除最后一个元素 //data.removeLast(); System.out.println(data.get(0)); System.out.println(data.size()); }

2.Set 接口

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。 Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义的 get(int index)方法,所以无法使用循环进行输出。 那么在此接口中有两个常用的子类:HashSet、TreeSet

2.1散列存放:HashSet

既然 Set 接口并没有扩充任何的 Collection 接口中的内容,所以使用的方法全部都是 Collection 接口定义而来的。 HashSet 属于散列的存放类集,里面的内容是无序存放的。

ublic static void main(String[] args) { //HsahSet:散列存放,Set集合里是不允许重复的,而且存的元素是无序存储 HashSet<String> data = new HashSet<>(); data.add("锄禾日当午"); data.add("窗前明月光"); data.add("举头望明月"); data.add("粒粒皆辛苦"); data.add("粒粒皆辛苦"); for (String s : data) { System.out.println(s); } } 举头望明月 窗前明月光 锄禾日当午 粒粒皆辛苦

2.2排序的子类:TreeSet

与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:

public class TreeSet<E> extendsAbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable public static void main(String[] args) { // TreeSet:存储数据是有序的 TreeSet<String> data = new TreeSet<>(); //这里就算打乱了顺序,输出依然是A,B,C,D data.add("B"); data.add("C"); data.add("A"); data.add("D"); //增强型for循环 for (String s : data) { System.out.println(s); } } A B C D

如果现在对一个对象进行排序,那结果是如何的? 定义 Person 类

public class Person { private String name; private int age; 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; } }

定义一个 TreeSet 集合,向里面增加若干个 Person 对象。

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); } }

执行以上的操作代码之后,发现出现了如下的错误提示:

Exception in thread "main" java.lang.ClassCastException: org.lamp.listdemo.treesetdemo02.Person cannot be cast to java.lang.Comparable at java.util.TreeMap.put(Unknown Source) at java.util.TreeSet.add(Unknown Source) at org.lamp.listdemo.treesetdemo02.TreeSetPersonDemo01.main(TreeSetPersonDemo01.java:11)

此时的提示是:Person 类不能向 Comparable 接口转型的问题? 所以,证明,如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口。

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 0; } } 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; } }

那么此时再次使用之前的代码运行程序。程序的执行结果如下:

[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]

从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。

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); } } [姓名:张三,年龄:10, ,姓名:李四,年龄:110, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]

此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。 小结: 关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。 不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。

3.集合输出

已经学习过了基本的集合操作,那么对于集合的输出本身也是有多种形式的,可以使用如下的几种方式: · Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%) 但是在讲解输出的时候一定要记住以下的原则:“ 只要是碰到了集合 ,用 则输出的时候想都不想就使用 Iterator 进行输出 。”

3.1Iterator

Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。 此接口定义如下:

public interface Iterator<E>

要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为 Iterator 接口进行实例化操作。 此接口规定了以下的三个方法: 通过 Collection 接口为其进行实例化之后,一定要记住,Iterator 中的操作指针是在第一条元素之上,当调用 next()方法的时候,获取当前指针指向的值并向下移动,使用 hasNext()可以检查序列中是否还有元素。

public static void main(String[] args) { //Iterator Collection<Integer> data = new ArrayList<Integer>(); //也可写成 ArrayList<Integer> data = new ArrayList<>(); data.add(1); data.add(2); data.add(3); data.add(5); data.add(4); //构建一个迭代器 Iterator<Integer> i = data.iterator(); //hasNext()判断是否还有下一个元素 while(i.hasNext()) { //next()让指针往下移动 Integer a = i.next(); System.out.println(a); } } 1 2 3 5 4

以上的操作是 Iterator 接口使用最多的形式,也是一个标准的输出形式。 但是在使用 Iterator 输出的时候有一点必须注意,在进行迭代输出的时候如果要想删除当前元素,则只能使用 Iterator接口中的 remove()方法,而不能使用集合中的 remove()方法。否则将出现未知的错误。

public static void main(String[] args) { //Iterator Collection<Integer> data = new ArrayList<Integer>(); //也可写成 ArrayList<Integer> data = new ArrayList<>(); data.add(1); data.add(2); data.add(3); data.add(5); data.add(4); Iterator<Integer> i = data.iterator(); while (i.hasNext()) { // 判断是否有下一个元素 Integer b = i.next(); // 取出当前元素 if (b.equals(4)) { data.remove(b); // 错误的,调用了集合中的删除 } else { System.out.print(b + "、"); } } } 1235、Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at Gather.IteratorDemo.main(IteratorDemo.java:29)

此时出现了错误,因为原本的要输出的集合的内容被破坏掉了。 正确的应该是:

public static void main(String[] args) { //Iterator Collection<Integer> data = new ArrayList<Integer>(); //也可写成 ArrayList<Integer> data = new ArrayList<>(); data.add(1); data.add(2); data.add(3); data.add(5); data.add(4); Iterator<Integer> i = data.iterator(); while (i.hasNext()) { // 判断是否有下一个元素 Integer b = i.next(); // 取出当前元素 if (b.equals(4)) { i.remove(); // 调用Iterator中的删除方法 } else { System.out.print(b + "、"); } } } 1235

但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素的操作。 Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须使用其子接口 —— ListIterator。

3.2ListIterator

ListIterator 是可以进行双向输出的迭代接口,此接口定义如下:

public interface ListIterator<E> extends Iterator<E>

此接口是 Iterator 的子接口,此接口中定义了以下的操作方法: 但是如果要想使用 ListIterator 接口,则必须依靠 List 接口进行实例化。

public static void main(String[] args) { //Iterator ArrayList<Integer> data = new ArrayList<>(); data.add(1); data.add(2); data.add(3); data.add(5); data.add(4); //ListIterator ListIterator<Integer> i1 = data.listIterator(); i1.add(100); //指针下移next() i1.next(); i1.next(); i1.set(200); System.out.println(data.size()); //指针上移previous() i1.previous(); i1.previous(); while(i1.hasNext()) { System.out.println(i1.next());//这里之所以没有打印100,是因为一开始指针没有指向0下标,而是0下标之上 } } 6 1 200 3 5 4

3.3新的支持:foreach

foreach 可以用来输出数组的内容,那么也可以输出集合中的内容。

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) ; } } A B C E D

4.Map 接口

以上的 Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了,类似于以下一种情况: · 张三 123456 · 李四 234567 那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key——>value的形式保存,也称为二元偶对象。 此接口定义如下:

public interface Map<K,V>

此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下: Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable

3.1新的子类:HashMap

HashMap 是 Map 的子类,此类的定义如下:

public class HashMap<K,V> extendsAbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。

public static void main(String[] args) { //Map //HashMap/Hashtable/ConcurrentHashMap //TreeMap //LinkedHashMap HashMap<String,String> map = new HashMap<>(); //添加元素 map.put("k1","哈哈哈哈"); map.put("k2","嘿嘿嘿嘿"); //获取value的值,通过key获取 String s = map.get("k1"); System.out.println(s); s = map.get("k2"); System.out.println(s); //通过遍历key数组获取value值 Set<String> st = map.keySet();//这里是获取了全部的key for (String key : st) { System.out.println(key+"-->"+map.get(key)); } //遍历value的值 Collection<String> values = map.values();//这里是获取了全部的value for (String value : values) { System.out.println(value); } } 哈哈哈哈 嘿嘿嘿嘿 k1-->哈哈哈哈 k2-->嘿嘿嘿嘿 哈哈哈哈 嘿嘿嘿嘿

Map 接口在开发中最基本的操作过程,根据指定的 key 找到内容,如果没有找到,则返回 null,找到 了则返回具体的内容。且HashMap 本身是属于无序存放的。

3.2旧的子类:Hashtable

Hashtable 是一个最早的 key——>value 的操作类,本身是在 JDK 1.0 的时候推出的。其基本操作与 HashMap 是类似的。

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); } } two = 2

操作的时候,可以发现与 HashMap 基本上没有什么区别,而且本身都是以 Map 为操作标准的,所以操作的结果形式都一样。但是 Hashtable 中是不能向集合中插入 null 值的。

3.3 HashMap 与 与 Hashtable 的区别

在整个集合中除了 ArrayList 和 Vector 的区别之外,另外一个最重要的区别就是 HashMap 与 Hashtable 的区别。

3.4排序的子类:TreeMap

TreeMap 子类是允许 key 进行排序的操作子类,其本身在操作的时候将按照 key 进行排序,另外,key 中的内容可以为任意的对象,但是要求对象所在的类必须实现 Comparable 接口。

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); } } LS --:> 李四 SQ --:> 孙七 WW --:> 王五 ZL --:> 赵六 ZS --:> 张三

此时的结果已经排序成功了,但是从一般的开发角度来看,在使用 Map 接口的时候并不关心其是否排序,所以此类只需要知道其特点即可。

3.5 关于 Map 集合的输出 在 Collection 接口中,可以使用 iterator()方法为 Iterator 接口实例化,并进行输出操作,但是在 Map 接口中并没有此方法的定义,所以 Map 接口本身是不能直接使用 Iterator 进行输出的。因为 Map 接口中存放的每一个内容都是一对值,而使用 Iterator 接口输出的时候,每次取出的都实际上是一个完整的对象。如果此时非要使用 Iterator 进行输出的话,则可以按照如下的步骤进行:

1、 使用 Map 接口中的 entrySet()方法将 Map 接口的全部内容变为 Set 集合 2、 可以使用 Set 接口中定义的 iterator()方法为 Iterator 接口进行实例化 3、 之后使用 Iterator 接口进行迭代输出,每一次的迭代都可以取得一个 Map.Entry 的实例 4、 通过 Map.Entry 进行 key 和 value 的分离

那么,到底什么是 Map.Entry 呢? Map.Entry 本身是一个接口。此接口是定义在 Map 接口内部的,是 Map 的内部接口。此内部接口使用 static 进行定义,所以此接口将成为外部接口。

实际上来讲,对于每一个存放到 Map 集合中的 key 和 value 都是将其变为了 Map.Entry 并且将 Map.Entry 保存在了Map 集合之中。 在 Map.Entry 接口中以下的方法最为常用: 使用 Iterator 输出 Map 接口

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()); } } WW --> 王五 ZL --> 赵六 LS --> 李四 ZS --> 张三 SQ --> 孙七

以上的代码一定要记住,Map 集合中每一个元素都是 Map.Entry 的实例,只有通过 Map.Entry 才能进行 key 和 value的分离操作。 除了以上的做法之外,在 JDK 1.5 之后也可以使用 foreach 完成同样的输出,只是这样的操作基本上不使用。

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()); } } WW --> 王五 ZL --> 赵六 LS --> 李四 ZS --> 张三 SQ --> 孙七

4. 总结

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 输出的详细步骤

最新回复(0)