第十六章 集合
1. 集合继承结构图

1.1 集合的基本概念
- 数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据
- 集合不能直接存储基本数据类型(list.add(100); //自动装箱Integer),另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址(或者说集合中存储的是引用)
- 所有的集合类和集合接口都在java.util包下
在java中集合分为两大类:
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
一类是以键值对儿的方式存储元素
以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;
2. Map集合继承结构图

3. collection接口常用方法
3.1 Collection中能存放什么元素
没有使用“泛型”之前,Collection中可以存储Object的所有子类型。使用了“泛型”之后,Collection中只能存储某个具体的类型。集合后期我们会学习“泛型”语法。目前先不用管。Collection中什么都能存,只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。)
3.2 Collection中的常用方法
boolean add(Object e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty() 判断该集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组。【了解】
3.3 集合遍历/迭代
public class CollectionTest02 {public static void main(String[] args) {// 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。// 在Map集合中不能用。在所有的Collection以及子类中使用。// 创建集合对象Collection c = new ArrayList(); // 后面的集合无所谓,主要是看前面的Collection接口,怎么遍历/迭代。// 添加元素c.add("abc");c.add("def");c.add(100);c.add(new Object());// 对集合Collection进行遍历/迭代// 第一步:获取集合对象的迭代器对象IteratorIterator it = c.iterator();// 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。/*以下两个方法是迭代器对象Iterator中的方法:boolean hasNext()如果仍有元素可以迭代,则返回 true。Object next() 返回迭代的下一个元素。*/while(it.hasNext()){Object obj = it.next();System.out.println(obj);}// 一直取,不判断,会出现异常:java.util.NoSuchElementException/*while(true){Object obj = it.next();System.out.println(obj);}*//*boolean hasNext = it.hasNext();System.out.println(hasNext);if(hasNext) {// 不管你当初存进去什么,取出来统一都是Object。Object obj = it.next();System.out.println(obj);}*/}}
public class CollectionTest03 {public static void main(String[] args) {// 创建集合对象Collection c1 = new ArrayList(); // ArrayList集合:有序可重复// 添加元素c1.add(1);c1.add(2);c1.add(3);c1.add(4);c1.add(1);// 迭代集合Iterator it = c1.iterator();while(it.hasNext()){// 存进去是什么类型,取出来还是什么类型。Object obj = it.next();/*if(obj instanceof Integer){System.out.println("Integer类型");}*/// 只不过在输出的时候会转换成字符串。因为这里println会调用toString()方法。System.out.println(obj);}// HashSet集合:无序不可重复Collection c2 = new HashSet();// 无序:存进去和取出的顺序不一定相同。// 不可重复:存储100,不能再存储100.c2.add(100);c2.add(200);c2.add(300);c2.add(90);c2.add(400);c2.add(50);c2.add(60);c2.add(100);Iterator it2 = c2.iterator();while(it2.hasNext()){System.out.println(it2.next());}}}
3.4 深入Collection集合的contains方法
contains方法是用来判断集合中是否包含某个元素的方法,那么它在底层是怎么判断集合中是否包含某个元素的呢:调用了equals方法进行比对,equals方法返回true,就表示包含这个元素。 ```java / 测试contains方法 测试remove方法。 结论:存放在一个集合中的类型,一定要重写equals方法。 / public class CollectionTest05 { public static void main(String[] args) {
// 创建集合对象Collection c = new ArrayList();// 创建用户对象User u1 = new User("jack");// 加入集合c.add(u1);// 判断集合中是否包含u2User u2 = new User("jack");// 没有重写equals之前:这个结果是false//System.out.println(c.contains(u2)); // false// 重写equals方法之后,比较的时候会比较name。System.out.println(c.contains(u2)); // truec.remove(u2);System.out.println(c.size()); // 0/*Integer x = new Integer(10000);c.add(x);Integer y = new Integer(10000);System.out.println(c.contains(y)); // true*/// 创建集合对象Collection cc = new ArrayList();// 创建字符串对象String s1 = new String("hello");// 加进去。cc.add(s1);// 创建了一个新的字符串对象String s2 = new String("hello");// 删除s2cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。// 集合中元素个数是?System.out.println(cc.size()); // 0
} }
class User{ private String name; public User(){} public User(String name){ this.name = name; }
// 重写equals方法// 将来调用equals方法的时候,一定是调用这个重写的equals方法。// 这个equals方法的比较原理是:只要姓名一样就表示同一个用户。public boolean equals(Object o) {if(o == null || !(o instanceof User)) return false;if(o == this) return true;User u = (User)o;// 如果名字一样表示同一个人。(不再比较对象的内存地址了。比较内容。)return u.name.equals(this.name);}
}
<a name="UXqhu"></a>### 3.5 关于集合元素的remove- 当**集合的结构发生改变时,迭代器必须重新获取**,如果还是用以前老的迭代器,会出现异常:java.util.ConcurrentModificationException- 在迭代集合元素的过程中,不能调用集合对象的remove方法,会出现:java.util.ConcurrentModificationException- 在迭代元素的过程当中,一定要**使用迭代器Iterator的remove方法,删除元素**,不要使用集合自带的remove方法删除元素。```javapublic class CollectionTest06 {public static void main(String[] args) {// 创建集合Collection c = new ArrayList();// 注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。// 一定要注意:集合结构只要发生改变,迭代器必须重新获取。// 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationExceptionIterator it = c.iterator();// 添加元素c.add(1); // Integer类型c.add(2);c.add(3);// 获取迭代器//Iterator it = c.iterator();/*while(it.hasNext()){// 编写代码时next()方法返回值类型必须是Object。// Integer i = it.next();Object obj = it.next();System.out.println(obj);}*/Collection c2 = new ArrayList();c2.add("abc");c2.add("def");c2.add("xyz");Iterator it2 = c2.iterator();while(it2.hasNext()){Object o = it2.next();// 删除元素// 删除元素之后,集合的结构发生了变化,应该重新去获取迭代器// 但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:java.util.ConcurrentModificationException// 出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)//c2.remove(o); // 直接通过集合去删除元素,没有通知迭代器。(导致迭代器的快照和原集合状态不同。)// 使用迭代器来删除可以吗?// 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。it2.remove(); // 删除的一定是迭代器指向的当前元素。System.out.println(o);}System.out.println(c2.size()); //0}}
4. List接口中常用方法
List集合存储元素特点:有序可重复
有序:List集合中的元素有下标。<br /> 从0开始,以1递增。<br /> 可重复:存储一个1,还可以再存储1.
List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:
以下只列出List接口特有的常用的方法:<br /> void add(int index, Object element) // 用的不多效率低<br /> Object set(int index, Object element)<br /> Object get(int index)<br /> int indexOf(Object o)<br /> int lastIndexOf(Object o)<br /> Object remove(int index)
因为有下标,所以List集合有自己比较特殊的遍历方式,通过下标遍历。【List集合特有的方式,Set没有。】
4.1 ArrayList集合
1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
2、集合底层是一个Object[]数组。
3、构造方法:<br /> new ArrayList();<br /> new ArrayList(20);4、ArrayList集合的扩容:增长到**原容量的1.5倍**。<br />ArrayList集合底层是数组,怎么优化?<br />尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
6、数组缺点:
随机增删元素效率比较低,另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
7、向数组末尾添加元素,效率很高,不受影响。
public class ArrayListTest02 {public static void main(String[] args) {// 默认初始化容量10List myList1 = new ArrayList();// 指定初始化容量100List myList2 = new ArrayList(100);// 创建一个HashSet集合Collection c = new HashSet();// 添加元素到Set集合c.add(100);c.add(200);c.add(900);c.add(50);// 通过这个构造方法就可以将HashSet集合转换成List集合。List myList3 = new ArrayList(c);for(int i = 0; i < myList3.size(); i++){System.out.println(myList3.get(i));}}}
链表的优点: 由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高,在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。 链表的缺点: 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。
4.2 LinkList源码分析
public class LinkedListTest01 {public static void main(String[] args) {// LinkedList集合底层也是有下标的。// 注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。// LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。List list = new LinkedList();list.add("a");list.add("b");list.add("c");for(int i = 0; i <list.size(); i++){Object obj = list.get(i);System.out.println(obj);}// LinkedList集合有初始化容量吗?没有。// 最初这个链表中没有任何元素。first和last引用都是null。// 不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。// 因为我们要面向接口编程,调用的方法都是接口中的方法。//List list2 = new ArrayList(); // 这样写表示底层你用了数组。List list2 = new LinkedList(); // 这样写表示底层你用了双向链表。// 以下这些方法你面向的都是接口编程。list2.add("123");list2.add("456");list2.add("789");for(int i = 0; i < list2.size(); i++){System.out.println(list2.get(i));}}}
5. vector
1、底层也是一个数组
2、初始化容量:10
3、怎么扩容的?
扩容之后是原容量的2倍。
4、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用较少了。
5、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:
java.util.Collections;
java.util.Collection 是集合接口。
java.util.Collections 是集合工具类。
public class VectorTest {public static void main(String[] args) {// 创建一个Vector集合List vector = new Vector();//Vector vector = new Vector();// 添加元素// 默认容量10个。vector.add(1);vector.add(2);vector.add(3);vector.add(4);vector.add(5);vector.add(6);vector.add(7);vector.add(8);vector.add(9);vector.add(10);// 满了之后扩容(扩容之后的容量是20.)vector.add(11);Iterator it = vector.iterator();while(it.hasNext()){Object obj = it.next();System.out.println(obj);}// 这个可能以后要使用!!!!List myList = new ArrayList(); // 非线程安全的。// 变成线程安全的Collections.synchronizedList(myList); // 这里没有办法看效果,因为多线程没学,你记住先!// myList集合就是线程安全的了。myList.add("111");myList.add("222");myList.add("333");}}
建议先学习第十七章的泛型再看一下内容
6. HashSet
1、存储时顺序和取出的顺序不同
2、不可重复。(存入多个相同的内容,存放到集合中的只有一个)
3、放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
7. Map接口
1、Map和Collection没有继承关系。
2、Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址
key起到主导的地位,value是key的一个附属品
3、Map接口中常用方法:
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection
Set
Set
将Map集合转换成Set集合 假设现在有一个Map集合,如下所示: map1集合对象
key value
1 zhangsan 2 lisi 3 wangwu 4 zhaoliu Set set = map1.entrySet(); set集合对象 1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry
】 2=lisi 【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】 3=wangwu 4=zhaoliu —-> 这个东西是个什么?Map.Entry
7.1 Map集合遍历
public class MapTest02 {public static void main(String[] args) {// 第一种方式:获取所有的key,通过遍历key,来遍历valueMap<Integer, String> map = new HashMap<>();map.put(1, "zhangsan");map.put(2, "lisi");map.put(3, "wangwu");map.put(4, "zhaoliu");// 遍历Map集合// 获取所有的key,所有的key是一个Set集合Set<Integer> keys = map.keySet();// 遍历key,通过key获取value// 迭代器可以/*Iterator<Integer> it = keys.iterator();while(it.hasNext()){// 取出其中一个keyInteger key = it.next();// 通过key获取valueString value = map.get(key);System.out.println(key + "=" + value);}*/// foreach也可以for(Integer key : keys){System.out.println(key + "=" + map.get(key));}// 第二种方式:Set<Map.Entry<K,V>> entrySet()// 以上这个方法是把Map集合直接全部转换成Set集合。// Set集合中元素的类型是:Map.EntrySet<Map.Entry<Integer,String>> set = map.entrySet();// 遍历Set集合,每一次取出一个Node// 迭代器/*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();while(it2.hasNext()){Map.Entry<Integer,String> node = it2.next();Integer key = node.getKey();String value = node.getValue();System.out.println(key + "=" + value);}*/// foreach// 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。// 这种方式比较适合于大数据量。for(Map.Entry<Integer,String> node : set){System.out.println(node.getKey() + "--->" + node.getValue());}}}
8. hashMap
8.1 哈希表数据结构
8.2 哈希表
- Hashtable的key可以为null吗?**Hashtable的key和value都是不能为null;HashMap集合的key和value都是可以为null**- Hashtable方法都带有synchronized:**线程安全**的。
线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。
- Hashtable和HashMap一样,底层都是哈希表数据结构。
Hashtable的初始化容量是11,默认加载因子是:0.75f,Hashtable的扩容是:原容量 * 2 + 1
import java.util.HashSet;import java.util.Set;/*1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!equals方法有可能调用,也有可能不调用。拿put(k,v)举例,什么时候equals不会调用?k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。拿get(k)举例,什么时候equals不会调用?k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。2、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。并且equals方法返回如果是true,hashCode()方法返回的值必须一样。equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。所以hashCode()方法的返回值也应该相同。3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。4、终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。5、对于哈希表数据结构来说:如果o1和o2的hash值相同,一定是放到同一个单向链表上。当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。*/public class HashMapTest02 {public static void main(String[] args) {Student s1 = new Student("zhangsan");Student s2 = new Student("zhangsan");// 重写equals方法之前是false//System.out.println(s1.equals(s2)); // false// 重写equals方法之后是trueSystem.out.println(s1.equals(s2)); //true (s1和s2表示相等)System.out.println("s1的hashCode=" + s1.hashCode()); //284720968 (重写hashCode之后-1432604525)System.out.println("s2的hashCode=" + s2.hashCode()); //122883338 (重写hashCode之后-1432604525)// s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,// 按说只能放进去1个。(HashSet集合特点:无序不可重复)Set<Student> students = new HashSet<>();students.add(s1);students.add(s2);// 这个结果按说应该是1. 但是结果是2.显然不符合HashSet集合存储特点。//怎么办?---> 重写hashcode方法System.out.println(students.size());}}
import java.util.HashMap;import java.util.Map;/*HashMap集合key部分允许null吗?允许但是要注意:HashMap集合的key null值只能有一个。有可能面试的时候遇到这样的问题。*/public class HashMapTest03 {public static void main(String[] args) {Map map = new HashMap();// HashMap集合允许key为nullmap.put(null, null);System.out.println(map.size()); // 1// key重复的话value是覆盖!map.put(null, 100);System.out.println(map.size()); //1// 通过key获取valueSystem.out.println(map.get(null)); // 100}}
8.3 hashMap
1、HashMap集合底层是哈希表/散列表的数据结构
2、哈希表是一个怎样的数据结构呢?
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表是一个数组和单向链表的结合体,哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
3、HashMap集合底层的源代码:
public class HashMap{// HashMap底层实际上就是一个数组。(一维数组)Node<K,V>[] table;// 静态的内部类HashMap.Nodestatic class Node<K,V> {// 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,//可以转换存储成数组的下标。)final int hash;final K key; // 存储到Map集合中的那个keyV value; // 存储到Map集合中的那个valueNode<K,V> next; // 下一个节点的内存地址。}}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
4、这两个方法的实现原理,是必须掌握的
map.put(k,v)
v = map.get(k)
5、HashMap集合的key部分特点:
无序,不可重复
为什么无序? 因为不一定挂到哪个单向链表上。 不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
6、哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀
什么是散列分布均匀? 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题? 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了,也是散列分布不均匀。散列分布均匀需要你重写hashCode()方法时有一定的技巧。
7、HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
重点: 放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法
9. TreeSet
1、无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合
2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标
3、TreeSet集合底层实际上是一个TreeMap,TreeMap集合底层是一个二叉树
4、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了
5、对自定义的类型来说,TreeSet可以排序吗?没有指定Person对象之间的比较规则,无法排序
6、放在TreeSet集合中的元素需要实现java.lang.Comparable接口;并且实现compareTo方法(equals可以不写),不然可能出现java.lang.ClassCastException … cannot be cast to class java.lang.Comparable
import java.util.TreeSet;public class TreeSetTest04 {public static void main(String[] args) {Customer c1 = new Customer(32);Customer c2 = new Customer(20);Customer c3 = new Customer(30);Customer c4 = new Customer(25);// 创建TreeSet集合TreeSet<Customer> customers = new TreeSet<>();// 添加元素customers.add(c1);customers.add(c2);customers.add(c3);customers.add(c4);// 遍历for (Customer c : customers){System.out.println(c);}}}// 放在TreeSet集合中的元素需要实现java.lang.Comparable接口。// 并且实现compareTo方法。equals可以不写。class Customer implements Comparable<Customer>{int age;public Customer(int age){this.age = age;}// 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!// k.compareTo(t.key)// 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0// 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。@Overridepublic int compareTo(Customer c) { // c1.compareTo(c2);// this是c1// c是c2// c1和c2比较的时候,就是this和c比较。/*int age1 = this.age;int age2 = c.age;if(age1 == age2){return 0;} else if(age1 > age2) {return 1;} else {return -1;}*///return this.age - c.age; // =0 >0 <0return c.age - this.age;}public String toString(){return "Customer[age="+age+"]";}}
import java.util.TreeSet;/*先按照年龄升序,如果年龄一样的再按照姓名升序。*/public class TreeSetTest05 {public static void main(String[] args) {TreeSet<Vip> vips = new TreeSet<>();vips.add(new Vip("zhangsi", 20));vips.add(new Vip("zhangsan", 20));vips.add(new Vip("king", 18));vips.add(new Vip("soft", 17));for(Vip vip : vips){System.out.println(vip);}}}class Vip implements Comparable<Vip>{String name;int age;public Vip(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Vip{" +"name='" + name + '\'' +", age=" + age +'}';}/*compareTo方法的返回值很重要:返回0表示相同,value会覆盖。返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】返回<0,会继续在左子树上找。*/@Overridepublic int compareTo(Vip v) {// 写排序规则,按照什么进行比较。if(this.age == v.age){// 年龄相同时按照名字排序。// 姓名是String类型,可以直接比。调用compareTo来完成比较。return this.name.compareTo(v.name);} else {// 年龄不一样return this.age - v.age;}}}
import java.util.Comparator;import java.util.TreeSet;/*TreeSet集合中元素可排序的第二种方式:使用比较器的方式。最终的结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:第一种:放在集合中的元素实现java.lang.Comparable接口。第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。Comparable和Comparator怎么选择呢?当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。Comparator接口的设计符合OCP原则。*/public class TreeSetTest06 {public static void main(String[] args) {// 创建TreeSet集合的时候,需要使用这个比较器。// TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。// 给构造方法传递一个比较器。//TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());// 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {@Overridepublic int compare(WuGui o1, WuGui o2) {return o1.age - o2.age;}});wuGuis.add(new WuGui(1000));wuGuis.add(new WuGui(800));wuGuis.add(new WuGui(810));for(WuGui wuGui : wuGuis){System.out.println(wuGui);}}}// 乌龟class WuGui{int age;public WuGui(int age){this.age = age;}@Overridepublic String toString() {return "小乌龟[" +"age=" + age +']';}}// 单独在这里编写一个比较器// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)/*class WuGuiComparator implements Comparator<WuGui> {@Overridepublic int compare(WuGui o1, WuGui o2) {// 指定比较规则// 按照年龄排序return o1.age - o2.age;}}*/
9.1 自平衡二叉树

10. 属性类Properties
- Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。Properties被称为属性类对象。Properties是线程安全的。
public class PropertiesTest01 {public static void main(String[] args) {// 创建一个Properties对象Properties pro = new Properties();// 需要掌握Properties的两个方法,一个存,一个取。pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");pro.setProperty("driver","com.mysql.jdbc.Driver");pro.setProperty("username", "root");pro.setProperty("password", "123");// 通过key获取valueString url = pro.getProperty("url");String driver = pro.getProperty("driver");String username = pro.getProperty("username");String password = pro.getProperty("password");System.out.println(url);System.out.println(driver);System.out.println(username);System.out.println(password);}}
11. Collections工具类
import java.util.*;/*java.util.Collection 集合接口java.util.Collections 集合工具类,方便集合的操作。*/public class CollectionsTest {public static void main(String[] args) {// ArrayList集合不是线程安全的。List<String> list = new ArrayList<>();// 变成线程安全的Collections.synchronizedList(list);// 排序list.add("abf");list.add("abx");list.add("abc");list.add("abe");Collections.sort(list);for(String s : list){System.out.println(s);}List<WuGui2> wuGuis = new ArrayList<>();wuGuis.add(new WuGui2(1000));wuGuis.add(new WuGui2(8000));wuGuis.add(new WuGui2(500));// 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。Collections.sort(wuGuis);for(WuGui2 wg : wuGuis){System.out.println(wg);}// 对Set集合怎么排序呢?Set<String> set = new HashSet<>();set.add("king");set.add("kingsoft");set.add("king2");set.add("king1");// 将Set集合转换成List集合List<String> myList = new ArrayList<>(set);Collections.sort(myList);for(String s : myList) {System.out.println(s);}// 这种方式也可以排序。//Collections.sort(list集合, 比较器对象);}}class WuGui2 implements Comparable<WuGui2>{int age;public WuGui2(int age){this.age = age;}@Overridepublic int compareTo(WuGui2 o) {return this.age - o.age;}@Overridepublic String toString() {return "WuGui2{" +"age=" + age +'}';}}
第十七章 泛型
1. 泛型基本概念
1、JDK5.0之后推出的新特性:泛型
2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用)
3、使用了泛型好处是什么?
第一:集合中存储的元素类型统一了
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
4、泛型的缺点是什么?
导致集合中存储的元素缺乏多样性,大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可
5、可以自定义泛型,<>里面的标识符可以随便写,在源码中一般是E或T
1.1 自动类型推断机制(又称为钻石表达式)
// ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许。// 自动类型推断,钻石表达式!List<Animal> myList = new ArrayList<>();
2. foreach
JDK5.0之后推出了一个新特性:叫做增强for循环,或者叫做foreach ```java public class ForEachTest02 { public static void main(String[] args) { // 创建List集合 List
strList = new ArrayList<>(); // 添加元素 strList.add(“hello”); strList.add(“world!”); strList.add(“kitty!”);
// 遍历,使用迭代器方式 Iterator
it = strList.iterator(); while(it.hasNext()){ String s = it.next();System.out.println(s);
}
// 使用下标方式(只针对于有下标的集合) for(int i = 0; i < strList.size(); i++){
System.out.println(strList.get(i));
}
// 使用foreach for(String s : strList){ // 因为泛型使用的是String类型,所以是:String s
System.out.println(s);
}
List
list = new ArrayList<>(); list.add(100); list.add(200); list.add(300); for(Integer i : list){ // i代表集合中的元素 System.out.println(i);
} } }
```

