对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。 在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。 类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在 java.util 包中。
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 的使用。
在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。 List 子接口的定义:
public interface List<E> extends Collection<E>此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:
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接口单独定义的 } }与 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 接口,这样才将这个类继续保留了下来。
这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别,为了方便大家笔试,列出此内容:
此类的使用几率是非常低的,但是此类的定义如下:
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()); }Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。 Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义的 get(int index)方法,所以无法使用循环进行输出。 那么在此接口中有两个常用的子类:HashSet、TreeSet
既然 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); } } 举头望明月 窗前明月光 锄禾日当午 粒粒皆辛苦与 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 集合中也是不能去掉重复值的。
已经学习过了基本的集合操作,那么对于集合的输出本身也是有多种形式的,可以使用如下的几种方式: · Iterator 迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%) 但是在讲解输出的时候一定要记住以下的原则:“ 只要是碰到了集合 ,用 则输出的时候想都不想就使用 Iterator 进行输出 。”
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 + "、"); } } } 1、2、3、5、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 + "、"); } } } 1、2、3、5、但是,从实际的开发角度看,元素的删除操作出现的几率是很小的,基本上可以忽略,即:集合中很少有删除元素的操作。 Iterator 接口本身可以完成输出的功能,但是此接口只能进行由前向后的单向输出。如果要想进行双向输出,则必须使用其子接口 —— ListIterator。
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 4foreach 可以用来输出数组的内容,那么也可以输出集合中的内容。
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以上的 Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了,类似于以下一种情况: · 张三 123456 · 李四 234567 那么保存以上信息的时候使用 Collection 就不那么方便,所以要使用 Map 接口。里面的所有内容都按照 key——>value的形式保存,也称为二元偶对象。 此接口定义如下:
public interface Map<K,V>此接口与 Collection 接口没有任何的关系,是第二大的集合操作接口。此接口常用方法如下: Map 本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
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 本身是属于无序存放的。
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 值的。
在整个集合中除了 ArrayList 和 Vector 的区别之外,另外一个最重要的区别就是 HashMap 与 Hashtable 的区别。
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 --> 孙七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 输出的详细步骤