07 - JavaSE之容器
本章宗旨:1136 -- 1个图 1个类 3个知识点 6个接口 容器 J2SDK 所提供的容器 API 位于 java.util 包内。 容器 API 的类图如下: Collection 接口的子接口分为:Set接口(包含 HashSet类) + List接口(包含LinkedList 类和 ArrayLis t类) Map接口:包含HashMap类 Set接口和List接口定义了不同的数据存储方式: Set 中的数据对象没有顺序并且不可以重复。 List 中的数据对象有顺序并且可以重复。 Map接口定义了存储“键(key)——值(value)映射”的方法。 Collection接口 Collection接口中所定义的方法: int size() boolean isEmpty() void clear() boolean contains(Object element) boolean add(Object element) boolean remove(Object element) Iterator iterator() boolean containsALL(Collection c) boolean addALL(Collection c) boolean removeALL(Collection c) boolean retainALL(Collection c) Object[] toArray() 举例1: import java.util.*; public class Test { public static void main(String[] args) { Collection c = new ArrayList(); // 可以放入不同类型的对象 c.add("Hello"); c.add(new String("World")); c.add(new Integer(100)); System.out.println(c.size()); System.out.println(c); } } // 3 // [Hello, World, 100] 举例2: import java.util.*; public class Test { public static void main(String[] args) { Collection c = new HashSet(); c.add("Hello"); c.add(new String("World")); c.add(new Integer(100)); // 可以删除因为HashSet重写了equals方法。 c.remove("Hello"); c.remove(new Integer(100)); System.out.println(c.size()); System.out.println(c); } } // 1 // [100] 容器类对象在调用 remove,contains 等方法时需要比较对象是否相等,这会涉及到对象类型的 equals 方法和 hashcode 方法;对于自定义的类型,需要重写 equals 和 hashCode 方法以实现自定义的对象相等规则。 相等的对象应该具有相同的 hash codes,但是 hashCode 只在这个对象作为索引、键值的时候使用此方法,其他时候虽然不用,但是还是要重写此方法,至于重写此方法的具体实现可以随便写(比如我直接返回0也是可以的)。 Iterator 接口 所有实现了 Collection 接口的容器类都有一个 iterator 方法,这个方法返回一个实现了 Iterator 接口的对象。 这个 Iterator 对象称为迭代器,用以方便的实现对容器内的元素的遍历操作。 Iterator 接口定义了如下方法: boolean hasNext(); // 判断游标右边是否有元素 Object next(); // 返回游标右边的元素并移动游标到下一个位置 void remove(); // 删除游标左边的元素,在执行完next之后,该操作只能执行一次 举例1: import java.util.*; public class Test { public static void main(String[] args) { Collection c = new ArrayList(); c.add("Hello"); c.add(new String("World")); c.add(new Integer(100)); Iterator i = c.iterator(); while(i.hasNext()) { System.out.println(i.next()); } } } Iterator 对象的 remove 方法是在迭代过程中删除元素的唯一的安全方法。(不能使用 Collection 对象的 remove 方法,因为管家Iterator 对象在迭代过程中会将元素上锁,外人无法访问。) import java.util.*; public class Test { public static void main(String[] args) { Collection c = new ArrayList(); c.add("Hello"); c.add(new String("World")); c.add(new Integer(100)); Iterator i = c.iterator(); for(Iterator i = c.iterator(); i.hasNext(); ) { if(i.next().equals(100)) { i.remove(); } } System.out.println(c); // [Hello, World] } } 补充:JDK1.5 增强的 for 循环 增强的 for 循环对于遍历 array 或者 Collection 的时候相当简便。 import java.util.*; public class EnhancedFor { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; for(int i : arr) { System.out.println(i); } Collection c = new ArrayList(); c.add(new String("aaa")); c.add(new String("bbb")); c.add(new String("ccc")); for(Object o : c) { System.out.println(o); } } } 增强型 for 循环的缺陷:(除了简单遍历外,不建议使用增强 for 循环) 对于数组:不能方便的访问下标值。 对于 Iterator ,不能方便的删除集合中特定的元素。 Set 接口 Set接口是 Collection 的子接口,Set 接口没有提供额外的方法,实现Set接口的容器类中的元素是没有顺序的,而且不可以重复。 Set容器可以与数学中的“集合”概念相对应。 JDK API中所提供的Set容器类有HashSet,TreeSet等。 举例1: public static void main(String[] args) { Set s = new HashSet(); s.add("hello"); s.add("world"); s.add("world"); // 相同的元素不会被加入,类似数学的集合概念 System.out.println(s); // [hello, world] } 举例2: public static void main(String[] args) { Set s1 = new HashSet(); Set s2 = new HashSet(); s1.add("a");s1.add("b");s1.add("c"); s2.add("d");s2.add("a");s2.add("b"); Set sn = new HashSet(s1); Set su = new HashSet(s1); sn.retainAll(s2); su.addAll(s2); System.out.println(sn); // [a, b] System.out.println(su); // [d, a, c, b] } List 接口 List接口是 Collection的 子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复。 List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。 JAVA SDK提供的List容器类有 ArrayList,LinkedList等。 Object get(int index) // 返回index位置的元素 // 返回值为Object,表示set替换掉的元素,返回出来 Object set(int index, Object element) void add(int index, Object element) Object remove(Object o) int indexOf(Object o) int lastIndexOf(Object o) 举例: List l1 = new LinkedList(); for(int i=0; i<6; i++) { li.add("a" + i); } // [a0, a1, a2, a3, a4, a5, a6] System.out.print((String)l1.get(2) + " "); // a2 System.out.println(li.indexOf("a3")); // 3 li.remove(1); System.out.println(l1); // [a0, a2, a3, a4, a5, a6] List 常用算法(Collections类) Collections类提供了一些静态方法实现了基于List容器的常用算法。 void sort(List) // 对List容器内的元素排序 void shuffle(List) // 对List容器内的元素进行随机排列 void reverse(List) // 对List容器内的元素进行逆序排列 void fill(List, Object) // 用特定的List元素列表填充整个List容器 void copy(List dest, List src) void binarySearch(List, Object) // 对于顺序的List容器,采用二分法查找指定对象 Comparable 接口 问题:上面的算法根据什么确定容器中对象的“大小”顺序? 其实,所有可以排序的类都实现了 java.lang.Comparable 接口,Comparable河口中只有一个方法: public int compareTo(Object obj); 该方法: 返回0表示: this == obj 返回正数表示:this > obj 返回负数表示:this < obj 实现 Comparable接口的类通过实现 compareTo 方法从而确定该类对象的排序方法。 PS:我们可以看到 compareTo 方法的参数类型是 Object 类型的,意味着如果我只想比较两只猫的话,我扔一只狗也是可以的,这就没意义了,所以在之后的 JDK 里面,将 Object 类型统一改为 泛型,这样在传入的时候就统一了比较的对象。 改写 Name 类,让其实现 Comparable 接口: class Name implements comparable { //... public int compareTo(Object o) { Name n = new (Name)o; int lastCmp = lastName.compareTo(n.lastName); // 先比较姓氏大小 return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName)); // 再比较名字大小 } } 改写了 compareTo 方法,相当于改写了Collections类的排序算法,至于如何排序,由你决定! 如何选择数据结构? 衡量标准:读的效率和写的效率(如字符串逆序操作) Array 读快写慢(写的慢,需要另开辟数组) Linked 写快读慢(写的快,还需要改变指针指向) Hash 介于两者之间 Map接口 实现Map接口的类用来存储 “键值对” Map接口的实现类有 HashMap 和 TreeMap Map类中存储的键值对通过键来标识,所以键的值不能重复 Object put(Object key, Object value) // 返回值为 Object,是替换掉的 value Object get(Object key) Object remove(Object key) boolean containsKey(Object key) boolean containsValue(Object value) int size() boolean isEmpty() void putAll(Map m) void clear() 举例1: import java.util.*; public class Test { public static void main(String[] args) { Map m1 = new HashMap(); Map m2 = new HashMap(); m1.put("one", new Integer(1)); m1.put("two", new Integer(2)); m1.put("three", new Integer(3)); m2.put("A", new Integer(1)); m2.put("B", new Integer(2)); System.out.println(m1.size()); System.out.println(m1.containsKey("one")); System.out.println(m2.containsValue(new Integer(2))); if(m1.containsKey("two")) { int i = ((Integer) m1.get("two")).intValue(); System.out.println(i); } Map m3 = new HashMap(m1); m3.putAll(m2); System.out.println(m3); } } 举例2: import java.util.*; public class Test { private static final Integer ONE = new Integer(1); public static void main(String[] args) { Map m = new HashMap(); for(int i=0; i<args.length; i++) { Integer freq = (Integer) m.get(args[i]); m.put(args[i], freq == null ? ONE : new Integer(freq.intValue() + 1)); } System.out.println(m); } } // java Test 77 88 99 99 99 // {77=1, 88=1, 99=3} Auto-boxing/unboxing 在合适的时机自动打包/解包 自动将基础类型转换成对象 自动将对象转换成基础类型 import java.util.*; public class Test { private static final int ONE = 1; public static void main(String[] args) { Map m = new HashMap(); for(int i=0; i<args.length; i++) { int freq = ((Integer) m.get(args[i]) == null ? 0 : (Integer) m.get(args[i])); m.put(args[i], freq == 0 ? ONE : freq + 1); } System.out.println(m); } } 泛型 起因 JDK1.4之前类型不明确,装入集合的类型都被当做 Object 对待,从而失去了其本身的实际类型。 从集合中取出元素时需要转型,效率低,容易产生错误。 解决办法 在定义集合的时候定义集合中对象的类型 import java.util.*; public class Test { public static void main(String[] args) { Collection<String> c = new HashSet<String>(); c.add("aaa"); c.add("bbb"); c.add("ccc"); for(Iterator<String> i = c.iterator(); i.hasNext(); ) { System.out.println(i.next()); } } } // aaa ccc bbb 总结 1136:一个图,一个类,三个知识点,六个接口