集合
一、集合框架
1.1概念
- 存放对象的容器,用于存放对象,定义了对多个对象进行操作的常用方法。可以实现类似数组的功能
- 数组缺点:
- 数组定长,一旦带你关于不可被改变
- 数组没有方法
- 数组只能存放相同类型的数据
- 集合特点:
- 长度可变
- 集合中丰富的操作元素的方法
- 集合中只能存放引用数据类型的数据
- 集合的分类
- 单列集合(集合中一个元素只能保存一个数据)Collection
- 双列集合(集合中一个元素只能保存两个数据)Map
1.2集合框架体系结构
Collection
体系集合
特点:代表一组任意类型的对象。
常用方法:
add(Object obj)
添加一个对象addAll(Collection c)
将一共集合中的所有对象添加到此集合中clear()
清空此集合中的所有对象contains(Object o)
检查此集合中是否包含o
对象equals(Object o)
比较此集合是否与指定对象相等isEmpty
判断此集合是否为空remove(Object o)
在此集合中移除o
对象size()
返回此集合中的元素个数toArray()
将此集合转换成数组
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class CollectionDemo {
public static void main(String[] args) {
//创建Collection对象
Collection coll=new ArrayList();
//add向集合中添加元素
coll.add("aaa");
coll.add(12);
coll.add("bbb");
System.out.println(coll);
/*
//清空集合所有元素
coll.clear();
System.out.println(coll);
*/
//判断集合中是否有指定的元素
System.out.println(coll.contains("ccc"));
//判断集合否为空
System.out.println(coll.isEmpty());
//删除集合中的指定元素
System.out.println(coll.remove(12));
//返回集合中有效元素个数
System.out.println(coll.size());
//将集合转换成一个数组
Object[] obj=coll.toArray();
//数组工具类 将数组转换成字符串
System.out.println(Arrays.toString(obj));
//数组的工具类 将数组转换成集合
int[] arr = {1,2,3};
List<int[]> ints = Arrays.asList(arr);
Collection coll1 = new ArrayList();
coll1.add("a");
coll1.add("b");
coll1.add("c");
Collection coll2 = new ArrayList();
coll2.add("1");
coll2.add("2");
coll2.add("3");
//add方法添加会把所有元素当作一个整体元素添加
//coll1.add(coll2);
//addAll 将一个的集合的每一个元素添加到指定元素
coll1.addAll(coll2);
System.out.println(coll1);
//containsAll 判断集合中是否包含另一个集合元素
System.out.println(coll1.containsAll(coll2));
//删除集合中指定集合的元素
coll1.removeAll(coll2);
System.out.println(coll1);
}
}
1.3集合遍历
- 使用迭代器遍历
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("a");
coll.add("b");
coll.add("c");
coll.add("e");
coll.add("f");
coll.add("g");
coll.add("h");
coll.add("i");
coll.add("j");
//创建Iterator对象
Iterator it = coll.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
使用迭代器需要注意的问题:
- 迭代器不能多次使用
- 迭代器不能再迭代过程中多次调用next()方法
- 迭代器在迭代过程中不要向集合中添加或移除元素
二、List
特点:
- 元素有序、且可重复的集合,集合中的每个元素都有其对应的顺序索引;
- 允许使用重复元素,可以通过索引来访问指定位置的集合元素;
- 默认按元素的添加顺序设置元素的索引。
相关实现类比较:
ArrayList
- 基于数组结构实现,使用在查询比较多的场合下;
- JDK1.2出现,运行效率高,线程不安全。
Vector
- 基于数组结构实现,使用在查询比较多的场合下;
- JDK1.0出现,运行效率低,线程安全。
LinkedList
- 基于链表结构实现,增删快,查询慢。
2.1ArrayList
基本使用
2.1.1基本操作
add(E e)
添加元素add(int index, E element)
添加元素到指定位置,ArrayList
元素下标从0
开始size()
获取ArrayList
长度get(int index)
获取指定位置的元素addAll(Collection<? extends E> c)
添加另外一个集合addAll(int index,Collection<? extends E> c)
添加另外一个集合到指定位置clear()
清空set(int index, E element)
设置某个位置的元素contains(Object o)
判断集合中是否包含某个元素remove(int index)
删除指定位置的元素remove(Object o)
删除特定元素indexOf(Object o)
返回元素的索引
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("aaa");
list.add("xxx");
list.add("bbb");
list.add("ccc");
System.out.println(list);
//list集合就是在Collection接口的基础上添加了一下带下标索引的方法
//向集合添加元素到指定位置
list.add(2,"zzz");
System.out.println(list);
//修改指定位置下标位置上的元素
list.set(2,"ooo");
System.out.println(list);
//删除指定下标位置上的元素信息
list.remove(1);
System.out.println(list);
//查找元素首次出现的索引
System.out.println(list.indexOf("aaa"));
}
}
2.1.2ArrayList
遍历
ArrayList
遍历的方式:
- 普通
for
循环 —while
循环 - 增强
for
循环 - 使用迭代器遍历
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("aaa");
list.add("xxx");
list.add("bbb");
list.add("ccc");
System.out.println(list);
//通过下标遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//通过增强for循环
/*
* for(遍历项的数据类型 遍历项:要遍历的集合和数组)
* {
* 打印语句
* }
* */
for (String Str : list) {
System.out.println(Str);
}
//通过迭代器遍历集合
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
2.2LinkedList
集合
特点:增删快遍历慢
2.2.1基本操作
add(E e)
将指定元素添加到此列表的结尾0add(int index, E element)
在此列表中指定的位置插入指定的元素addFirst(E e)
将指定元素插入此列表的开头addLast(E e)
将指定元素添加到此列表的结尾clear()
从此列表中移除所有元素offer(E e)
将指定元素添加到此列表的末尾(最后一个元素)peek()
获取但不移除此列表的头(第一个元素)poll()
获取并移除此列表的头(第一个元素)pop()
从此列表所表示的堆栈处弹出一个元素push(E e)
将元素推入此列表所表示的堆栈- size() 返回此列表的元素数
2.3VectorList
集合
基本操作与 ArrayList 集合一致
vector
和ArrayList
的区别
- Vector ArrayList 底层都是数组
- Vector 初始化是就创建了长度为10的数组 ArrayList
在第一次添加元素的时候进行扩容
- Vector线程安全
LinkedList与Vector和ArrayList区别
LinkedList增删快遍历慢 Vector和ArrayList遍历快增删慢
三、Set
Set
接口是Collection
的子接口,Set
接口没有提供额外的方法。
Set
集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set
集合中,则添加操作失败。
Set
判断两个对象是否相同不是使用==
运算符,而是根据equals()
方法。
Set
接口实现类比较:
HashSet
作为Set
接口的主要实现类,线程不安全的,可以存储null
值;LinkedHashSet
作为HashSet
的子类,遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet
效率高于HashSet
;TreeSet
可以按照添加对象的指定属性,进行排序。
3.1、HashSet
HashSet
是Set
接口的典型实现,大多数时候使用Set
集合时都使用这个实现类。
HashSet
按Hash
算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
HashSet
具有以下特点:
- 不能保证元素的排列顺序;
- 元素不能重复;
HashSet
不是线程安全的;- 集合元素可以是
null
。
HashSet
集合判断两个元素相等的标准:两个对象通过hashCode()
方法比较相等,并且两个对象的equals()
方法返回值也相等。
对于存放在Set
容器中的对象,对应的类一定要重写equals()
和hashCode()
方法,以实现对象相等规则。即:相等的对象必须具有相等的散列码。
3.2.1、Set
的无序性和不可重复性
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:保证添加的元素按照
equals()
判断时,不能返回true
。即:相同的元素只能添加一个。
//学生类
public class Student {
private String id;
private String name;
private String age;
//有参和无参构造方法
//get和set方法
//toString方法
//equals和hashCode方法
}
public class HsahSetDemo {
public static void main(String[] args) {
Student p1 = new Student("001", "Tom", 20);
Student p2 = new Student("002", "Bob", 21);
Student p3 = new Student("002", "Bob", 21);
Student p4 = new Student("003", "Smith", 22);
HashSet<Student> set = new HashSet<>();
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
/*
没有重写equals()和hashCode()方法,重复元素能够重复添加。
*/
for (Student s : set) {
System.out.println(s);
}
}
}
HashSet底层实现原理:HashMap
HashSet
中元素的添加过程:
-
当向
HashSet
集合中存入一个元素时,HashSet
会调用该对象的hashCode()
方法来得到该对象的hashCode()
值,然后根据hashCode()
值,通过某种散列函数决定该对象在HashSet
底层数组中的存储位置。- 如果此位置上没有其他元素,则添加成功;
- 如果此位置上有其他元素,则进行进一步比较。
-
比较两元素的
hashCode()
的值:-
如果两个元素的
hashCode()
值不相等,添加成功; -
如果两个元素的
hashCode()
值相等,会再继续调用equals()
方法,如果equals()
方法结果为true
,添加失败;如果为false
,那么会添加该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。 -
HashSet底层去重原理
- 首先:比较两个对象的Hash值,如果Hash值不同则认为两个对象不重复
- 如果Hash相同,进而比较equals,如果equals,返回true,则认为两个对象重复,否则认为还是不重复
HashSet
底层结构:数组+链表如果两个元素的
equals()
方法返回true
,但它们的hashCode()
返回值不相等HashSet
将会把它们存储在不同的位置,但依然可以添加成功 -
3.2TreeSet
TreeSet
可以确保集合元素处于排序状态。
TreeSet
底层实现原理:TreeMap
- 去重原理:
- 根据树没有存放的依据,所以需要借助CompareTo方法,进行比较
- 去重的根据是通过CompareTo的返回值进行判断,如果返回值为0证明相等,就证明重复 去重
- CompareTo返回值 大于0升序 小于0降序
public class TreeSetDemo {
public static void main(String[] args) {
//默认使用自然排序在TreeSet中排序
TreeSet<String> set = new TreeSet<>();
set.add("123");
set.add("abc");
set.add("aBc");
set.add("AA");
set.add("EE");
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------------------------");
TreeSet<String> set1 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return -o1.compareTo(o2);
}
});
set1.add("123");
set1.add("abc");
set1.add("aBc");
set1.add("AA");
set1.add("EE");
for (String s : set1) {
System.out.println(s);
}
}
}
3.5Collections
常用方法
Collections
是一个操作List
、Set
和Map
等集合的工具类。
Collections
中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
排序操作(均为static
方法):
-
reverse(List<?> list)
:反转List
中元素的顺序; -
shuffle(List<?> list)
:对List
集合元素进行随机排序; -
sort(List<T> list)
:根据元素的自然顺序对指定List
集合元素按升序排序; -
sort(List<T> list, Comparator<? super T> c)
:根据指定的Comparator
产生的顺序对List
集合元素进行定制排序; -
swap(List<?> list, int i, int j)
:将指定List
集合中的i
处元素和j
处元素进行交换。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//将集合元素进行翻转
Collections.reverse(list);
System.out.println(list);//ccc, bbb, aaa
//将集合中的顺序随机打乱
Collections.shuffle(list);
System.out.println(list);
}
}
四、Map
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pywmPSqf-1668416522083)(D:\Folder\笔记\img\Map.png)]
常用方法:
-
添加、删除、修改操作
put(Object key,Object value)
:将指定key-value
添加到(或修改)当前map
对象中putAll(Map m)
:将m
中的所有key-value
对存放到当前map
中remove(Object key)
:移除指定key
的key-value
对,并返回value
clear()
:清空当前Map
中的所有数据
-
查询操作
get(Object key)
:获取指定key
对应的value
containsKey(Object key)
:是否包含指定的key
containsValue(Object value)
:是否包含指定的value
size()
:返回map
中key-value
对的个数isEmpty()
:判断当前map
是否为空equals(Object obj)
:判断当前map
和参数对象obj
是否相等
-
其他操作
keySet()
:返回所有key
构成的Set
集合values()
:返回所有value
构成的Collection
集合entrySet()
:返回所有key-value
对构成的Set
集合
4.1、HashMap
HashMap
是Map
接口使用频率最高的实现类。
- 允许使用
null
键和null
值,与HashSet
一样,不保证映射的顺序; - 所有的
key
构成的集合是Set
:无序的、不可重复的。所以,key
所在的类要重写:equals()
和hashCode()
//Map基本操作
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
//添加元素
map.put("1", "zs");
map.put("2111", "ls");
map.put("32211", "ww");
System.out.println(map);
System.out.println("---------------------------");
//添加另一个map
HashMap<String, String> map1 = new HashMap<>();
map1.put("4122", "tom");
map1.put("533", "bob");
map.putAll(map1);
System.out.println(map);
System.out.println("---------------------------");
//根据指定的Key移除指定的键值对
map.remove("533");
System.out.println(map);
//通过key获取对应的value
System.out.println(map.get("1"));
//判断是否包含对应的key
System.out.println(map.containsKey("1"));
//判断是否包含对应的value
System.out.println(map.containsValue("zs"));
//返回键值对个数
System.out.println(map.size());
//判断map是否为空
System.out.println(map.isEmpty());
//清空map
map.clear();
System.out.println(map);
}
}
遍历:
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
//添加元素
map.put("1", "zs");
map.put("2111", "ls");
map.put("32211", "ww");
/**
* 遍历map所有的key
* 1.获取所有的key
* 2.遍历
*/
Set<String> set = map.keySet();
for (String s : set) {
System.out.println(s);
}
/**
* 遍历map所有的value
* 1.获取所有的value
* 2.遍历
*/
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
/**
* 遍历map所有的键值对 方式1
* 1.获取所有的key
* 2.遍历key
* 3.遍历key的过程中获取key对应的value
*/
Set<String> keys = map.keySet();
for (String k : keys) {
String v = map.get(k);
System.out.println(k + ":" + v);
}
/**
* 遍历map所有的键值对 方式2
* 1.获取Entry
* 2.遍历所有的Entry
*/
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ":" + value);
}
}
}
案例演示:
//创建一个城市的map集合(山东 青岛 济南 潍坊)
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class Demo01 {
public static void main(String[] args) {
HashMap<String, List<String >> map = new HashMap<>();
ArrayList<String> list = new ArrayList<>();
list.add("青岛");
list.add("济南");
list.add("淄博");
map.put("山东",list);
System.out.println(map.get("山东"));
}
}
put添加流程
- 通过扰乱算法,计算一个hash值(目的避免计算的下标重复)
- 第一次添加元素会扩容为16的Node数组
- 通过下标寻址算法hash值与数组长度-1做运算 计算出一个下标
- 当前下标位置上为null,直接创建一个Node节点,并放到下标位置
- 当前位置不为null
- 判断添加元素是否与之前的元素相等(hashcode和equals)如果相等则覆盖,并返回旧值
- 如果当前元素是红黑树,那么元素直接添加到红黑树上
- 如果当前元素上位链表,会添加到链表尾部(尾插法)
- 链表会树化的条件:
- 链表长度大于8
- 数组长度大于64
- 链表会树化的条件:
4.2Hashtable
Hashtable与HashMap用法基本一致
- HashMap与Hashtable区别
- HashMap K V 可以Null Hashtable K V不可以为Null
- HashMap线程不安全 Hashtable 线程安全(以后就算考虑线程安全安全 也不会用这个类 用ConcurrentHashMap)
五、泛型
4.1概述
泛型可以理解为标签
- 中药店,每个抽屉外面贴着标签;
- 超市购物架上很多瓶子`,每个瓶子装的是什么,有标签。
集合在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5
之前只能把元素类型设计为Object
,JDK1.5
之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>
,List<E>
,ArrayList<E>
这个<E>
就是类型参数,即泛型。
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
从JDK1.5
以后,Java
引入了参数化类型(Parameterized type)
的概念,允许我们在创建集合时再指定集合元素的类型,正如:List<String>
,这表明该List
只能保存字符串类型的对象。
JDK1.5
改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参
4.2为什么要有泛型
- 解决元素存储的安全性问题
- 解决获取数据元素时,需要类型强制转换的问题
集合中没有泛型时:
String
类型对象添加到集合中,在集合中当成Object
类型对象- 从集合中获取,读取到的对象只能被当成
Object
类的对象读取 - 如果我们希望将读取到的对象当成
String
类的对象使用,需要向下转型 - 转换过程繁琐,而且可能出现
ClassCastException
异常
集合中有泛型时:
String
类型对象添加到集合中,在集合中当成String
类型对象;- 从集合中获取,读取到的对象被当成
String
类的对象读取,不需要向下转型。
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException
异常。同时,代码更加简洁、健壮
4.3泛型类及泛型接口
-
泛型的声明:
interface List<T>
和class GenTest<K,V>
其中,T,K,V
不代表值,而是表示类型。这里使用任意字母都可以。常用T
表示,是Type
的缩写
//定义带泛型的类
public class MyClass1<T> {
public void m1(T t) {
}
}
//定义泛型接口
public interface MyInterface<T> {
void m1(T t);
}
//定义实现泛型接口的类
public class MyClass1<T> implements MyInterface<T> {
@Override
public void m1(T t) {
}
}
//定义继承泛型类的类
public class MyClass2<T> extends MyClass1<T> {
@Override
public void m1(T t) {
super.m1(t);
}
}
public class Demo {
public static void main(String[] args) {
MyClass1<String> myClass1 = new MyClass1<>();
myClass1.m1("aaa");
MyClass2<String> myClass2 = new MyClass2<>();
myClass2.m1("bbb");
MyClass3<String> myClass3 = new MyClass3<>();
myClass3.m1("hello");
}
}
4.4泛型方法
-
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型
-
为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新
new
一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活
格式:
[访问权限] 返回类型 <泛型> 方法名([泛型标识 参数名称]) 抛出的异常
//定义带泛型的类 public class MyClass<T> { public void m1(T t) { } //泛型方法 public void <E> m2(E e) { } /* public int add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } public float add(float a, float b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } public double add(double a, double b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } */ //如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法 public <T extends Number> double add(T a, T b) { System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue())); return a.doubleValue() + b.doubleValue(); } } public class Demo { public static void main(String[] args) { MyClass1<String> myClass1 = new MyClass1<>(); myClass1.m1("aaa"); Person p = new Person(); p.setId("001"); p.setName("zs"); p.setAge(20); //调用泛型方法 myClass.m2(p); //调用被static修饰的泛型方法 System.out.println(test(123)); } //static的泛型方法 public static <T> T test(T t) { return t; } }
4.5通配符
<?>
表示不确定的数据类型
<? extends 类型>
使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
<? super 类型>
使用时指定的类型不能小于操作的类,即>=
举例:
<? extends Person>
定义泛型上边界(无穷小 ,Person]只允许泛型为Person
及Person
子类的引用调用
doubleValue()));
return a.doubleValue() + b.doubleValue(); }
}
public class Demo {
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1<>();
myClass1.m1(“aaa”);Person p = new Person(); p.setId("001"); p.setName("zs"); p.setAge(20); //调用泛型方法 myClass.m2(p); //调用被static修饰的泛型方法 System.out.println(test(123)); } //static的泛型方法 public static <T> T test(T t) { return t; }
}
4.5通配符
<?>
表示不确定的数据类型
<? extends 类型>
使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
<? super 类型>
使用时指定的类型不能小于操作的类,即>=
举例:
<? extends Person>
定义泛型上边界(无穷小 ,Person]只允许泛型为Person
及Person
子类的引用调用<? super Person>
定义泛型下边界[Person, 无穷大)只允许泛型为Person
及Person
父类的引用调用