Java SE 21th day: Collections Framework
1、知识点
2、具体内容
2.1 类集框架(重点)
所谓的类集指的是一个动态的对象数组,因为对象数组本身存在了长度的问题,所以在最早的时候是使用了链表程序完成了动态对象数组的开发,但是这种开发是很困难的,尤其是对于那些使用者而言,所以在JDK 1.2之后专门引入了集合框架的概念,为用户提供了大量的数据结构的实现类。
在这些集合框架之中,主要是使用接口定义操作的方法,核心的操作接口:Collection、List、Set、Map、Iterator、Enumeration。
在JDK 1.5之后,为了防止出现安全隐患(ClassCastException,像最早的链表一样,都使用Object保存,那么就一定会有向下转型),所以对于所有的集合操作接口重新进行了定义,增加了泛型的声明。
2.2 单值保存的最大父接口:Collection(重点)
所谓单值保存,指的是每次只向集合之中保存一个对象的信息,这一点与最早编写的链表操作的功能是一样的,java.util.Collection定义了这些操作的最大父类标准,在此接口中一共定义了15个方法,核心方法如下:
No | 方法名称 | 类型 | 描述 |
1 | boolean add(E e) | 普通 | 增加一个对象 |
2 | void clear() | 普通 | 清空数据 |
3 | boolean contains(Object o) | 普通 | 进行比较 |
4 | Iterator<E> iterator() | 普通 | 将所有的集合变为Iterator接口输出 |
5 | boolean isEmpty() | 普通 | 判断集合中是否保存了内容 |
6 | boolean remove(Object o) | 普通 | 从集合中删除一个元素 |
7 | int size() | 普通 | 返回集合中保存数据的个数 |
8 | Object[] toArray() | 普通 | 将集合变为对象数组后返回 |
9 | <T> T[] toArray(T[] a) | 普通 | 将集合变为对象数组后返回 |
在Collection接口中定义的这些常用方法,实际上在之前编写链表程序的时候大部分都实现过了,所以对于集合操作而言,如果之前的图书大厦程序写熟练了,那么集合基本上就会了。
但是,虽然Collection是集合操作的最大父接口,可是真正应用的并不是这个接口,而是这个接口的两个子接口:List、Set。
2.3 允许保存重复的接口:List(重点)
List是Collection接口的子接口,此接口对Collection接口做了大量的扩充,但是这些扩充的方法之中,只有两个还可以稍微算是有用的方法:
方法名称 | 类型 | 描述 | |
1 | E get(int index) | 普通 | 得到指定位置的内容 |
2 | ListIterator<E> listIterator() | 普通 | 为ListInterator接口实例化 |
但是List依然是接口,那么按照面向对象的概念来讲,如果要想实例化接口对象,则一定要采用接口的子类,而在List接口中有两个常用的子类:ArrayList、Vector。
2.3.1 新的子类:ArrayList,90%使用率
ArrayList类是List接口使用最多的一个子类,这个类的定义如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable |
这个类是AbstractList抽象类的子类,而AbstractList类的定义结构如下:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> |
ArrayList重复实现List<E>接口只是为了突出显示,让人知道罢了。
下面就使用这个类进行List接口和Collection接口的操作演示。
范例:增加和输出数据
package framework; import java.util.ArrayList; import java.util.List;
public class CollectionDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); System.out.println("Are the collections NULL? " + all.isEmpty()); all.add("Choi"); all.add("Choi"); //重复数据 all.add("Charlene"); all.add("SoYeon"); System.out.println("Are the collections NULL? " + all.isEmpty()); System.out.println(all); } } |
Are the collections NULL? true Are the collections NULL? false [Choi, Choi, Charlene, SoYeon] |
修改代码,使用循环输出每一个对象:
package framework; import java.util.ArrayList; import java.util.List;
public class CollectionDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); System.out.println("Are the collections NULL? " + all.isEmpty()); all.add("Choi"); all.add("Choi"); //重复数据 all.add("Charlene"); all.add("SoYeon"); System.out.println("Are the collections NULL? " + all.isEmpty()); for (int i = 0; i < all.size(); i++) { System.out.println(all.get(i)); } } } |
Are the collections NULL? true Are the collections NULL? false Choi Choi Charlene SoYeon |
这种集合操作与之前的链表开发是完全一样的。
既然List接口是Collection接口的子类,那么对于ArrayList类而言,也肯定可以为Collection父接口进行对象的实例化操作,而这一操作的核心问题就在于Collection接口中并没有像List接口中定义的get()方法,所以只能全部的数据变为对象之后进行输出。
package framework;
import java.util.ArrayList; import java.util.Collection; import java.util.List;
public class CollectionDemo { public static void main(String[] args) { Collection<String> all = new ArrayList<String>(); System.out.println("Are the collections NULL? " + all.isEmpty()); all.add("Choi"); all.add("Choi"); // 重复数据 all.add("Charlene"); all.add("SoYeon"); System.out.println("Are the collections NULL? " + all.isEmpty()); Object obj[] = all.toArray(); // 变为对象数组 for (int i = 0; i < obj.length; i++) { String str = (String) obj[i];// 向下转型,存在安全隐患 System.out.println(str); } } } |
Are the collections NULL? true Are the collections NULL? false Choi Choi Charlene SoYeon |
为了避免这种安全隐患的操作,所以在JDK 1.5之后又引入了一个新的将数据变为对象数组的操作方法。
package framework;
import java.util.ArrayList; import java.util.Collection; import java.util.List;
public class CollectionDemo { public static void main(String[] args) { Collection<String> all = new ArrayList<String>(); System.out.println("Are the collections NULL? " + all.isEmpty()); all.add("Choi"); all.add("Choi"); // 重复数据 all.add("Charlene"); all.add("SoYeon"); System.out.println("Are the collections NULL? " + all.isEmpty()); String str[] = all.toArray(new String[] {});// 符合语法 for (int i = 0; i < str.length; i++) { System.out.println(str[i]); } } } |
Are the collections NULL? true Are the collections NULL? false Choi Choi Charlene SoYeon |
通过上面的操作可以发现,虽然此时可以成功的完成了问题,也可以成功的避免了安全隐患,可是从操作代码上而言,并不如List接口方便,所以这种操作在开发之中是不会出现的,而集合的输出操作也不是像之前使用的List中的size()和get()方法共同完成的,以后会有其他的标准输出形式。
2.3.2 旧的子类:Vector
ArrayList是新的子类,而Vector是一个旧的子类,是在JDK 1.0的时候就已经存在了,但是观察一下Vector类的定义结构:
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable |
发现Vector的定义结构与ArrayList基本上是一样的,而且所有的操作肯定对于子类用户只是关注构造方法,所有的方法应该以父接口为标准。
package framework;
import java.util.Vector;
public class CollectionDemo { public static void main(String[] args) { Vector<String> all = new Vector<String>(); System.out.println("If the collections are NULL? " + all.isEmpty()); all.add("Choi"); all.add("Choi"); // 重复数据 all.add("Charlene"); all.add("SoYeon"); System.out.println("If the collections are NULL? " + all.isEmpty()); System.out.println(all); } } |
If the collections are NULL? true If the collections are NULL? false [Choi, Choi, Charlene, SoYeon] |
通过代码的编写与运行根本就看不出ArrayList和Vector类的两者的区别,而这两者本身也的确有着很大的区别,但是从代码的编写上看不出。
2.3.3 ArrayList和Vector的区别(面试题)
ArrayList和Vector同时作为List接口的子类,肯定两者有两者的使用特点,那么下面通过一张表格进行说明。
No | 区别点 | ArrayList | Vector |
1 | 推出时间 | JDK1.2时推出,属于新的类 | JDK1.0时推出,属于旧的类 |
2 | 性能 | 采用异步处理方式,性能较高 | 采用同步处理方式,性能相对较低 |
3 | 线程安全性 | 非线程安全的操作 | 线程安全的操作 |
4 | 输出 | Iterator、ListIterator、foreach | Iterator、ListIterator、foreach、Enumeration |
所以从两者的使用上而言,开发之中主要使用ArrayList子类。
2.4 不允许保存重复数据的子接口:Set(重点)
Set接口也是Collection的一个常用子接口,与List子接口不同的是,Set集合之中不允许保存任何重复的内容,而在Set接口中也有两个子接口:HashSet、TreeSet。
Set接口并没有将Collection接口进行扩充,只是与Collection保持同样的功能。
2.4.1 散列存放的子类:HashSet
所谓的散列实际上就是指无序。
package framework;
import java.util.HashSet; import java.util.Set;
public class CollectionDemo { public static void main(String[] args) { Set<String> all = new HashSet<String>(); all.add("Choi"); all.add("Choi"); // 重复数据 all.add("Charlene"); all.add("SoYeon"); System.out.println(all); } } |
[SoYeon, Choi, Charlene] |
现在通过程序运行之后,可以发现,集合之中没有任何重复的数据存在,而且与List接口那样的插入顺序及保存顺序相比,是没有任何排列顺序的。
2.4.2 排序存放的子类:TreeSet
TreeSet肯定是树排序,其原理就非常类似于之前讲解的二叉树程序。
package framework;
import java.util.Set; import java.util.TreeSet;
public class CollectionDemo { public static void main(String[] args) { Set<String> all = new TreeSet<String>(); all.add("Therese"); all.add("Choi"); // 重复数据 all.add("Charlene"); all.add("SoYeon"); all.add("JiYeon"); System.out.println(all); } } |
[Charlene, Choi, JiYeon, SoYeon, Therese] |
TreeSet保存的内容的确是排序了,所以HashSet和TreeSet的区别也比较明确了。
2.4.3 关于排序的说明
既然TreeSet子类可以进行数据的排序,那么就意味着可以对任意的类对象排序,但是如果现在要想保存的是用户自定义的对象,则这个对象所在的类必须满足如下两点:
n 对象所在的类必须实现Comparable接口;
n 在TreeSet子类之中,如果要进行排序的话,则应该将一个类的所有属性进行比较,只有完全一样之后才会认为是同一个对象,而如果只比较了部分属性,那么结果就不准确了。
package framework;
import java.util.Set; import java.util.TreeSet;
class Vehicle implements Comparable<Vehicle> { private String name; private int speed;
public Vehicle(String name, int speed) { this.name = name; this.speed = speed; }
@Override public String toString() { return "交通工具的名字:" + this.name + ",速度:" + this.speed + "\n"; }
@Override public int compareTo(Vehicle o) { if (this.speed > o.speed) { return 1; } else if (this.speed < o.speed) { return -1; //这里不需要设置“this.speed=o.speed,return 0”,因为Set接口不允许重复值 } else { return this.name.compareTo(o.name); //调用String类的compareTo() } } }
public class CollectionDemo { public static void main(String[] args) { Set<Vehicle> all = new TreeSet<Vehicle>(); all.add(new Vehicle("Ferrari", 450)); all.add(new Vehicle("Porsche", 560)); all.add(new Vehicle("Ferrari", 450)); //重复数据 all.add(new Vehicle("Lamborghini", 620)); all.add(new Vehicle("Chevrolet", 620)); all.add(new Vehicle("Benz", 380)); System.out.println(all); } } |
[交通工具的名字:Benz,速度:380 , 交通工具的名字:Ferrari,速度:450 , 交通工具的名字:Porsche,速度:560 , 交通工具的名字:Chevrolet,速度:620 , 交通工具的名字:Lamborghini,速度:620 ] |
只要是多个对象排序,都要使用Comparable接口,与之前的所有操作都完全一样,但是本程序同时也完成了相同对象的过滤,可是这种相同对象的判断,并不是标准的做法。
我们知道,TreeSet和HashSet都属于Set接口,如果TreeSet现在可以实现对象的去重功能了,那么把程序中的TreeSet改为HashSet也应该可以实现对象的去重功能,但是经过试验,这是无法实现的,设置连Set接口中的其他方法(如remove()),HashSet类此时都无法实现。
2.4.4 关于重复元素的说明
首先回顾一下讲解自定义链表的时候,删除和查找数据的时候依靠的是equals()方法,但是对于Set集合而言,如果要想进行重复元素的判断,则必须依靠两种方法:
n 对象比较:public boolean equals(Object obj);
n 对象编码:public int hashCode();
在进行重复元素判断,查找(contains())、删除(remove())等操作之前,会首先利用hashCode()判断对象的编码,之后如果编码相同,再进行各个属性的比较,但是对于这种编码应该是有一个自己的计算公式。
package framework;
import java.util.HashSet; import java.util.Set; import java.util.TreeSet;
class Vehicle { //不需要实现Comparable接口 private String name; private int speed;
public Vehicle(String name, int speed) { this.name = name; this.speed = speed; }
@Override public String toString() { return "交通工具的名字:" + this.name + ",速度:" + this.speed + "\n"; }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + speed; return result; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Vehicle other = (Vehicle) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (speed != other.speed) return false; return true; } }
public class CollectionDemo { public static void main(String[] args) { Set<Vehicle> all = new HashSet<Vehicle>(); all.add(new Vehicle("Ferrari", 450)); all.add(new Vehicle("Porsche", 560)); all.add(new Vehicle("Ferrari", 450)); // 重复数据 all.add(new Vehicle("Lamborghini", 620)); all.add(new Vehicle("Chevrolet", 620)); all.add(new Vehicle("Benz", 380)); all.remove(new Vehicle("Lamborghini", 620)); System.out.println(all); } } |
[交通工具的名字:Porsche,速度:560 , 交通工具的名字:Benz,速度:380 , 交通工具的名字:Ferrari,速度:450 , 交通工具的名字:Chevrolet,速度:620 ] |
这也就是在之前所强调的,一个完整的简单Java类应该覆写Object类中的三个方法:toString()、equals()、hashCode(),在任何的系统之中,如果要查找对象、删除对象必须使用hashCode()和equals()。而上面例子TreeSet比较特殊一些,使用Comparable接口就可以完成了。
面试题:请解释一下Object类及各个主要方法:
n Object是所有类的父类,可以接收所有的应用数据类型;
n Object类的主要方法:
n 取得对象编码:public int hashCode();
n 对象比较:public boolean equals(Object other);
n 对象输出:public String toString();
n 对象克隆:public Object clone() throws CloneNotSupportedException;
n 对象收尾工作:public void finalize() throws Throwable;
n 取得Class对象:public Class<?>getClass();
n 线程等待:public void wait() throws InterruptedException;
n 唤醒一个等待线程:public void notify();
n 唤醒全部等待线程:public void notifyAll();
2.5 集合的输出(重点)
之前的所有操作只是完成了集合数据的增加功能,而如果现在要想进行输出,有四种输出方法:Iterator、ListIterator、Enumeration、foreach。
2.5.1 迭代输出:Iterator(核心中的核心)
Iterator接口是专门为输出准备的一个操作接口,在此接口中定义了两个主要的方法:
n 判断是否还有数据:boolean hasNext();
n 取出数据:E next();
如果要想取得Iterator接口的实例对象必须依靠Collection接口的iterator()方法(Iterator<E> iterator()),既然此方法定义在了Collection接口之中,那么List和Set子接口也肯定都有。
package framework;
import java.util.ArrayList; import java.util.Iterator; import java.util.List;
public class CollectionDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); all.add("Charlene"); all.add("Theresa"); all.add("Cyndi"); Iterator<String> iter = all.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } } } |
Charlene Theresa Cyndi |
不管何种情况下,一定要记住,只要是对于集合的输出操作永远都使用Iterator接口完成。
2.5.2 双向迭代:ListIterator(了解)
Iterator只支持由前向后的输出,如果现在想要完成双向的输出就必须使用Iterator的子接口——ListIterator完成,可是要想取得本接口的实例化对象就不能再利用Collection接口了,在List子接口中存在这样的方法:ListIterator<E> listIterator()。
package framework;
import java.util.ArrayList; import java.util.List; import java.util.ListIterator;
public class CollectionDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); all.add("Charlene"); all.add("Theresa"); all.add("Cyndi"); 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() + "、"); } } } |
由前往后输出Charlene、Theresa、Cyndi、 由后往前输出:Cyndi、Theresa、Charlene、 |
但是在进行由后向前输出操作之前,一定要先进行由前向后的输出,这种输出一般没有参考性,知道就行了。
2.5.3 废弃的输出:Enumeration(重点)
Enumeration最早的时候被人称为枚举输出,实际上也是最早JDK所提供的一个输出接口,但是这个接口已经被废除了,可是有一些古老的操作方法还是要使用Enumeration,这个接口在JDK 1.5之后也增加了泛型,里面有两个方法:
n 判断是否有下一个元素:boolean hasMoreElements();
n 取出数据:E nextElement();
可是如果想要为这个接口实例化也不是Collection、List、Set接口中所规定的方法,而是使用了Vector子类中的一个方法:public Enumeration<E>elements()。
package framework;
import java.util.Enumeration; import java.util.Vector;
public class CollectionDemo { public static void main(String[] args) { Vector<String> all = new Vector<String>(); all.add("Charlene"); all.add("Theresa"); all.add("Cyndi"); Enumeration<String> enu = all.elements(); while (enu.hasMoreElements()) { System.out.print(enu.nextElement() + "、"); } } } |
从功能上讲,Enumeration的输出和Iterator使用形式很相似,但是却不如Iterator使用广泛,Enumeration只在必须使用的地方才使用,而集合输出的标准操作还是使用Iterator接口。
2.5.4 JDK 1.5的新支持——foreach(了解)
在之前讲解JDK 1.5新特性的时候已经讲解过了foreach的操作,实际上就是一个增强的for循环,这种循环除了可以进行数组的输出之外,也可以进行集合的输出操作。
package framework;
import java.util.ArrayList; import java.util.List;
public class CollectionDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); all.add("Charlene"); all.add("Theresa"); all.add("Cyndi"); for (String str : all) { System.out.print(str + "、"); } } } |
Charlene、Theresa、Cyndi、 |
这种语法虽然简洁,但是别使,标准的开发就是Iterator。
2.6 保存二元偶对象:Map(重点)
所谓的二元偶对象就是指的是同时保存一对对象,例如:结婚登记肯定不能少于两个人,因为要一起注册,一起保存信息,所以所谓的二元偶对象指的就是两个相关的对象,而这两个相关对象也存在着:key = value的形式。
例如,如果说现在要想保存一个电话本的信息:
n 张三(key),123456(value);
n 李四(key),789012(value);
如果现在要查找张三的电话,肯定是先找到张三对应的key,之后再找到对应的电话:123456(value),而这种功能就使用Map接口完成,本接口所定义的方法如下:
No | 方法名称 | 类型 | 描述 |
1 | V put(K key, V value) | 普通 | 向集合中保存对象 |
2 | V get(Object key) | 普通 | 根据指定的key从集合中取得对应的value |
3 | Set<Map.Entry<K,V>> entrySet() | 普通 | 将所有的集合变为Set集合 |
4 | Set<K> keySet() | 普通 | 取得所有的KEY,按照Set集合返回 |
5 | int size() | 普通 | 元素个数 |
而在开发之中使用最多的两个Map接口的子类就是:HashMap、Hashtable。
2.6.1 新的子类:HashMap
HashMap是Map接口中使用最多的一个子类,以HashMap为例说明Map接口的基本功能。
package framework;
import java.util.HashMap; import java.util.Map;
public class CollectionDemo { public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "Charlene"); map.put(null, null);// 设置null map.put(1, "Yumiko");// key重复,用新的内容覆盖旧的内容 map.put(2, "Selina"); map.put(3, "Hebe"); System.out.println(map); System.out.println(map.get(1)); System.out.println(map.get(10));// 不存在 } } |
{null=null, 1=Yumiko, 2=Selina, 3=Hebe} Yumiko null |
可以发现HashMap的操作过程与之前的List集合的功能很相似,唯一不同的是现在保存的是一对数据。
范例:取得全部的key
package framework;
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;
public class CollectionDemo { public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "Charlene"); map.put(null, null);// 设置null map.put(1, "Yumiko");// key重复,用新的内容覆盖旧的内容 map.put(2, "Selina"); map.put(3, "Hebe"); Set<Integer> set = map.keySet(); // 取得全部的key Iterator<Integer> iter = set.iterator(); while (iter.hasNext()) { Integer key = iter.next(); System.out.println(key + "→" + map.get(key)); } } } |
null→null 1→Yumiko 2→Selina 3→Hebe |
一定要注意,以上虽然输出了Map中的全部数据,但是标准的Map输出操作并不会使用此类方法。
2.6.2 旧的子类:Hashtable
在JDK 1.0的时候实际上也推出过一个与Map接口类似功能的类——Hashtable,此类现在在JDK 1.2之后也变为了Map接口的子类,所以从使用上而言与HashMap没太大区别。
package framework;
import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set;
public class CollectionDemo { public static void main(String[] args) { Map<Integer, String> map = new Hashtable<Integer, String>(); map.put(1, "Charlene"); map.put(1, "Yumiko");// key重复,用新的内容覆盖旧的内容 map.put(2, "Selina"); map.put(3, "Hebe"); Set<Integer> set = map.keySet(); // 取得全部的key Iterator<Integer> iter = set.iterator(); while (iter.hasNext()) { Integer key = iter.next(); System.out.println(key + "→" + map.get(key)); } } } |
3→Hebe 2→Selina 1→Yumiko |
与HashMap的不同之处从表面上讲只是不能保存null内容。
2.6.3 HashMap和Hashtable的区别(面试题)
HashMap和Hashtable同时作为Map接口的子类,肯定两者有两者的使用特点,那么下面通过一张表格进行说明。
No | 区别点 | HashMap | Hashtable |
1 | 推出时间 | JDK1.2时推出,属于新的类 | JDK1.0时推出,属于旧的类 |
2 | 性能 | 采用异步处理方式,性能较高 | 采用同步处理方式,性能相对较低 |
3 | 线程安全性 | 非线程安全的操作 | 线程安全的操作 |
4 | 设置null | 允许设置 | 不允许设置,否则出现NullPointerException |
所以从两者的使用上而言,开发之中主要使用HashMap子类。
2.6.4 使用Iterator输出Map集合(核心)
对于集合而言,所有的操作标准做法是使用Iterator接口输出,但是Map集合不像Collection集合那样,直接提供了iterator()方法,所以如果想使用Iterator接口输出Map集合数据的话,则首先必须明白Collection和Map保存数据的特点。
Map.Entry接口的定义如下:
public static interface Map.Entry<K,V> |
既然接口的定义上使用了“static”关键字,那么这个Map.Entry接口就属于Map的一个内部接口,而且这个内部接口使用了static定义,表示是一个外部接口,而在这个接口中有两个主要方法:
n 取得包装的key:K getKey();
n 取得包装的value:V getValue();
于是,下面就可以按照如下的固定步骤使用Iterator完成Map接口数据的输出:
1. 使用Map接口中的entrySet()方法将全部数据变为Set集合;
2. 使用Set接口中的iterator()方法取得Iterator接口的实例化对象,并进行迭代输出;
3. 通过Iteratro接口的next()方法可以取得一个Map.Entry对象,以进行key和value的分离。
范例:使用Iterator输出Map集合。
package framework;
import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set;
public class CollectionDemo { public static void main(String[] args) { Map<Integer, String> map = new Hashtable<Integer, String>(); map.put(1, "Charlene"); map.put(2, "Yumiko"); map.put(3, "Selina"); map.put(4, "Hebe"); Set<Map.Entry<Integer, String>> set = map.entrySet(); Iterator<Map.Entry<Integer, String>> iter = set.iterator(); while (iter.hasNext()) { Map.Entry<Integer, String> me = iter.next(); System.out.println(me.getKey() + " → " + me.getValue()); } } } |
4 → Hebe 3 → Selina 2 → Yumiko 1 → Charlene |
本程序作为以后开发的绝对核心程序,一定要充分的理解。
2.6.5 关于Map中key的说明
通过Map接口的定义可以发现,在Map集合之中,key和value的类型都可以通过外部指定,所以用户也可以使用一个自己定义的类完成,但是这个类必须满足以下要求:
很明显通过Map的get()方法是一个查找过程,既然是查找肯定要存在判断和比较,所以必须覆写hashCode()方法和equals()方法。
范例:不满足以上条件的开发
package framework;
import java.util.HashMap; import java.util.Map;
class Vehicle { private String name;
public Vehicle(String name) { this.name = name; }
@Override public String toString() { return "车的名字:" + this.name; } }
public class CollectionDemo { public static void main(String[] args) { Map<String, Vehicle> map = new HashMap<String, Vehicle>(); map.put(new String("PS"), new Vehicle("Porsche")); System.out.println(map.get(new String("PS"))); } } |
车的名字:Porsche |
现在功能是完成了,但如果反过来呢?即,通过Vehicle类来查找String类数据。修改后的main类:
public class CollectionDemo { public static void main(String[] args) { Map<Vehicle, String> map = new HashMap<Vehicle, String>(); map.put(new Vehicle("Porsche"), new String("PS")); System.out.println(map.get(new Vehicle("Porsche"))); } } |
null |
可以发现,此时功能并不能在意想中实现,那按照条件更新程序,即覆写hashCode()和equals()方法,其他不变:
package framework;
import java.util.HashMap; import java.util.Map;
class Vehicle { private String name;
public Vehicle(String name) { this.name = name; }
@Override public String toString() { return "车的名字:" + this.name; }
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; }
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Vehicle other = (Vehicle) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } }
public class CollectionDemo { public static void main(String[] args) { Map<Vehicle, String> map = new HashMap<Vehicle, String>(); map.put(new Vehicle("Porsche"), new String("PS")); System.out.println(map.get(new Vehicle("Porsche"))); } } |
PS |
以后在进行代码开发的时候,所有的key的类型全部都设置为String是最合适的。
另外需要提醒的是:Collection接口的数据主要是作为保存和全部输出操作的,而Map接口的作用除了保存之外,主要的功能是进行数据的查找。
2.7 集合工具类:Collections(了解)
在java.util包中存在一个Collections类,但是此类定义上与Collection接口没有任何关系,但是这个类却提供了许多与集合有关的操作。
package framework;
import java.util.ArrayList; import java.util.Collections; import java.util.List;
public class CollectionsDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); Collections.addAll(all, "A", "B", "C", "D", "E", "F"); Collections.reverse(all);// 反转 System.out.println(all); } } |
[F, E, D, C, B, A] |
面试题:请解释Collection和Collections的关系?
两者之间在定义上没有直接的关系,Collection是单值集合的操作最大父接口,而Collections只是一个集合的工具操作类。
2.8 栈操作(理解)
栈是一种先进后出的数据结构实现,例如:网页上有后退按钮,编辑器上也有撤销操作,而在Java中使用Stack类进行栈的操作,此类是Vector的子类,这个类主要使用两个方法:
n 入栈操作:public E push(E item)
n 出栈操作:public E pop()
范例:实现栈操作
package framework;
import java.util.ArrayList; import java.util.Collections; import java.util.List;
public class CollectionsDemo { public static void main(String[] args) { List<String> all = new ArrayList<String>(); Collections.addAll(all, "A", "B", "C", "D", "E", "F"); Collections.reverse(all);// 反转 System.out.println(all); } }有问题!!! |
D C B A Thread [main] (Suspended (exception EmptyStackException)) Stack<E>.peek() line: 85 Stack<E>.pop() line: 67 CollectionDemo.main(String[]) line: 16 |
栈这种功能类一般使用不到,这个的使用还是看项目要求。
2.9 属性操作(重点)
在Windows中存在属性文件,例如Windows的启动配置文件是boot.ini文件,这种属性的信息就可以通过Properties类完成,这个类的定义如下:
public class Properties extends Hashtable<Object,Object> |
此类是Hashtable的子类,可是这个时候的泛型没用,所以都将其设置为Object,因为所有的属性内容的类型都是String型的数据,而在这个类中有如下几个主要方法;
No | 方法名称 | 类型 | 描述 |
1 | public String getProperty(String key) | 普通 | 根据指定的key取得返回属性,如果没有返回null |
2 | public String getProperty(String key, String defaultValue) | 普通 | 根据指定的key取得返回属性,如果没有返回默认值 |
3 | public Object setProperty(String key, String value) | 普通 | 设置属性 |
4 | public void store(OutputStream out, String comments) throws IOException | 普通 | 使用输出流输出属性内容 |
5 | public void load(InputStream inStream) throws IOException | 普通 | 使用输入流读取属性内容 |
范例:属性的设置和取得
package framework;
import java.util.Properties;
public class PropertyDemo { public static void main(String[] args) { Properties pro = new Properties(); pro.setProperty("CC", "Charlene_Choi"); pro.setProperty("TF", "Theresa_Fu"); pro.setProperty("SY", "So_Yeon"); pro.setProperty("CW", "Cyndi_Wang"); System.out.println(pro.getProperty("TF")); System.out.println(pro.getProperty("CC")); System.out.println(pro.getProperty("CA")); System.out.println(pro.getProperty("CB","Default:Not Found!")); } } |
Theresa_Fu Charlene_Choi null Default:Not Found! |
另外,所有的属性可以输出到属性文件之中,所有的属性文件指的就是后缀是“*.properties”的文件。
package framework;
import java.io.File; import java.io.FileOutputStream; import java.util.Properties;
public class PropertyDemo { public static void main(String[] args) throws Exception { Properties pro = new Properties(); pro.setProperty("CC", "Charlene_Choi"); pro.setProperty("TF", "Theresa_Fu"); pro.setProperty("SY", "So_Yeon"); pro.setProperty("CW", "Cyndi_Wang"); pro.setProperty("JY", "朴智妍"); // 中文 File file = new File("d:" + File.separator + "stars.properties"); pro.store(new FileOutputStream(file), "Stars Info"); /* * 当然,我们也可以使用打印流输出,等价: * PrintStream ps = new PrintStream(file); * pro.store(ps, "StarsInfo"); */ } } |
输出结果:
可以看到,对于中文的存储是使用UNICODE编码存储的。
范例:通过制定的输入流读取文件内容
package framework;
import java.io.File; import java.io.FileInputStream; import java.util.Properties;
public class PropertyDemo { public static void main(String[] args) throws Exception { Properties pro = new Properties(); File file = new File("d:" + File.separator + "stars.properties"); pro.load(new FileInputStream(file)); System.out.println(pro.getProperty("CC")); System.out.println(pro.getProperty("JY")); System.out.println(pro.getProperty("ABC"));
} } |
Charlene_Choi 朴智妍 null |
当然,现在用户也可以直接操作属性文件以增加新的内容,但是如果有中文,则必须转码,使用JDK所提供的“native2ascii.exe”操作命令完成。如下:
属性文件以后在开发之中,肯定是要经常使用到的,如果要保存的是中文,则必须转码,等学到Struts的时候就能明白此问题发生所在了。
3、总结
1、一定要记清楚各个操作接口的特点及常用子类;
2、Iterator永远是作为集合的标准输出;
3、Object类中的equal()、toString()的核心操作。
4、Java SE核心知识点
在Java SE之中所讲解的全部内容目的:不是做Java SE开发的,因为这种开发是需要进行图形界面编写的,但是图形界面在Java中基本不用了,所以Java
1. 图书大厦:作为接口程序的总结点;
2. 异常处理的标准格式:try…catch…finally、throws、throw、异常的处理流程;
3. JDK 1.5之后的一些新技术,能看懂就行了;
4. 设计模式:Singleton、Factory、Proxy;
5. 多线程的两种实现方式以及区别:→ Android
6. String、基本数据类型(包装类)、Date之间(SimpleDateFormat)互相转换;
7. 正则表达式,数据验证肯定使用正则:正则符号、String类的支持;
8. 比较器的操作(Comparable)只是作为Java SE讲解使用的,如果以后不会用TreeSet比较器则没用;
9. 如果现在传入了完整的“包.类”、属性或方法名称的时候一定要记住是反射操作;
10. 文件拷贝程序的实现,必须灵活编写; →Struts服务
11. PrintStream、Scanner、对象序列化;
12. JDBC中PrepareStatement、Connection、DriverManager、ResultSet的使用、数据的CRUD、统计、分页(ROWNUM);
13. 集合的基本操作,现在主要以:List、Map、Iterator为主,必须清楚各个接口和各个子类的继承关系、主要的使用特点;
14. 程序和现实生活的关系,引用表示;
15. 简单Java类的开发原则以及与表的关系。
网页制作:HTML显示操作、表格的操作、表单的编写,不用开发工具,自己手工写。
WEB:HTML + JavaScript + JSP + Servlet + Tomcat +EL + JSTL + Java SE核心