集合
一. 集合概述
介绍
Java集合是使程序能够存储和操纵元素不固定的一组数据。 所有Java集合类都位于java.uti包中。
【问】:
之前我们需要把多个元素放到一起的时候,使用的是数组。那么为何还要提供Java集合工具类呢?
- 我们通过对比数组和Java集合工具类来解释Java集合工具类的必要性。
数组 | 集合 |
---|---|
长度固定 | 长度不固定 |
存放任意类型 | 不能存放基本数据类型,只能存放对象的引用 |
注意:
如果集合中存放基本类型,一定要将其 “装箱”成对应的”基本类型包装类”。
二. 层次结构
Java的集合类主要由两个接口派生而出:
Collection和Map。
Collection和Map是Java结合框架的根接口,这两个接口又包含了一些子接口或实现类。
1. Collection的继承层次结构
2. Map的继承层次结构
3. 总结
由以上两图我们可以看出Java集合类有清晰的继承关系,有很多子接口和实现类。但是,并不是所有子接口或实现类都是最常用的。
下面我们列举出最常用的几个子接口和实现类:
Collection ——> List ——> ArrayList类
Collection ——> List ——> LinkedList类
Collection ——> Set ——> HashSet类
Collection ——> Set ——> SortedSet接口 ——> TreeSet类
Map ——> HashMap类
Map ——> SortedMap ——> TreeMap类
三. Collection接口和Iterator
1. Collection介绍
Collection接口是List接口和Set接口的父接口,它定义的方法可以用于操作List集合和Set集合。
刚才得知继承体系结构,Collection类是最大的,也就意味着,Collection中定义的方法规定是List和Set都有的
集合中 只能存储单个元素,并且只能保存引用数据类型,保存Object
- 能放Object 就意味着什么都能放,因为多态
Collection接口定义的方法
方法 | 描述 |
---|---|
boolean add(Object o) | 该方法用于向集合里添加一个元素,添加成功返回true |
void clear() | 清除集合里的所有元素,将集合长度变为0 |
boolean contains(Object o) | 返回集合里是否包含指定元素 |
boolean containsAll(Collection c) | 返回集合里是否包含集合c里的所有元素 |
int hashCode() | 返回此collection的哈希码值 |
boolean isEmpty() | 返回集合是否为空,当集合长度为0时,返回true |
Iterator iterator() | 返回一个Iterator对象,用于遍历集合里的元素 |
boolean remove(Object o) | 删除集合中指定元素o,当集合中包含了一个或多个元素o时,这些元素将被删除,该方法将返回true |
boolean removeAll(Collection c) | 从集合中删除集合c里包含的所有元素,如果删除了一个或一个以上的元素,返回true |
boolean retainAll(Collection c) | 从集合中删除不在集合c里包含的元素,如果删除了一个或一个以上的元素,返回true |
int size() | 返回集合中的元素个数 |
Object[] toArray() | 该方法把集合转换成一个数组,所有集合元素变成对应的数组元素 |
【示例】
public class Collection_01 {
public static void main(String[] args) {
// 创建集合
Collection c1 = new ArrayList();
// 自动装箱为 Integer 类型,然后向上转型为 Object 类型,发生多态
c1.add(1);
c1.add(1);
c1.add(1);
c1.add(1);
c1.add(1.2);
c1.add(true);
c1.add(new Collection_01());
c1.add(new Object());
System.out.println(c1.size());// 8
System.out.println(c1.isEmpty());// false
// 直接遍历集合
for (Object object : c1) {
System.out.println(object);
}
// 1
// 1
// 1
// 1
// 1.2
// true
// com.Collection_01@15db9742
// java.lang.Object@6d06d69c
// 把集合转换为 Object 数组,再遍历
Object[] arr = c1.toArray();
for (Object object : arr) {
System.out.println(object);
}
c1.clear();
System.out.println(c1.size());// 0
}
}
- 删除和判断是否包含
boolean contains(Object o)
判断是否包含某个元素,底层会自动调用该对象的equals
c.contains(m1) : m1调用equals方法,和集合中的元素进行比较,
boolean remove(Object o) 删除集合中某个元素 也会调用 equals方法进行比较
所以 我们再使用 remove和contains的时候,注意覆写equals方法,因为删除和判断是否包含是需要通过equals方法定位元素的
但是 Object中的equals方法默认比较内存地址,所以我们需要根据需求进行equals方法重写
public class Collection_03 {
public static void main(String[] args) {
// 创建集合
Collection c = new ArrayList();
Integer i1 = new Integer(129);
Integer i2 = new Integer(129);
// 插入 i1
c.add(i1);
System.out.println(c.contains(i2));// true
Manager m1 = new Manager(1, "张三");
Manager m2 = new Manager(1, "张三");
c.add(m1);
System.out.println(c.contains(m2));// true
System.out.println(c.size());// 2
c.remove(m2);
System.out.println(c.size());// 1
}
}
class Manager {
int no;
String name;
public Manager(int no, String name) {
super();
this.no = no;
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Manager) {
Manager m = (Manager) obj;
if (m.no == this.no && m.name.equals(this.name)) {
return true;
}
}
return false;
}
}
2. Iterator
【介绍】
- Collection接口的iterator()和toArray()方法都用于获得集合中的所有元素,前者返回一个Iterator对象,后者返回一个包含集合中所有元素的数组。
- Iterator接口隐藏底层集合中的数据结构,提供遍历各种类型集合的统一接口。
Iterator it = 集合对象.iterator();
调用集合对象自己的iterator() 就能创建这个集合的迭代器
迭代器中有三个方法
- boolean hasNext() : 判断当前游标的下一位是否还有元素,有就返回true,没有就返回false
- E next() : 将迭代器的游标向下移动一位,并返回这个数据
- remove() : 删除当前游标指向的元素
- 原则上,上面三个方法必须按照这个顺序调用
迭代器一旦创建,集合中不能添加或删除元素,如果添加或删除了集合中的元素,需要重新生成迭代器
但是更改里面的数据 不用重新生成
- 使用迭代器
public class Collection_02 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(1);
c.add("1wq3");
c.add(false);
c.add(12.32);
// 创建迭代器
Iterator it = c.iterator();
// 如果添加或删除数据之后,一定要重新生成迭代器
c.add(2);
it = c.iterator();
while (it.hasNext()) {
Object o = it.next();
System.out.println(o);
}
// 1.迭代器使用完成之后,不会自动复原,需要重新创建才能再次遍历
Iterator it1 = c.iterator();
while (it1.hasNext()) {
Object o = it1.next();
System.out.println(o);
}
}
}
-
使用迭代器的时候,不能使用集合的删除,应该使用迭代器的删除
-
实例 :
public class Collection_04 {
public static void main(String[] args) {
// Collection c = new ArrayList();
// List l = new ArrayList();
ArrayList c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(4);
// ArrayList 有个 remove 方法重载,传入整型是删除对应的下标
// c.remove(2);
// for (Object object : c) {
// System.out.println(object);
// }
// c.remove(1);
// for (Object object : c) {
// System.out.println(object);
// }
Iterator it = c.iterator();
while (it.hasNext()) {
Object object = it.next();
// 不能使用迭代器的删除,迭代器创建后,集合不能添加删除元素
// c.remove(object);//java.util.ConcurrentModificationException
// 可以使用迭代器的删除,会把迭代器和集合中都删除
// 删除当前指向的元素
it.remove();
}
}
}
【接口主要方法】
方法 | 描述 |
---|---|
boolean hasNext() | 如果被迭代的集合有下一个元素,则返回true |
Object next() | 返回集合里下一个元素 |
void remove() | 删除集合里上一次next方法返回的元素 |
【示例】
【for与iterator对比】
- Iterator的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现。
- 使用Iterator来遍历集合中元素,如果不再使用List转而使用Set来组织数据,则遍历元素的代码不用做任何修改
- 使用for来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样
- for循环需要下标
【示例】
使用Collection接口演示iterator访问不需要下标。(Collection接口没有提供下标访问)
【foreach增强循环】
foreach遍历集合相当于获取迭代器,通过判断是否有下一个元素,执行操作。遍历数组相当于经典的for循环。
优点 | 缺点 |
---|---|
遍历的时候更加简洁 | 不能同时遍历多个集合 |
不用关心集合下标的问题。减少了出错的概率 | 在遍历的时候无法修改和删除集合数据 |
3. List
【特点】
- List是一个有序集合,既存入集合的顺序和取出的顺序一致
- List集合允许添加的元素重复
List不单单继承了Collection的方法,还增加了一些新的方法。
方法 | 描述 |
---|---|
void add(int index, Object element) | 将元素element插入到List的index处 |
boolean addAll(int index, Collection c) | 将集合c所包含的所有元素都插入在List集合的index处 |
Object get(int index) | 返回集合index处的元素 |
int indexOf(Object o) | 返回对象o在List集合中出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在List集合中最后一次出现的位置索引 |
Object remove(int index) | 删除并返回index索引处的元素 |
Object set(int index, Object element) | 将index索引处的元素替换成element对象,返回新元素 |
List subList(int fromIndex, int toIndex) | 返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合 |
-
List
List元素的特点 有序可重复,存入顺序和取出顺序一致,并且可以有多个重复元素
ArrayList : 底层是数组 , 查询快,增删慢
LinkedList : 底层是双向链表 , 增删快,查询慢
-
ArrayList :
默认初始化容量是10 , 扩大容量是原始容量的1.5倍
veator : 默认容量是10,扩大容量是2倍
ArrayLList是veator的升级版,所以vector已经废弃
vector是线程安全,执行效率低
ArrayList是非线程安全,执行效率高
【示例】
public class Collection_05_List_01 {
@Override
public String toString() {
return "===";
}
public static void main(String[] args) {
List l1 = new ArrayList();
l1.add(1);
l1.add("张三");
l1.add(new Collection_05_List_01());
System.out.println(l1);
// [1, 张三, ===] , 因为输出一个 list 的时候,结构是这样的[元素1 , 元素2 , ...]
// 会调用集合中每个元素自身的 toSting()方法
// get : 获取指定索引对应的元素,等于 数组[index]
for (int i = 0; i < l1.size(); i++) {
System.out.println(l1.get(i));
}
// 1
// 张三
// ===
// 把元素插入到指定位置,原位置数据和原位置之后的数据向后移动一位
l1.add(1, "你好");
for (Object object : l1) {
System.out.println(object);
}
// 1
// 你好
// 张三
// ===
// 用指定的元素把指定位置上的元素替换
l1.set(2, "Hello");
Iterator it = l1.iterator();
while (it.hasNext()) {
Object object = it.next();
System.out.println(object);
}
// 1
// 你好
// Hello
// ===
}
}
- 排序
List想要对里面的数据进行排序,那么存入的元素必须实现一个接口 Comparable
Collections.sort(li);
实例 :
public class Collection_06_SortList {
public static void main(String[] args) {
List li = new ArrayList();
li.add(2);
li.add(1);
li.add(5);
li.add(4);
// Arrays.sort(a);
Collections.sort(li);
for (Object object : li) {
System.out.println(object);
}
// 1
// 2
// 4
// 5
A a = new A();
a.add(123);
a.add("张三");
a.add(2);
a.set(2, "李四");
for (int i = 0; i < a.size(); i++) {
System.out.println(a.get(i));
}
// 123
// 张三
// [Ljava.lang.Object;@15db9742
}
}
class A {
private Object[] elementData;
private int index = 0;
public A() {
this.elementData = new Object[10];
}
public int size() {
return index;
}
public void add(Object element) {
elementData[index] = element;
index++;
}
public void set(int index, Object object) {
elementData[index] = elementData;
}
public Object get(int index) {
return elementData[index];
}
}
3.1 ArrayList、Vector
【特点对比】
- ArrayList和Vector都是基于数组实现的,两者用法差不多
- ArrayList随机查询效率高,随机增删元素效率较低
- Vector提供了一个Stack子类,模拟“栈”数据结构——”先进后出”
- ArrayList是线程不安全的,Vector是线程安全的
【重点示例】
ArrayList中存放对象,在判断包含或者删除元素时,通过equals比较是否为同一个对象,然后进行操作
3.2 LinkedList
【特点】
- LinkedList是双向链表实现的
- 随机查询效率低,随机增删效率高
【LinkedList新增方法】
方法 | 描述 |
---|---|
void addFirst(Object e) | 将指定元素插入该集合的开头 |
void addLast(Object e) | 将指定元素插入该集合结尾 |
boolean offerFirst(Object e) | 将指定元素插入该集合的开头 |
boolean offerLast(Object e) | 将指定元素插入该集合结尾 |
boolean offer(Object e) | 将指定元素插入该集合结尾 |
Object getFirst() | 获取,但不删除集合第第一个元素 |
Object getLast() | 获取,但不删除集合最后一个元素 |
Object peekFirst() | 获取,但不删除该集合第一个元素,如果集合为空,则返回null |
Object peekLast() | 获取,但不删除该集合最后一个元素,如果集合为空,则返回null |
Object pollFirst() | 获取,并删除该集合第一个元素,如果集合为空,则返回null |
Object pollLast() | 获取,并删除该集合最后一个元素,如果集合为空,则返回null |
Object removeFirst() | 获取,并删除该集合的第一个元素 |
Object removeLast() | 获取,并删除该集合的最后一个元素 |
Object pop() | pop出该集合的第一个元素 |
void push(Object e) | 将一个元素push到集合 |
链表 : 链表中保存节点,节点中有三个元素,自身对象(添加的元素),下一个节点的地址,上一个节点的地址
- LinkedList就是基于双向链表实现的
链表是没有下标的,所以查询慢, 当然我们可以通过 get方法 使用下标找到数据,
但是get方法中,也是通过循环,从首节点一步步向后找的,不像ArrayList,底层是数组,有下标,可以直接数组[下标],可以参考源码
【示例】
public class Collection_07_LinkedList {
public static void main(String[] args) {
LinkedList li = new LinkedList();
// 尾部添加 成功返回true
li.add(1);
// 头部添加
li.push(7);
// 头部添加
li.addFirst(2);
// 尾部添加
li.addLast(3);
// 头部添加,成功返回true
li.offerFirst(4);
// 尾部添加,成功返回true
li.offerLast(5);
// 尾部添加,成功返回true
li.offer(6);
// 上面几个方法 , 本质调用的就是两个方法, linkLast和linkFirst 所以没有任何区别,主要为了解决大家的命名习惯问题
// 4271356
System.out.println(li);
// 获取指定下标对应的元素
System.out.println(li.get(2));
System.out.println("--");
// 获取首位元素
System.out.println(li.getFirst());
// 获取最后一个元素
System.out.println(li.getLast());
// 获取首位元素,并删除该元素,如果没有首位元素(就是集合中元素个数为0 ) 返回null
System.out.println(li.poll());
// 获取首位元素,并删除该元素,如果没有首位元素(就是集合中元素个数为0 ) ,报错
// java.util.NoSuchElementException
System.out.println(li.pop());
for (Object object : li) {
System.out.println(object);
}
}
}
【总结】
- List主要有两个实现ArrayList和LinkedList,他们都是有顺序的,也就是放进去是什么顺序,取出来还是什么顺序
- ArrayList——遍历、查询数据比较快,添加和删除数据比较慢(基于可变数组)
- LinkedList——查询数据比较慢,添加和删除数据比较快(基于链表数据结构)
- Vector——Vector已经不建议使用,Vector中的方法都是同步的,效率慢,已经被ArrayList取代
- Stack——继承Vector实现的栈,栈结构是先进后出,但已被LinkedList取代
实例 :
public class Collection_08_LinkedList {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
linkedList.add("d");
linkedList.remove("c");
System.out.println(linkedList);
// [a, b, d]
}
}
4. Set
【特点】
- Set是一个无序集合,既存入集合的顺序和取出的顺序不一致
- Set集合中元素不重复
【常用方法】
方法 | 描述 |
---|---|
boolean add(E e) | 如果此set中尚未包含指定元素,则添加指定元素 |
boolean isEmpty() | 如果此set不包含任何元素,则返回true |
boolean contains(Object o) | 如果此set包含指定元素,则返回 true |
boolean remove(Object o) | 如果指定元素存在于此set中,则将其移除 |
int size() | 返回此set中的元素的数量 |
void clear() | 从此set中移除所有元素 |
【示例】
public class Collection_09_SortedSet_01 {
public static void main(String[] args) throws ParseException {
SortedSet ss = new TreeSet();
ss.add(1);
ss.add(22);
ss.add(2);
System.out.println(ss);
// [1, 2, 22]
ss = new TreeSet();
ss.add("abc");
ss.add("abd");
ss.add("ba");
ss.add("ac");
System.out.println(ss);
// [abc, abd, ac, ba]
ss = new TreeSet();
String st1 = "2008-08-08";
String st2 = "2008-08-07";
String st3 = "2008-09-01";
String st4 = "2007-09-01";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse(st1);
Date d2 = sdf.parse(st2);
Date d3 = sdf.parse(st3);
Date d4 = sdf.parse(st4);
ss.add(d1);
ss.add(d2);
ss.add(d3);
ss.add(d4);
for (Object object : ss) {
Date date = (Date) object;
System.out.println(sdf.format(date));
}
// 2007-09-01
// 2008-08-07
// 2008-08-08
// 2008-09-01
}
}
实例 :
public class Collection_10_SortedSet_02 {
public static void main(String[] args) {
SortedSet users = new TreeSet();
users.add(new User(19));
users.add(new User(20));
users.add(new User(1));
users.add(new User(23));
System.out.println(users.size());
System.out.println(users);
}
}
class User implements Comparable {
int age;
public User(int age) {
super();
this.age = age;
}
@Override
public String toString() {
return "User [age=" + age + "]";
}
@Override
public int compareTo(Object o) {
if (!(o instanceof User)) {
return 0;
}
User u1 = (User) o;
return this.age - u1.age;
// 返回0 说明集合中有这个元素,就不添加
// 小于0 说明要添加的元素比集合中的元素小,往前放
// 大于0 说明大,往后放
// return -1;
}
}
4.1 HashSet
哈希表 : 又叫散列表,用来保存键值对(key,value), 就是一个数组中,每个元素都是一个单向链表
根据添加元素的hash值,通过hash算法,就能够确定要把元素添加到哪个链表中
hash算法 : 一种安全加密算法,把不定长的值,更改为定长的值,不能保证其值的唯一性
其中算法包括 :
- 直接寻址法
- 数字分析法
- 平方取中法
- 随机数
- 除数取余法
java中屏蔽了散列数据结构,封装到了HashMap/HashSet/HashTable中,其中HashTable已经过时
hash算法 : java中指的是 hashCode()函数
目标 : 给每个对象生成一个唯一的标识符
散列表的数组下标还是0~N,只不过根据key得到的hash值,再通过hash算法就能得到这个元素所在的下标
hash的目的 : 为了查询快,hash值是一个固定的值,整型值,所以在定长的情况下,查询极快
HashSet和HashMap :
- HashSet 就是HashMap的一个封装,只不过把value值屏蔽了
- HashMap中是key,value 键值对 但是在HashSet中 只有 key
如何向hash表中添加数据
-
添加的时候,HashMap保存的是映射关系,这个映射关系靠 Map.Entry(K,V) 来维护,是一个接口
单向链表 : 每个节点有三部分
1 有一个Entry(K,V)
2 next
3 hash值 -
添加过程 : 先调用要存储的映射关系的key对象,调用key对象的hashCode()方法,生成hash码值
然后根据hash值通过hash算法进行hash,得到数组下标,然后想数组中添加元素
如果数组中还没有对应的hash的数据,就占用一个数组的空间,保存这个key-value的映射关系
如果有,就调用key的equals方法,挨个在hash值相同的链表中,进行比较,如果返回true,那么代表 该链表中有这个key
如果已经有这个元素.key不添加,value值覆盖原来的value
如果equals方法返回false,说明两个对象只是hash值相同,但是并不是同一个对象
那么就把该对象插入到链表中
HashSet 只保存一个元素
HashMap 保存键值对
添加的元素 要实现equals方法和hashCode方法
实例 :
public class Collection_13_Set_01 {
public static void main(String[] args) {
Set s = new HashSet();
s.add(1);
s.add(1);
s.add(2);// id=1
System.out.println(s);
HashSet hs = new HashSet();
Employee e1 = new Employee("1000", "obama");
Employee e2 = new Employee("1000", "obalv");
Employee e3 = new Employee("2000", "张三");
Employee e4 = new Employee("3000", "张三");
Employee e5 = new Employee("4000", "张三");
Employee e6 = new Employee("5000", "李四");
// System.out.println(e1.hashCode());
// System.out.println(e2.hashCode());
// System.out.println(e3.hashCode());
// System.out.println(e4.hashCode());
// System.out.println(e5.hashCode());
// System.out.println(e6.hashCode());
// hashCode执行了
// 1507423
// hashCode执行了
// 1507423
// hashCode执行了
// 1537214
// hashCode执行了
// 1567005
// hashCode执行了
// 1596796
// hashCode执行了
// 1626587
hs.add(e1);
// 如果重复就不添加
hs.add(e2);
hs.add(e3);
hs.add(e4);
hs.add(e5);
hs.add(e6);
for (Object object : hs) {
System.out.println(object);
}
// hashCode执行了
// hashCode执行了
// equals执行了
// hashCode执行了
// hashCode执行了
// hashCode执行了
// hashCode执行了
// Employee [no=5000, name=李四]
// Employee [no=4000, name=张三]
// Employee [no=1000, name=obama]
// Employee [no=2000, name=张三]
// Employee [no=3000, name=张三]
}
}
class Employee {
String no;
String name;
public Employee(String no, String name) {
super();
this.no = no;
this.name = name;
}
@Override
public boolean equals(Object obj) {
System.out.println("equals执行了");
if (this == obj) {
return true;
}
if (obj instanceof Employee) {
Employee e = (Employee) obj;
if (no.equals(e.no)) {
return true;
}
}
return false;
}
static int i = 16;
@Override
public int hashCode() {
System.out.println("hashCode执行了");
return no.hashCode();
}
@Override
public String toString() {
return "Employee [no=" + no + ", name=" + name + "]";
}
}
4.2 TreeSet
TreeSet底层由TreeMap实现。可排序,默认自然升序。
- 想要让TreeSet集合中元素进行排序 有三种方式
要添加的元素对应的类实现java.lang.Comparable接口,并实现compareTo方法
让SortedSet集合做到排序还有一种方法,java.util.Comparator比较器类
写一个匿名内部类 ,还是 java.util.Comparator
默认的一些类型中,都实现了比较器,比如Date,String,Integer等等
所以 我们自定义类一个数据类型的时候(类),当需要对这个类对象进行排序,就需要我们自定义比较器,用于比较
如果 两种排序方式都有的话,优先使用compare()方法
源码中是这样调用的,先判断有没有compare(),如果没有再去执行compareTo(),在TreeMap类中的1294行
Comparable : 我们叫做元素自身的比较器,就是要添加的元素需要实现这个接口,这种情况下一般用于我们自定义的类中
Comparator : 比较器类,常应用于:比如Integer是默认升序,我想降序,就需要使用Comparator来重新设置排序,因为它的优先级高
如果添加的元素不是我们自定义的类,
-
该类有排序(实现了Comparable), 但不是我们想要的排序效果,需要使用 Comparator 来进行调整排序,因为它的优先级高
-
如果该类没有排序(没有实现Comparable),我们就需要使用Comparator来设置排序,因为这个时候我们不可能去更改人家的源码,不可能让这个类去实现Comparable的
实例 :
public class Collection_11_SortedSet_03 {
public static void main(String[] args) {
// SortedSet products = new TreeSet(new ProductComparator());
@SuppressWarnings("unchecked")
SortedSet products = new TreeSet(new Comparator() {
//匿名内部类实现
@Override
public int compare(Object o1, Object o2) {
// o1是要添加的元素
// o2是集合中的元素
double price1 = ((Product1)o1).price;
double price2 = ((Product1)o2).price;
if (price1 == price2) {
return 0;
}else if (price1 > price2) {
return 1;
}else {
return -1;
}
}
});
Product1 p1 = new Product1(1.5);
Product1 p2 = new Product1(2.2);
Product1 p3 = new Product1(0.5);
products.add(p1);
products.add(p2);
products.add(p3);
System.out.println(products);
//[Product1 [price=0.5], Product1 [price=1.5], Product1 [price=2.2]]
}
}
class Product1{
double price;
public Product1(double price) {
super();
this.price=price;
}
@Override
public String toString() {
return "Product1 [price="+price+"]";
}
}
class ProductComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
// o1是要添加的元素
// o2是集合中的元素
double price1 = ((Product1)o1).price;
double price2 = ((Product1)o2).price;
if (price1 == price2) {
return 0;
}else if (price1 > price2) {
return 1;
}else {
return -1;
}
}
}
如果一个类没有实现Comparable接口,那么把对象放到list中之后,不能直接调用sort(list)进行排序
但是可以使用sort的方法重载,sort(list,比较器类),来实现对list的排序
实例 :
public class Collection_12_SortList {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List list = new ArrayList();
list.add(new User1(18));
list.add(new User1(12));
list.add(new User1(23));
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((User1) o1).age - ((User1) o2).age;
}
});
System.out.println(list);
}
}
class User1 {
int age;
public User1(int age) {
super();
this.age = age;
}
@Override
public String toString() {
return "User1 [age=" + age + "]";
}
}
【总结】
- HashSet底层由HashMap实现
- TreeSet底层由TreeMap实现
- <详解见HashMap、TreeMap>
5. Collections工具类和Comparable、Comparator比较器
5.1 Collections工具类
Collections是一个包装工具类。它包含有各种有关集合操作的静态多态方法,此类不能实例化,服务于Java的Collection接口。
【常用方法】
sort、reverse、fill、copy、max、min、swap等
【重点讲解:Sort排序】
其他方法,我们自行练习
public static <T extends Comparable<? super T>> void sort(List list)
- 根据元素的自然顺序 对指定列表按升序进行排序。列表中的所有元素都必须实现 Comparable 接口。
- 此外,列表中的所有元素都必须是可相互比较的(也就是说,对于列表中的任何 e1 和 e2 元素,e1.compareTo(e2) 不得抛出 ClassCastException)。
【示例】
我们打开API查看String类,它是实现了Comparable接口的。所以,我们可以使用字符串对象为例,演示sort排序。
5.2 Comparable、Comparator比较器
对象排序,就是比较大小,要实现Comparable或Comparator比较器之一,才有资格做比较排序。
【介绍】
- Comparable:与对象紧相关的比较器,可以称“第一方比较器”。
- Comparator:此为与具体类无关的第三方比较器。
【示例】
四. Map接口
Map用于保存具有映射关系的数据,因此Map集合里保存两组值。
- 一组值用于保存key,一组值用于保存value
- key~value之间存在单向一对一关系,通过指定key可以找到唯一的value值
- key和value都可以是任何引用类型对象
- 允许存在value为null,但是只允许存在一个key为null
【常用方法】
方法 | 描述 |
---|---|
V put(K key, V value) | 将指定的值与此映射中的指定键关联 |
boolean containsKey(Object key) | 如果此映射包含指定键的映射关系,则返回true |
boolean containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回true |
boolean isEmpty() | 如果此映射未包含键-值映射关系,则返回true |
V get(Object key) | 返回指定键所映射的值,如果此映射不包含该键的映射关系,则返回null |
Set keySet() | 返回此映射中包含的键的set集合 |
Collection values() | 返回此映射中包含的值的Collection集合 |
Set<Map.Entry<K,V>> entrySet() | 返回此映射中包含的映射关系的set集合 |
boolean equals(Object o) | 返回指定的对象与此映射是否相等 |
int hashCode() | 返回此映射的哈希码值 |
V remove(Object key) | 如果存在一个键的映射关系,则将其从此映射中移除 |
void clear() | 从此映射中移除映射关系 |
int size() | 返回此映射中的键-值关系数 |
【示例】
1. HashMap类
【特点】
- key无序不可重复
- 底层是哈希表
【哈希表实现原理】 - HashMap实际上是一个"链表的数组"的数据结构,每个元素存放链表头结点的数组,即数组和链表的结合体。
- 当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
- 只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
- 如果这两个Entry的key通过equals比较返回true,新添加Entry的value将覆盖集合中原有Entry的value,但key不会覆盖。
【总结】
- HashMap中key的hashCode值决定了<k,v>键值对组成的entry在哈希表中的存放位置。
- HashMap中key通过equals()比较,确定是覆盖key对应的value值还是在链表中添加新的entry。
- 综合前两条,需要重写hashCode()和equals()方法。
Map : 无序可重复 value可重复 , key不可重复
Map和Collection集合不一样,但是操作都是类似的
put(key,value) 添加数据
clear()
size()
isEmpty();
get(key) 根据key 获取value值
values() : 获取所有的value,以集合形式返回
containsKey(key)
containsValue(value)
keySet() 获取所有key,以set形式返回
entrySet() 获取键值对,以set形式返回
remove()
map不能直接遍历
【示例】
public class Collection_14_Map_01 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Map map = new HashMap();
map.put("A", "one");
map.put("B", "two");
map.put("C", "three");
map.put(1003, "rose"); // Integer
map.put('A', "1000");
map.put(65, "1000");
map.put("'A'", "3000");
map.put("A", "2000"); // value 覆盖
// 可以什么都是 null ,但是没什么意义
// key 只能有一个为null,因为唯一,value可以有很多null
map.put(null, null);
// 8个
System.out.println(map.size());
// false
System.out.println(map.containsKey("1003"));
// true
System.out.println(map.containsValue("2000"));
// 2000
System.out.println(map.get("A"));
System.out.println("---");
// 获取所有的value
Collection values = map.values();
for (Object object : values) {
System.out.println(object);
}
// null
// 2000
// 1000
// 1000
// two
// three
// rose
// 3000
System.out.println("==--");
// 根据key删除,返回对应的value值
map.remove(null);
System.out.println("===");
// 把map中的key取出,形成set
Set s = map.keySet();
for (Object object : s) {
System.out.println(object + " : " + map.get(object));
}
// A : 2000
// A : 1000
// 65 : 1000
// B : two
// C : three
// 1003 : rose
// 'A' : 3000
System.out.println("-==-");
// 把map中的所有entry 取出,形成Set
// 并且 entry 覆写了 toString方法,会以 key=value的形式展示
Set set = map.entrySet();
for (Object object : set) {
System.out.println(object);
}
// A=2000
// A=1000
// 65=1000
// B=two
// C=three
// 1003=rose
// 'A'=3000
System.out.println("---");
System.out.println(1);
System.out.println("1");
System.out.println('1');
}
}
2. TreeMap
TreeMap是SortedMap接口的实现类,可以根据Key进行排序,HashMap没有这个功能。
【特点】
- 底层由可排序二叉树实现
- 不指定比较器默认按照key自然升序,指定比较器按照比较器排序
【示例】
- 匿名内部类实现
package com;
import java.util.Comparator;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public class Collection_15_SortedMap_01 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
@SuppressWarnings("unchecked")
SortedMap products = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
double price1 = ((Product) o1).price;
double price2 = ((Product) o2).price;
if (price1 == price2) {
return 0;
} else if (price1 < price2) {
return -1;
} else {
return 1;
}
}
});
Product p1 = new Product("water", 1.0);
Product p2 = new Product("苹果", 3.0);
Product p3 = new Product("香蕉", 2.0);
Product p4 = new Product("梨", 1.5);
// value 可以设置为已购买的数量
products.put(p1, 4.0);
products.put(p2, 2.0);
products.put(p3, 1.0);
products.put(p4, 4.0);
Set keys = products.keySet();
for (Object key : keys) {
Product p = (Product) key;
double value = (double) products.get(key);
System.out.println(p + "->" + value + "kg 总价 = " + (value * p.price));
}
// Product [name=water, price=1.0]->4.0kg 总价 = 4.0
// Product [name=梨, price=1.5]->4.0kg 总价 = 6.0
// Product [name=香蕉, price=2.0]->1.0kg 总价 = 2.0
// Product [name=苹果, price=3.0]->2.0kg 总价 = 6.0
}
}
class Product {
String name;
double price;
public Product(String name, double price) {
super();
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product [name=" + name + ", price=" + price + "]";
}
}
- 元素自身覆写了 compareTo 方法
package com;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public class Collection_16_SortedMap_02 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
SortedMap products = new TreeMap();
Product2 p1 = new Product2("water", 1.0);
Product2 p2 = new Product2("苹果", 3.0);
Product2 p3 = new Product2("香蕉", 2.0);
Product2 p4 = new Product2("梨", 1.5);
// value 可以设置为 已购买的数量
products.put(p1, 4.0);
products.put(p2, 2.0);
products.put(p3, 1.0);
products.put(p4, 4.0);
Set keys = products.keySet();
for (Object key : keys) {
Product2 p = (Product2) key;
double value = (double) products.get(key);
System.out.println(p + "--->" + value + "kg 总价 = " + (value * p.price));
}
}
}
class Product2 implements Comparable {
String name;
double price;
public Product2(String name, double price) {
super();
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Product [name=" + name + ", price=" + price + "]";
}
@Override
public int compareTo(Object o) {
double price1 = this.price;
double price2 = ((Product2) o).price;
if (price1 == price2) {
return 0;
} else if (price1 < price2) {
return -1;
} else {
return 1;
}
}
}
- 再来一个匿名内部类:
package com;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Collection_17_SortedMap_03 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Set set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
int a = ((Integer) o1);
int b = ((Integer) o2);
return b - a;
}
});
set.add(1);
set.add(5);
set.add(3);
set.add(2);
System.out.println(set);
}
}
Map转list
Map 转换为 List 并以 value 进行排序
实例 :
public class Collection_21_MapToList {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("c", 2);
map.put("d", 1);
map.put("b", 12);
// 因为 map 中存储的是entry,所以想要把 map 转换为 list ,那么list 中的元素应该就是 Entry类型
// 先把entry拿出来
Set set = map.entrySet();
// 调用ArrayList 的有参构造,把 set 直接转换为 List
List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set);
Collections.sort(list, new Comparator<Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
// TODO Auto-generated method stub
return o1.getValue() - o2.getValue();
}
});
System.out.println(list);
// [a=1, d=1, c=2, b=12]
}
}
【总结】
TreeMap需要key实现Comparable接口,排序主要看compareTo()方法。
五. 泛型
泛型是指所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
【示例】
- 定义一个对象池对象,可以根据需求存放各种类型对象。
class ProjectPool<T>{
private List<T> list = new ArrayList<T>();
public void add(T t){
list.add(t);
}
public int size(){
return list.size();
}
}
- 什么是泛型
类型检查
编译过程中,检查数据类型是否匹配
- 什么是泛型 :
集合实际上和数组一样,只能存储统一数据类型,只不过这个类型是Object,所有元素都会发生向上转型,所以导致什么都能放
泛型不能使用基本数据类型,只能使用引用数据类型
转型为多态之后,子类的属性就访问不到了,想要使用,必须向下转型,很麻烦
类似于中药柜子
优点 : 统一类型,减少类型转换
缺点 : 只能存储单一类型的元素
实例 :
public class Collection_18_Generic_01 {
public static void main(String[] args) {
Set s = new HashSet();
A a = new A();
B b = new B();
C c = new C();
s.add(a);
s.add(b);
s.add(c);
for (Object object : s) {
// A d = (A)object;
if (object instanceof A) {
A d = (A) object;
d.m1();
} else if (object instanceof B) {
B d = (B) object;
d.m1();
}
}
// A 的 m1方法
// B 的 m1方法
List<D> ds = new ArrayList<D>();
ds.add(a);
ds.add(b);
ds.add(c);
for (D d : ds) {
d.m1();
}
// A 的 m1方法
// B 的 m1方法
// C 的 m1方法
}
}
interface D {
public void m1();
}
class A implements D {
public void m1() {
System.out.println("A 的 m1方法");
}
}
class B implements D {
public void m1() {
System.out.println("B 的 m1方法");
}
}
class C implements D {
public void m1() {
System.out.println("C 的 m1方法");
}
}
使用大写字母A,B,C,D…X,Y,Z定义的,就都是泛型,把T换成A也一样,这里T只是名字上的意义而已
? 表示不确定的java类型
T (type) 表示具体的一个java类型
K V (key value)分别代表java键值中的Key Value
E (element) 代表Element
如果人家规定了泛型,我们没有传递类型使用的情况,下, 泛型类型 就等于是Object
实例 :
public class Collection_19_Generic_02 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
// 只能添加String
// list.add(1);
list.add("aaa");
list.add("aadsaa");
list.add("aaa321");
// 不需要强制类型转换
for (String string : list) {
System.out.println(string);
}
// aaa
// aadsaa
// aaa321
List<User> users = new ArrayList<User>();
users.add(new User(12));
users.add(new User(11));
users.add(new User(15));
for (User user : users) {
System.out.println(user.age);
}
// 12
// 11
// 15
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("sad", 2);
}
}
class User {
int age;
public User(int age) {
super();
this.age = age;
}
}
- 集合中使用泛型
List<Student> stuList = new ArrayList<Student>();
实例 :
package com;
public class Collection_20_Generic_03 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.m1(1);
myClass.m1("asd");
// 1
// asd
MyClass<String> myClass2 = new MyClass();
// myClass2.m1(2);
myClass2.m1("asdf");
}
}
class MyClass<T> {
public void m1(T t) {
System.out.println(t);
}
}
【总结】
- 泛型能更早的发现错误,如类型转换错误
- 使用泛型,那么在编译期将会发现很多之前要在运行期发现的问题
- 代码量往往会少一些、运维成本减少
- 抽象层次上更加面向对象