第十六章 集合
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进行遍历/迭代
// 第一步:获取集合对象的迭代器对象Iterator
Iterator 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);
// 判断集合中是否包含u2
User u2 = new User("jack");
// 没有重写equals之前:这个结果是false
//System.out.println(c.contains(u2)); // false
// 重写equals方法之后,比较的时候会比较name。
System.out.println(c.contains(u2)); // true
c.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");
// 删除s2
cc.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方法删除元素。
```java
public class CollectionTest06 {
public static void main(String[] args) {
// 创建集合
Collection c = new ArrayList();
// 注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。
// 一定要注意:集合结构只要发生改变,迭代器必须重新获取。
// 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException
Iterator 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) {
// 默认初始化容量10
List myList1 = new ArrayList();
// 指定初始化容量100
List 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,来遍历value
Map<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()){
// 取出其中一个key
Integer key = it.next();
// 通过key获取value
String 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.Entry
Set<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方法之后是true
System.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为null
map.put(null, null);
System.out.println(map.size()); // 1
// key重复的话value是覆盖!
map.put(null, 100);
System.out.println(map.size()); //1
// 通过key获取value
System.out.println(map.get(null)); // 100
}
}
8.3 hashMap
1、HashMap集合底层是哈希表/散列表的数据结构
2、哈希表是一个怎样的数据结构呢?
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表是一个数组和单向链表的结合体,哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
3、HashMap集合底层的源代码:
public class HashMap{
// HashMap底层实际上就是一个数组。(一维数组)
Node<K,V>[] table;
// 静态的内部类HashMap.Node
static class Node<K,V> {
// 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,
//可以转换存储成数组的下标。)
final int hash;
final K key; // 存储到Map集合中的那个key
V value; // 存储到Map集合中的那个value
Node<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
// 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
@Override
public 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 <0
return 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;
}
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/*
compareTo方法的返回值很重要:
返回0表示相同,value会覆盖。
返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
返回<0,会继续在左子树上找。
*/
@Override
public 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>() {
@Override
public 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;
}
@Override
public String toString() {
return "小乌龟[" +
"age=" + age +
']';
}
}
// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
/*
class WuGuiComparator implements Comparator<WuGui> {
@Override
public 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获取value
String 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;
}
@Override
public int compareTo(WuGui2 o) {
return this.age - o.age;
}
@Override
public 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);
} } }
```