容器框架(Collection、Map)
一、Collection(单列集合)
1.1 集合概述
- 集合:是Java提供的一种容器,可以用来存储多个数据。
集合和数组都是容器,两者的区别?
-
数组的长度是固定的,集合的长度是可变的。
-
数组存储的是同一类型的元素,可以是基本类型,也可以是引用类型。
集合存储的都是对象(引用类型),而且对象的类型可以不一致。在开发中,一般当对象多的时候可以用集合进行存储
1.2 集合框架
1.3 Collection集合共性的方法:
- public boolean add(E e); 添加元素
- public void clear(); 清空集合中所有的元素
- public boolean remove(E e); 删除元素:集合中存在元素,删除,返回true;不存在,删除失败,返回false
- public boolean contains(E e); 判断当前集合中是否包含给定的对象
- public boolean isEmpty(); 判断当前集合是否为空
- public int size(); 返回集合中元素的个数
- public Object[] toArray(); 把集合中的元素,存储到数组中
//java.util.Collection接口:所有单列集合最顶层的接口,里面定义了所有单列集合共性的方法。
public class Demo1Collection {
public static void main(String[] args) {
//使用多态创建集合
Collection<String> coll = new ArrayList<>();
//添加元素:add
boolean b = coll.add("KD");
//System.out.println(b); //true 添加成功
coll.add("PG");
coll.add("James");
coll.add("KT");
System.out.println(coll); //[KD, PG, James, KT] 重写了toString()
//当前集合所含元素个数:size
System.out.println("当前集合中含有元素:" + coll.size() + "个"); //4
//移除元素:remove
coll.remove("PG");
System.out.println(coll); //[KD, James, KT]
System.out.println("移除一个元素后,元素个数:" + coll.size() + "个"); //3
//判断当前集合中是否含有给定的对象:contains
boolean b1 = coll.contains("PG");
System.out.println("集合中是否含有元素'PG':" + b1); //false
//判断当前集合是否为空:isEmpty
System.out.println("当前集合是否为空:" + coll.isEmpty()); //false
//把集合中元素存储到数组中
Object[] obj = coll.toArray();
System.out.println("数组长度:" + obj.length); //3
//遍历数组obj
System.out.print("数组元素:");
for (int i = 0; i < obj.length; i++) {
System.out.print(obj[i] + " ");
}
System.out.println();
//清空集合:clear 并不删除集合,集合还存在。
coll.clear();
System.out.println(coll); //[]
}
}
1.4 集合的遍历方式:
- (带索引的集合可用)普通for循环
- 迭代器
- for-each循环
二、Iterator迭代器
2.1 Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator。
Iterator接口也是java集合中的一员,但它与Collection、Map接口有所不同,Collection与Map接口主要用于存
储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出,这种取出方式专业术语称为迭代。
2.2 迭代器的使用
java.util.Iterator接口:迭代器(对集合进行遍历)
1.有两个常用的方法:
boolean hasNext(); 如果仍有元素可以迭代,则返回true
判断集合中还有没有下一个元素,有就返回true,没有就返回false
E next(); 返回迭代的下一个元素
取出集合中的下一个元素
2.Iterator迭代器,是一个接口,我们无法使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊:
Collection接口中有一个方法,叫iterator(),这个方法返回的就是迭代器的实现类对象
Collection.Iterator<E> iterator(); 返回在此Collection的元素上进行迭代的迭代器。
3.迭代器的使用步骤(重点):
① 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
② 使用Iterator接口中的方法hasNext()判断还有没有下一个元素
③ 使用Iterator接口中的方法next()取出集合中的下一个元素
NoSuchElementException 没有下一个元素异常!
public class Demo1Iterator {
public static void main(String[] args) {
//创建一个集合对象
Collection<String> coll = new ArrayList<>();
//添加元素
coll.add("杜兰特");
coll.add("詹姆斯");
coll.add("库里");
coll.add("韦德");
coll.add("威斯布鲁克");
coll.add("哈登");
//获取迭代器对象
//多态: 接口 实现类对象
Iterator<String> iterator = coll.iterator();
//判断集合中还有没有下一个元素
while(iterator.hasNext()){
System.out.println(iterator.next()); //获取该元素
}
2.3 迭代器实现原理
2.4 For-Each循环
增强for循环(也称for-each循环):JDK1.5之后新特性,专门用来遍历集合和数组。
它的内部原理其实是一个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
格式:
for(数组/集合的数据类型 变量名 :数组/集合名){
//操作代码; }
三、泛型Generic
3.1 概述
未知的数据类型。当不确定集合中存储元素的数据类型时,就可以使用泛型来表示。比如:
E e : Element
T t : Type
3.2 集合(不)使用泛型
- 不使用泛型:
①好处:默认Object类型,可以存储任意类型的数据
②弊端:不安全,会引发异常。ClassCastException!
public class Demo1Generic {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("abc");
list.add(1);
//获取迭代器
Iterator it = list.iterator();
//判断还有没有下一个元素: hasNext()
//有就获取该元素
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
/* 想使用String特有的方法:
length获取长度;父类不能使用子类特有的方法和属性,多态 Object obj = "abc"
向下转型
会抛出 ClassCastException!不能把Integer类型转换为String */
String s = (String)obj;
System.out.println(s.length());
}
}
}
- 使用泛型:
①优点:避免类型转换的麻烦,存什么类型就取出什么类型。
②缺点:泛型是什么类型,只能存储什么类型的数据。
public class Demo2Generic {
public static void main(String[] args) {
//使用泛型创建一个集合对象
ArrayList<String> list =new ArrayList<>();
//添加元素
list.add("彭于晏");
list.add("李一桐");
list.add("128269");
//使用迭代器遍历集合
//获取迭代器对象
Iterator<String> it = list.iterator();
//hasNext(): 判断还有没有下一个元素
while(it.hasNext()){
//获取该元素
String s = it.next();
//存储的都是String类型,可以使用length(),避免了类型转换的麻烦!
System.out.println(s + "-->" + s.length());
}
}
}
3.3 泛型的定义与使用
3.3.1 含有泛型的类
/*
定义一个含有泛型的类,模拟ArrayList集合:
泛型是一个未知的数据类型,当我们不确定什么数据类型的时候,可以使用泛型。
泛型可以接受任意的数据类型,可以是Integer、String、Student... 但必须是同一数据类型!
创建对象的时候确定泛型的数据!
*/
public class Test01GenericClass {
public static void main(String[] args) {
GenericClass gc = new GenericClass();
gc.setName("只能是字符串");
//创建对象的时候确定泛型的数据类型
GenericClass<Integer> gc2 = new GenericClass<>();
gc2.setName(206);
System.out.println(gc2.getName());
}
}
//定义和使用含有泛型的类:
class GenericClass <E> {
private E name;
public E getName(){
return name;
}
public void setName(E name){
this.name = name;
}
}
3.3.2 含有泛型的方法
/*
定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间
格式:
修饰符 <E> 返回值类型 方法名(参数列表(使用泛型)){
方法体;
}
含有泛型的方法,在调用方法时确定泛型的数据类型!
传递什么类型的参数,泛型就是什么类型
*/
public class GenericMethod {
public static void main(String[] args) {
demo1("KD");
demo1(12);
demo1(true);
demo1('X');
}
public static <E> void demo1(E e){
System.out.println(e + "-Collection.Generic");
}
}
3.3.3 含有泛型的接口
3.4 泛型通配符
- 代表任意的数据类型
-
基本使用
①不能创建对象使用
②只能作为方法的参数
public class Demo3Generic { public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<>(); list1.add("KD"); list1.add("PG"); ArrayList<Integer> list2 = new ArrayList<>(); list2.add(35); list2.add(13); printArray(list1); System.out.println("--------------"); printArray(list2); } /* 定义一个方法,能遍历所有数据类型的ArrayList集合 这时候我们不知道ArrayList集合是什么数据类型,可以用泛型的通配符?来接收数据类型 注意: 泛型是没有继承概念的! */ public static void printArray(ArrayList<?> list){ //使用for-each循环遍历集合 for(Object obj:list){ System.out.println(obj); } } }
-
高级使用<受限泛型>
①泛型上限:? extends E 代表使用的泛型只能是E类型的子类/本身
②泛型下限:? super E 代表使用的泛型只能是E类型的父类/本身
public class Demo4Generic { public static void main(String[] args) { Collection<Integer> cl1 = new ArrayList<>(); Collection<String> cl2 = new ArrayList<>(); Collection<Number> cl3 = new ArrayList<>(); Collection<Object> cl4 = new ArrayList<>(); getElement1(cl1); //getElement1(cl2); //报错! getElement1(cl3); //getElement1(cl4); //报错! //getElement2(cl1); //报错! //getElement2(cl2); //报错! getElement2(cl3); getElement2(cl4); } //泛型的上限:此时的泛型?,必须是Number类型或Number类型的子类 public static void getElement1(Collection<? extends Number> coll) {} //范兴德下限:此时的泛型?,必须是Number类型或Number类型的父类 public static void getElement2(Collection<? super Number> coll) {} }
四、数据结构
4.1 栈和队列:
4.2 数组:
4.3 链表:
4.4树:
五、List集合
5.1 List接口的特点:
-
有序的集合,存储元素和取出元素的顺序是一致的(存储123 取出123)
-
允许存储重复的元素
-
有索引,包含了一些带索引的方法
-
带索引的方法(特有):
public void add(int index, E e); 将指定的元素,添加到该集合中指定的位置上。
public E get(int index); 返回集合中指定位置的元素
public E remove(int index); 移除列表中指定位置的元素,返回的是被移出的元素
public E set(int index, E e); 用指定元素替换集合中指定位置的元素,返回值为更新前的元素
注意:操作索引的时候,一定要防止索引越界异常!IndexOutOfBoundsException!
5.2 List接口的实现类
5.2.1 ArrayList
- 底层是一个数组,查询快,增删慢;不同步,多线程
5.2.2 LinkedList
-
底层是一个链表,查询慢,增删快;不同步,多线程
-
特有方法:
public void addFirst(E e); 将指定元素插入到此列表的开头
public void addLast(E e); 将指定元素添加到此列表的结尾
public void push(E e); 将元素推入此列表所表示的堆栈(相当于addFirst( ))
public E getFirst(); 返回此列表的第一个元素
public E getLast(); 返回此列表的最后一个元素
public boolean isEmpty(); 判断列表是否为空,如果集合不包含元素,返回true
public E removeFirst(); 移除并返回此列表的第一个元素
public E removeLast(); 移除并返回此列表的最后一个元素
public E pop(); 从此列表所处的堆栈中弹出一个元素(相当于removeFirst( ))
5.2.3 Vector
- 所有单列集合的祖先,底层是一个数组,单线程,同步。(了解就行)
六、Set集合
6.1 Set接口的特点:
- 不允许存储重复的元素
- 没有索引,也没有带索引的方法,也不能用普通的for循环遍历
6.2 HashSet类
6.2.1 特点:
- 不允许存储重复的元素
- 没有索引,也没有带索引的方法,也不能用普通的for循环遍历
- 是一个无序的集合,存储元素和取出元素的顺序可能不一样
- 底层是一个哈希表(HashMap),查询速度非常快
6.2.2 哈希值、哈希冲突
-
哈希值:是一个十进制整数,由系统随机给出(就是对象的地址值,是计算机底层模拟出来的一个逻辑地址,并不是数据实际存储的物理地址。)
-
在Object方法中,可以获取对象的哈希值 int hashCode(); 返回该对象的哈希码值 hasCode方法的源码: public native int hashCode(); native: 代表该方法调用的是本地操作系统的方法
-
哈希冲突:两个元素不同,但哈希值相同。
6.2.3 哈希表(HashMap)
6.2.4 Set集合存储元素不重复的原理
前提:Set集合中的元素必须重写hashCode()和equals()
6.3 LinkedHashSet类
- 有序,不重复,无索引。
- 底层是一个哈希表 + 链表;多了一层链表(记录元素的存储顺序),保证元素有序。
七、Collections集合工具类
java.util.Collections 是集合工具类,用来对集合进行操作。
常用方法:
public static <T> boolean addAll(Collection<T> c, T... elements); 往集合中添加一些元素
public static void shuffle(List<?> list); 打乱集合顺序
public static <T> void sort(List<T> list); 将集合中元素按照默认规则排序(默认是升序)
public static <T> void sort(List<?> list,Comparator<? super T>); 将集合中元素按照指定规则排序
/*
public static <T> void sort(List<T> list); 将集合中元素按照默认规则排序(默认是升序)
注意:
sort(List<T> list)使用前提:
被排序的集合里边存储的元素,必须实现Comparable,重写接口中的compareTo()定义排序的规则
Comparable接口的排序规则:自己(this) - 参数:升序
*/
public class Demo2Sort {
public static void main(String[] args) {
/*
注意:
Integer、String类均实现了Comparable接口,且重写了其中的compareTo()
因此,能够调用Collections.sort()并按照它们的规则进行排序
*/
ArrayList<Integer> list01 = new ArrayList<>();
//往集合中添加元素
Collections.addAll(list01,2,8,6);
System.out.println(list01);
//打乱集合
Collections.shuffle(list01);
System.out.println(list01);
//排序(默认是升序)
Collections.sort(list01);
System.out.println(list01);
ArrayList<String> list02 = new ArrayList<>();
Collections.addAll(list02,"a","c","b");
System.out.println(list02); //[a, c, b]
Collections.sort(list02);
System.out.println(list02); //[a, b, c]
ArrayList<Players> list03 = new ArrayList<>();
Players p1 = new Players("杜兰特",35);
Players p2 = new Players("詹姆斯",23);
Players p3 = new Players("保罗·乔治",13);
Collections.addAll(list03,p1,p2,p3);
System.out.println(list03); //[Players{name= 杜兰特,numbers= 35}, Players{name= 詹姆斯,numbers= 23}, Players{name= 保罗·乔治,numbers= 13}]
//Collections.sort(list03); //没有实现Comparable接口时会报错! Players类必须实现Comparable接口,重写其中的compareTo()
Collections.sort(list03);
System.out.println(list03); //[Players{name= 保罗·乔治,numbers= 13}, Players{name= 詹姆斯,numbers= 23}, Players{name= 杜兰特,numbers= 35}]
}
}
class Players implements Comparable<Players>{
private String name;
private int numbers;
public Players(){
}
public Players(String name,int numbers){
this.name = name;
this.numbers = numbers;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getNumbers(){
return numbers;
}
public void setNumbers(int numbers){
this.numbers = numbers;
}
public String toString(){
return "Players{name= " + name + ",numbers= " + numbers + "}";
}
//重写compareTo()
public int compareTo(Players p){
//自定义比较的规则:比较两个人的球衣号
return this.numbers - p.numbers; //球衣号码升序
}
}
/*
public static <T> void sort(List<?> list,Comparator<? super T>); 将集合中元素按照指定规则排序
*/
public class Demo3Sort {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,3,2);
System.out.println(list); //[1, 3, 2]
//匿名内部类
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//重写比较的规则:
return o1-o2; //升序
// return o2-o1; //降序
}
});
System.out.println(list); //[1, 2, 3]
ArrayList<Stars> list1 = new ArrayList<>();
Stars s1 = new Stars("陈乔恩",28);
Stars s2 = new Stars("刘诗诗",26);
Stars s3 = new Stars("z汤唯",22);
Stars s4 = new Stars("q汤唯",22);
Collections.addAll(list1,s1,s2,s3,s4);
System.out.println(list1);
Collections.sort(list1, new Comparator<Stars>() {
@Override
public int compare(Stars o1, Stars o2) {
//按照年龄升序排序:
return o1.getAge()-o2.getAge();
}
});
/* 扩展了解:
Collections.sort(list1, new Comparator<Stars>() {
@Override
public int compare(Stars o1, Stars o2) {
int result = o1.getAge() - o2.getAge();
if(result == 0){
//如果年龄相同,按姓名第一个字母排序
return o1.getName().charAt(0) - o2.getName().charAt(0); //升序
}
return result;
}
});
*/
System.out.println(list1);
}
}
class Stars {
private String name;
private int age;
public Stars() {
}
public Stars(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Stars{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
八、Map(双列集合)
8.1 Map集合的特点
java.util.Map<K,V>接口:
- 是一个双列集合,一个元素包含两个值(K:键,V:值)
- Map集合中的元素,key和value的数据结构可以相同,也可以不同。
- Map集合中的元素,key是唯一的,不允许重复,value可以重复。
- Map集合中的元素,键和值是一一对应的,一个键对应一个值。
8.2 Map接口常用子类
8.2.1 HashMap<K,V>类
-
无序:存储元素和取出元素的顺序可能不一样
-
底层是一个哈希表:查询速度特别快
JDK1.8之前:数组 + 单向链表
JDK1.8之后:数组 + 单向链表/红黑树(链表长度超过8时):提高查询速度
-
不同步,多线程,不安全。
8.2.2 LinkedHashMap<K,V>类
java.util.LinkedHashMap extends HashMap<K,V>
- 有序:存储元素和取出元素的顺序是一样的
- 底层是一个哈希表 + 链表(保证迭代的顺序)
- 不同步,多线程,不安全。
8.3 Map接口常用方法
-
public V put(K key,V value); 把指定的键和指定的值添加到Map集合当中去
返回值:V
存储键值对的时候,key不重复,返回值V是null
存储键值对的时候,key重复,会用新的value替换Map中重复的value,返回被替换掉的value值
-
public V remove(Object key); 把指定的键 所对应的键值对元素 从Map集合中删除,返回被删除元素的值
返回值:V
key不存在,返回值V为null
key存在,V返回被删除元素的值
-
public boolean containsKey(Object key); 判断集合中是否包含指定的key
包含返回true
不包含返回false
-
public V get(Object key); 根据指定的键,在Map中获取对应的值
返回值:V
key不存在,V返回null
key存在,V返回对应的value值
8.4 遍历Map集合
8.4.1 ①:通过键找值的方式
Map集合中的方法:
public Set<k> keySet(); 返回此映射中包含的Set视图。
实现步骤:
a. 使用Map集合中的keySet()方法,把Map集合中所有的键取出来,存到Set集合中
b. 遍历Set集合,获取Map集合中的每一个key
c. 调用Map集合中的public V get(Object key),通过key找到value
public class Demo2KeySet {
public static void main(String[] args) {
//创建一个Map集合,多态:
Map<String,Integer> map = new HashMap<>();
//往map集合中添加元素: put
map.put("布鲁克林篮网",71113);
map.put("洛杉矶湖人",323);
map.put("洛杉矶快船",213);
//keySet(): 把map集合中速所有的键取出来,存到Set集合中
Set<String> set = map.keySet();
//遍历Set集合方式:①迭代器
//获取迭代器
Iterator<String> it = set.iterator();
//hasNext(): 判断还有没有下一个元素
while(it.hasNext()){
//获取该元素: key
String key = it.next();
//调用Map集合get(Object key)方法,通过键找到值
Integer v = map.get(key);
System.out.println(key + "=" + v);
}
System.out.println("---------------------------");
//for-each循环:遍历Set集合
for(String sKey:map.keySet()){
//调用Map集合get(Object key)方法,通过键找到值
System.out.println(sKey + "=" + map.get(sKey));
}
}
}
8.4.2 ②:通过内部接口Map.Entry<K,V>的Entry对象的方式
Map集合中有一个内部接口:Map.Entry<K,V>;
作用:在创建Map集合的时候,会自动生成一个Entry对象,用来记录键与值(键值对对象,键与值的映射关系)。
实现步骤:
a. 使用Map集合中的entrySet(),把生成的Entry对象存放在Set集合中;
b. 遍历Set集合,获取每一个Entry对象;
c. 通过Entry对象来调用Map.Entry<K,V>接口中的两个方法:
getKey( ),getValue( ), 获取键和值。
public class Demo3EntrySet {
public static void main(String[] args) {
//创建一个Map集合,多态:
Map<String,String> map = new HashMap<>();
//添加元素:键值对。put();
map.put("吴奇隆","刘诗诗");
map.put("陈晓","陈妍希");
map.put("尔康","紫薇");
//entrySet(); 把Entry对象(键值对对象)存放在Set集合中
Set<Map.Entry<String, String>> entries = map.entrySet();
//遍历Set集合:for-each循环
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey(); //获取键
String value = entry.getValue(); //获取值
System.out.println(key + "=" + value);
}
System.out.println("----------------");
//遍历Set集合:迭代器Iterator
Iterator<Map.Entry<String, String>> it = entries.iterator();
while(it.hasNext()){
Map.Entry<String, String> entry = it.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
}
}
8.5 HashMap存储自定义类型键值
Map集合保证键是唯一的,不能重复:
作为key的元素,必须重写Object类中的hashCode()和equals()!
8.6 Hashtable<K,V>类
java.util.Hashtable<K,V>集合 implements Map<K,V>接口
Hashtable底层是一个哈希表,是一个安全的单线程集合,速度慢。
HashMap:底层是一个哈希表,是一个不安全的多线程集合,速度快。
Hashtable集合,不能存储null值,null键
HashMap(之前学的所有集合):可以存储null值,null键
Hashtable和Vector集合一样,在JDK1.2版本之后被更先进的集合(HashMap,ArrayList)取代了
Hashtable的子类Properties集合依然活跃在历史的舞台,并且是唯一一个和IO流相关的集合!
Vector:最早期的单列集合;Hashtable:最早期的双列集合
8.7 JDK9新特性
JDK9新特性:
List接口,Set接口,Map接口:里面增加了一个静态的方法of();可以给集合一次性添加多个元素
static <E> List<E> of(E...elements);
使用前提:
当集合中存储的元素个数已经确定了,不再改变时使用。
注意:
1.of方法只适用于List、Set、Map接口,不适用于接口的实现类
2.of方法的返回值是一个不能改变的集合,集合不能再使用add、put方法添加元素,会抛出异常
3.Set、Map接口在调用of方法时,不能有重复的元素,否则会抛出异常。
UnsupportedOperationException! 不支持操作异常!