代码:
Collection c = new ArrayList();
c.add("hello");
c.add("world");
c.add("java");
System.out.println(c);
为什么c输出的不是地址值呢?
A:Collection c = new ArrayList();
这是多态,所以输出c的toString()方法,其实是输出ArrayList的toString()
B:看ArrayList的toString()
而我们在ArrayList里面却没有发现toString()。
以后遇到这种情况,也不要担心,你认为有,它却没有,就应该去它父亲里面看看。
C:toString()的方法源码
1 public String toString() { 2 Iterator<E> it = iterator(); //集合本身调用迭代器方法,得到集合迭代器 3 if (! it.hasNext()) 4 return "[]"; 5 6 StringBuilder sb = new StringBuilder(); 7 sb.append('['); 8 for (;;) { 9 E e = it.next(); //e=hello,world,java 10 sb.append(e == this ? "(this Collection)" : e); 11 if (! it.hasNext()) 12 //[hello, world, java] 13 return sb.append(']').toString(); 14 sb.append(',').append(' '); 15 } 16 }
需求:用户登录注册案例。
假如用户类的内容比较对,将来维护起来就比较麻烦,为了更清晰的分类,我们就把用户又划分成了两类
用户基本描述类
成员变量:用户名,密码
构造方法:无参构造
成员方法:getXxx()/setXxx()
用户操作类
登录,注册
分包:
A:功能划分
B:模块划分
C:先按模块划分,再按功能划分
今天我们选择按照功能划分:
用户基本描述类包 cn.itcast.pojo
用户操作接口 cn.itcast.dao
用户操作类包 cn.itcast.dao.impl
今天是集合实现,过几天是IO实现,再过几天是GUI实现,就业班我们就是数据库实现
用户测试类 cn.itcast.test
// 为了让多个方法能够使用同一个集合,就把集合定义为成员变量
// 为了不让外人看到,用private
// 为了让多个对象共享同一个成员变量,用static
private static ArrayList<User> array = new ArrayList<User>();
* Collection
* |--List
* 有序(存储顺序和取出顺序一致),可重复
* |--Set
* 无序(存储顺序和取出顺序不一致),唯一
*
* HashSet:它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。
* 注意:虽然Set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
* 而你的顺序恰好和它的存储顺序一致,这代表不了有序,你可以多存储一些数据,就能看到效果。
1 // 创建集合对象 2 Set<String> set = new HashSet<String>(); 3 4 // 创建并添加元素 5 set.add("hello"); 6 set.add("java"); 7 set.add("world"); 8 set.add("java"); 9 set.add("world"); 10 11 // 增强for 12 for (String s : set) { 13 System.out.println(s); 14 }
* HashSet:存储字符串并遍历
* 问题:为什么存储字符串的时候,字符串内容相同的只存储了一个呢?
* 通过查看add方法的源码,我们知道这个方法底层依赖 两个方法:hashCode()和equals()。
* 步骤:
* 首先比较哈希值
* 如果相同,继续走,比较地址值或者走equals()
* 如果不同,就直接添加到集合中
* 按照方法的步骤来说:
* 先看hashCode()值是否相同
* 相同:继续走equals()方法
* 返回true: 说明元素重复,就不添加
* 返回false:说明元素不重复,就添加到集合
* 不同:就直接把元素添加到集合
* 如果类没有重写这两个方法,默认使用的Object()。一般来说不同相同。
* 而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉。只留下一个。
* 需求:存储自定义对象,并保证元素的唯一性
* 要求:如果两个对象的成员变量值都相同,则为同一个元素。
*
* 目前是不符合我的要求的:因为我们知道HashSet底层依赖的是hashCode()和equals()方法。
* 而这两个方法我们在学生类中没有重写,所以,默认使用的是Object类。
* 这个时候,他们的哈希值是不会一样的,根本就不会继续判断,执行了添加操作。
HashSet集合的add()方法的源码:
1 interface Collection { 2 ... 3 } 4 5 interface Set extends Collection { 6 ... 7 } 8 9 class HashSet implements Set { 10 private static final Object PRESENT = new Object(); 11 private transient HashMap<E,Object> map; 12 13 public HashSet() { 14 map = new HashMap<>(); 15 } 16 17 public boolean add(E e) { //e=hello,world 18 return map.put(e, PRESENT)==null; 19 } 20 } 21 22 class HashMap implements Map { 23 public V put(K key, V value) { //key=e=hello,world 24 25 //看哈希表是否为空,如果空,就开辟空间 26 if (table == EMPTY_TABLE) { 27 inflateTable(threshold); 28 } 29 30 //判断对象是否为null 31 if (key == null) 32 return putForNullKey(value); 33 34 int hash = hash(key); //和对象的hashCode()方法相关 35 36 //在哈希表中查找hash值 37 int i = indexFor(hash, table.length); 38 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 39 //这次的e其实是第一次的world 40 Object k; 41 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 42 V oldValue = e.value; 43 e.value = value; 44 e.recordAccess(this); 45 return oldValue; 46 //走这里其实是没有添加元素 47 } 48 } 49 50 modCount++; 51 addEntry(hash, key, value, i); //把元素添加 52 return null; 53 } 54 55 transient int hashSeed = 0; 56 57 final int hash(Object k) { //k=key=e=hello, 58 int h = hashSeed; 59 if (0 != h && k instanceof String) { 60 return sun.misc.Hashing.stringHash32((String) k); 61 } 62 63 h ^= k.hashCode(); //这里调用的是对象的hashCode()方法 64 65 // This function ensures that hashCodes that differ only by 66 // constant multiples at each bit position have a bounded 67 // number of collisions (approximately 8 at default load factor). 68 h ^= (h >>> 20) ^ (h >>> 12); 69 return h ^ (h >>> 7) ^ (h >>> 4); 70 } 71 } 72 73 74 hs.add("hello"); 75 hs.add("world"); 76 hs.add("java"); 77 hs.add("world");
* LinkedHashSet:底层数据结构由哈希表和链表组成。
* 哈希表保证元素的唯一性。
* 链表保证元素有素。(存储和取出是一致)
1 // 创建集合对象 2 LinkedHashSet<String> hs = new LinkedHashSet<String>(); 3 4 // 创建并添加元素 5 hs.add("hello"); 6 hs.add("world"); 7 hs.add("java"); 8 hs.add("world"); 9 hs.add("java"); 10 11 // 遍历 12 for (String s : hs) { 13 System.out.println(s); 14 }
* TreeSet:能够对元素按照某种规则进行排序。
* 排序有两种方式
* A:自然排序
* B:比较器排序
*
* TreeSet集合的特点:排序和唯一
*
* 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。
TreeSet的add()方法的源码解析:
1 interface Collection {...} 2 3 interface Set extends Collection {...} 4 5 interface NavigableMap { 6 7 } 8 9 class TreeMap implements NavigableMap { 10 public V put(K key, V value) { 11 Entry<K,V> t = root; 12 if (t == null) { 13 compare(key, key); // type (and possibly null) check 14 15 root = new Entry<>(key, value, null); 16 size = 1; 17 modCount++; 18 return null; 19 } 20 int cmp; 21 Entry<K,V> parent; 22 // split comparator and comparable paths 23 Comparator<? super K> cpr = comparator; 24 if (cpr != null) { 25 do { 26 parent = t; 27 cmp = cpr.compare(key, t.key); 28 if (cmp < 0) 29 t = t.left; 30 else if (cmp > 0) 31 t = t.right; 32 else 33 return t.setValue(value); 34 } while (t != null); 35 } 36 else { 37 if (key == null) 38 throw new NullPointerException(); 39 Comparable<? super K> k = (Comparable<? super K>) key; 40 do { 41 parent = t; 42 cmp = k.compareTo(t.key); 43 if (cmp < 0) 44 t = t.left; 45 else if (cmp > 0) 46 t = t.right; 47 else 48 return t.setValue(value); 49 } while (t != null); 50 } 51 Entry<K,V> e = new Entry<>(key, value, parent); 52 if (cmp < 0) 53 parent.left = e; 54 else 55 parent.right = e; 56 fixAfterInsertion(e); 57 size++; 58 modCount++; 59 return null; 60 } 61 } 62 63 class TreeSet implements Set { 64 private transient NavigableMap<E,Object> m; 65 66 public TreeSet() { 67 this(new TreeMap<E,Object>()); 68 } 69 70 public boolean add(E e) { 71 return m.put(e, PRESENT)==null; 72 } 73 } 74 75 真正的比较是依赖于元素的compareTo()方法,而这个方法是定义在 Comparable里面的。 76 所以,你要想重写该方法,就必须是先 Comparable接口。这个接口表示的就是自然排序。
* 如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口
* TreeSet集合保证元素排序和唯一性的原理
* 唯一性:是根据比较的返回是否是0来决定。
* 排序:
* A:自然排序(元素具备比较性)
* 让元素所属的类实现自然排序接口 Comparable
* B:比较器排序(集合具备比较性)
* 让集合的构造方法接收一个比较器接口的子类对象 Comparator
1 // 如果一个方法的参数是接口,那么真正要的是接口的实现类的对象 2 // 而匿名内部类就可以实现这个东西 3 TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() { 4 @Override 5 public int compare(Student s1, Student s2) { 6 // 姓名长度 7 int num = s1.getName().length() - s2.getName().length(); 8 // 姓名内容 9 int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) 10 : num; 11 // 年龄 12 int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2; 13 return num3; 14 } 15 });
HashSet存储元素保证唯一性的代码及图解:
TreeSet存储元素自然排序和唯一的图解: