集合类库
1.1集合的概述
1.1.1集合的由来
- 当需要在java程序中记录单个数据内容时,则声明一个变量;
- 当需要在Java程序中记录多个类型相同的数据内容时,声明一个一维数组;
- 当需要在Java程序中记录多个类型不同的数据内容时,则创建一个对象;
- 当需要在Java程序中记录多个类型相同的对象数据时,创建一个对象数组;
- 当需要在java程序中记录多个类型不同的对象数据时,则准备一个集合;
1.1.2集合的框架结构
- java中集合框架顶层框架是:java.util.Collection集合和java.util.Map集合;
- 其中Collection集合中存取元素的基本单位是:单个元素;
- 其中Map集合中存取元素的基本单位是:单对元素;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NztcDlF7-1632296285389)(http://bed.thunisoft.com:9000/ibed/2021/06/23/CXdirlje4.png)]
1.2Collection集合
1.2.1基本概念
java.util.Collection接口是List接口、Queue接口以及Set接口的父接口,因此该接口里定义的方法既可以用于List集合,也可以用于Queue集合和Set集合。
1.2.2常用的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DwzJtUG7-1632296285391)(http://bed.thunisoft.com:9000/ibed/2021/06/26/CYxdyzXBR.png)]
1.add与addAlll方法
//1.准备一个Collection集合并打印
// Collection c1=new Collection(); //接口不能实例化,也就是不能创建对象
Collection c1=new ArrayList(); //接口类型的引用指向实现类的对象,形成了多态
//自动调用toString方法,调用ArrayList类中的toString方法,默认打印格式为:[元素值1,元素值2,...]
System.out.println("集合中的元素有:"+c1); //集合中的元素有:[],[]中括号中内容为空
System.out.println("--------------------");
//2.向集合中添加单个元素并打印
boolean b1=c1.add(new java.lang.String("one"));
System.out.println("b1="+b1); //true
System.out.println("集合中的元素有:"+c1); //[one]
System.out.println("--------------------");
b1=c1.add(Integer.valueOf(2));
System.out.println("b1="+b1); //true
System.out.println("集合中的元素有:"+c1); //[one,2]
System.out.println("--------------------");
b1=c1.add(new Person("包简嘉",30));
System.out.println("b1="+b1); //true
//打印集合中的所有元素时,本质上就是打印集合中的每个对象,也就是让每个对象调用类的toString方法
System.out.println("集合中的元素有:"+c1); //[one, 2, Person{name='包简嘉', age=30}]
//3.向集合中添加多个元素并打印
Collection c2=new ArrayList();
c2.add("three"); //常亮池
c2.add(4); //自动装箱机制
System.out.println("c2="+c2); //[three,4]
//将c2中的所有元素全部添加到集合c1中
//b1=c1.addAll(c2); //c1=[one, 2, Person{name='包简嘉', age=30}, three, 4]
//表示将集合c2整体看作一个元素添加到集合c1中
b1=c1.add(c2); //c1=[one, 2, Person{name='包简嘉', age=30}, [three, 4]]
System.out.println("b1="+b1); //true
System.out.println("c1="+c1);
Person类:
import java.util.Objects;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o){ return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.contains与containsAll方法
//4.判断集合中是否包含参数指定的单个元素
b1=c1.contains(new String("one"));
System.out.println("b1="+b1); //true
//contains方法的工作原理是:Objects.equals(o,e),其中o代表contains方法的形式参数,e代表集合中的每个元素
//也就是说contains的工作原理就是拿着参数对象与集合中已有的元素依次进行比较。而该方法equals的工作原理如下:
/*
public static boolean equals(Object a,Object b){ 其中a代表person对象,b代表集合中已有的对象
return (a==b)||(a!=null&&a.equals(b));
元素包含的第一种方式就是:Person对象与集合中已有对象的地址相同
第二种方式就是:Person对象不为空且Person对象调用equals方法与集合中已有元素相等
当person类中没有重写equals方法时,则调用object类中继承下来的equals方法,比较两个对象的地址:false
当Person类中重写equals方法后,则调用重写以后的版本,比较两个对象的内容:true
}
*/
b1=c1.contains(new Person("包简嘉",30));
System.out.println("b1="+b1);
System.out.println("分隔符----------------------");
System.out.println(c1);
//5.判断当前集合中是否包含参数指定集合的所有元素
Collection c3=new ArrayList();
c3.add(2);
//判断集合c1中是否包含集合c3中的所有元素,只有集合c3中的所有元素都在集合c1中出现,才会返回true,否则都是false
b1=c1.containsAll(c3); //[one, 2, Person{name='包简嘉', age=30}, [three, 4]]
System.out.println(b1); //true
c3.add("five");
b1=c1.containsAll(c3);
System.out.println(b1); //false
//笔试考点
System.out.println("笔试考点分隔符");
System.out.println("c2="+c2); //c2=[three, 4]
b1=c1.containsAll(c2);
System.out.println("b1="+b1); //false
//判断集合c1中是否拥有集合c2这个整体为单位的元素
b1=c1.contains(c2);
System.out.println("b1="+b1); //true
3.retainAll方法(交集)
当前集合发生改变则为true
//6.计算两个集合的交集并保留到当前集合中
System.out.println("c2="+c2); //c2=[three, 4]
System.out.println("c3="+c3); //c3=[2, five]
//也就是让集合的自己和自己取交集
b1=c2.retainAll(c2);
System.out.println("b1="+b1); //false 表示当前的集合中元素并没有发生变化
//计算c2和c3的交集并保留到集合c2中,取代集合c2原有的数据
b1=c2.retainAll(c3);
System.out.println("b1="+b1); //true 当前集合的元素发生了改变
System.out.println("c2="+c2); //c2=[]
System.out.println("c3="+c3); //c3=[2, five]
4.remove方法
//7.实现集合中单个元素和所有元素的删除操作
System.out.println("c1="+c1); //c1=[one, 2, Person{name='包简嘉', age=30}, [three, 4]]
//删除参数指定的单个元素
b1=c1.remove(1);
System.out.println("b1="+b1); //false
b1=c1.remove(2);
System.out.println("b1="+b1); //true
System.out.println("c1="+c1); //c1=[one, Person{name='包简嘉', age=30}, [three, 4]]
//remove方法的工作原理:Object.equals(o,e)
b1=c1.remove(new Person("包简嘉",30));
System.out.println("c1="+c1); //c1=[one, [three, 4]]
System.out.println("分隔符----------------------");
//8.实现集合中所有元素的删除操作
System.out.println("c3="+c3); //c3=[2, five]
//从集合c1中删除集合c3中的所有元素,本质上就是一个一个元素进行删除,有元素则删除,否则不删除
b1=c1.removeAll(c3);
System.out.println("b1="+b1); //false
//笔试考点
System.out.println("remove笔试考点分隔符----------------------");
b1=c1.remove(c3);
System.out.println("b1="+b1); //false
System.out.println("c1="+c1); //c1=[one, [three, 4]]
4.其他方法
//9.实现集合中其他方法的测试 ctrl+n直接搜索并打开类的源码 使用ctrl+f12搜索类中的方法
System.out.println("集合中元素的个数:"+c1.size()); //2
System.out.println(0== c1.size()?"集合已经空了":"集合没空呢"); //集合没空呢
System.out.println(c1.isEmpty()?"集合已经空了":"集合没空呢"); //集合没空呢
//清空集合中的所有元素
c1.clear();
System.out.println("集合中元素的个数:"+c1.size()); //0
System.out.println(0== c1.size()?"集合已经空了":"集合没空呢"); //集合已经空了
System.out.println(c1.isEmpty()?"集合已经空了":"集合没空呢"); //集合已经空了
//准备两个集合并判断是否相等
Collection c4=new ArrayList();
c4.add(1);
c4.add(2);
Collection c5=new ArrayList();
c5.add(1);
c5.add(2);
System.out.println(c4.equals(c5)); //true
//10.实现集合和数组类型之间的转换 通常任务:集合是用于取代数组的结构
//实现集合向数组类型的转换
Object[] objects=c5.toArray();
System.out.println("数组中的元素有:"+ Arrays.toString(objects));//数组中的元素有:[1, 2]
//实现数组类型到集合类型的转换
List<Object> objects1 = Arrays.asList(objects);
Collection objects2 = Arrays.asList(objects);
System.out.println(objects1); //[1, 2]
System.out.println(objects2); //[1, 2]
1.3 Iterator接口
1.3.1基本概念
- java.util.Iterator接口主要用于描述迭代器对象,可以遍历Collection集合中的所有元素。
- java.util.Collection接口继承Iterator接口,因此所有实现Collection接口的实现类都可以使用该迭代器对象。
1.3.2常用的方法
方法声明 | 功能介绍 |
---|---|
boolean hasNext() | 判断集合中是否有可以迭代/访问的元素 |
E next() | 用于取出一个元素并指向下一个元素 |
void remove() | 用于删除访问到的最后一个元素 |
1.hasNext与next方法
//1.准备一个Collection集合并放入元素后打印
Collection c1 = new ArrayList();
c1.add("one");
c1.add(2);
c1.add(new Person("包简嘉",23));
//遍历方式一:自动调用toString方法 String类型的整体
System.out.println("c1="+c1); //c1=[one, 2, Person{name='包简嘉', age=23}]
System.out.println("--------------------");
/*
//遍历方式二:使用迭代器来遍历集合中的所有元素:更加灵活
//2.1获取集合中的迭代器对象
Iterator iterator1=c1.iterator(); //true
//2.2判断是否有元素可以访问
System.out.println(iterator1.hasNext()); //获取到的元素是:one
//2.3取出一个元素并指向下一个
System.out.println("获取到的元素是:"+iterator1.next()); //true
System.out.println(iterator1.hasNext());
System.out.println("获取到的元素是:"+iterator1.next()); //获取到的元素是:2
System.out.println(iterator1.hasNext()); //true
System.out.println("获取到的元素是:"+iterator1.next()); //获取到的元素是:Person{name='包简嘉', age=23}
System.out.println(iterator1.hasNext()); //false
System.out.println("获取到的元素是:"+iterator1.next()); //编译ok,运行发生NoSuchElementException没有这样的元素异常
*/
Iterator iterator1=c1.iterator();
while(iterator1.hasNext()){
System.out.println("获取到的元素是:"+iterator1.next());
//获取到的元素是:one
//获取到的元素是:2
//获取到的元素是:Person{name='包简嘉', age=23}
}
//由于上个循环已经使得迭代器走到了最后,因此需要重置迭代器
iterator1=c1.iterator();
//3.使用迭代器来模拟toString方法的打印效果
StringBuilder sb1=new StringBuilder();
sb1.append("[");
while(iterator1.hasNext()){
//当获取的元素是最后一个元素时,则拼接元素加中括号
//否则拼接元素加逗号加空格
Object obj=iterator1.next();
if(iterator1.hasNext()) {
sb1.append(obj).append(",").append(" ");
}
else{
sb1.append(obj).append("]");
}
}
System.out.println("c1="+sb1); //c1=[one, 2, Person{name='包简嘉', age=23}]
//4.不断地去获取集合中的元素并判断,当元素值为"one"时删除该元素
iterator1=c1.iterator();
while(iterator1.hasNext()){
Object obj=iterator1.next();
if ("one".equals(obj)){
iterator1.remove();//使用迭代器remove方法删除元素没问题
//c1.remove(obj); //使用集合的remove方法编译ok,运行发生ConcurrentModificationException并发修改异常
}
}
System.out.println("删除后集合中的元素有:"+c1); //删除后集合中的元素有:[2, Person{name='包简嘉', age=23}]
1.4 for each循环
1.4.1 基本概念
Java5推出了增强型for循环语句,可以应用数组和集合的遍历
是经典迭代的“简化版”
1.4.2 语法格式
for(元素类型 变量名:数组/集合名称){
循环体;
}
1.4.3 执行流程
不断从数组/集合中取出一个元素赋值给变量名并执行循环体,直到取完所有元素为止。
1.4.4 代码例子
//5.使用foreach结构实现集合和数组中元素的遍历,代码简单且方法灵活
//由调试源码可知:该方式确实是迭代器的简化版
for (Object obj:c1) {
System.out.println("取出来的元素是:"+obj);
//取出来的元素是:2
//取出来的元素是:Person{name='包简嘉', age=23}
}
int[] arr=new int[]{1,2,3,4,5,6};
for (int i:arr){
System.out.println("i="+i);
i=66; //修改局部变量i的值,并不是修改数组中元素的数值
}
/*i=1
i=2
i=3
i=4
i=5
i=6
*/
1.5 List集合
1.5.1 基本概念
- java.util.List集合是Collection集合的子集合,该集合中允许有重复的元素并且有先后放入次序。
- 该集合的主要实现类有:ArrayList类、LinkedList类、Stack类、Vector类。
- 其中ArrayList类的底层是采用动态数组进行数据管理的,支持下标访问,增删元素不方便。
- 其中LinkedList类的底层是采用双向链表进行数据管理的,访问不方便,增删元素方便。
- 可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下可以忽略这个差别。
- 其中Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种具有后进先出特征的数据结构,叫做栈(last in first out LIFO)。
- 其中Vector类的底层是采用动态数组(扩大2倍)进行数据管理的,该类与ArrayList类相比属于线程安全的类,效率比较低。
1.ArrayList的底层原理
//1.声明一个List接口类型的引用指向ArrayList类型的对象,形成了多态
//由源码可知,当new对象时并没有申请数组的内存空间
List lt1=new ArrayList();
//2.向集合中添加元素并打印
//由源码可知:当调用add方法添加元素时会给数组申请长度为10的一维数组,扩容原理是:原始长度的1.5倍
lt1.add("one");
System.out.println("lt1="+lt1); //lt1=[one]
//new ArrayList源码
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private int newCapacity(int minCapacity){
int oldCapacity=elementData.length;
int newCapacity=oldCapacity+(oldCapacity>>1);
if(newCapacity-minCapacity <=0){
if(elementData==DEFAULICAPACITY_EMPTY_ELEMENETDATA)
return Math.max(DEFAULT_CAPACITY_minCapacity);
if(minCapacity<0)
throw new OutMemoryError();
return minCapacity;
}
}
2.LinkedList的底层源码
//2.声明一个List接口类型的引用指向LinkedList类型的对象,形成了多态
List lt2=new LinkedList();
lt2.add("one");
System.out.println("lt2="+lt2); //lt2=[one]
/**new LinkedList时候构造了空的链表
* Constructs an empty list.
*/
public LinkedList() {
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
1.5.2 常用的方法
方法声明 | 功能介绍 |
---|---|
void add(int index,E element) | 向集合中指定位置添加元素 |
Boolean addAll(int index,Collection<?extends E>c) | 向集合中添加所有元素 |
E get(int index) | 从集合中获取指定位置元素 |
Int IndexOf(Object o) | 查找参数指定的对象 |
int lastIndexof(Object o) | 反向查找参数指定的对象 |
E set(int index,E element) | 修改指定位置的元素 |
E remove(int index) | 删除指定位置的元素 |
List subList(int fromIndex,int toIndex) | 用于获取子List |
1.add方法
//1.准备一个List集合并打印
List lt1 = new LinkedList();
System.out.println(lt1); //[]
//2.向集合中添加元素打印
//向集合中的开头位置添加元素
lt1.add(0,"one");
System.out.println("lt1="+lt1); //lt1=[one]
//向集合中的末尾位置添加元素
lt1.add(1,3);
System.out.println("lt1="+lt1); //lt1=[one, 3]
//向集合中的中间位置添加元素
lt1.add(1,"two");
System.out.println("lt1="+lt1); //lt1=[one, two, 3]
System.out.println("-------------");
2.get方法
//3.根据参数指定的下表来获取元素
String str1=(String) lt1.get(0);
System.out.println("获取到的元素是:"+str1); //获取到的元素是:one
/*
String str2=(String)lt1.get(2); //注意:获取元素并进行强制类型转换时一定要慎重,因为容易发生类型转换异常
System.out.println("获取到的元素是:"+str2); //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
//编译ok,运行发生ClassCastException类型转换异常
*/
//4.使用get方法获取集合中的所有元素并打印
for (int i=0;i<lt1.size();i++){
System.out.println(lt1.get(i));
//one
//two
//3
}
StringBuilder sb1=new StringBuilder();
sb1.append("[");
for (int i=0;i<lt1.size();i++){
if (lt1.size()-1 !=i) {
sb1.append(lt1.get(i) + ", ");
}else
{
sb1.append(lt1.get(i));
}
}
sb1.append("]"); //[one, two, 3]
System.out.println(sb1);
3.indexOf和lastIndexOf方法
//5.查找指定元素出现的索引位置
lt1.add("one");
System.out.println("lt1="+lt1); //lt1=[one, two, 3, one]
System.out.println("one第一次出现的索引位置为:"+lt1.indexOf("one")); //one第一次出现的索引位置为:0
System.out.println("one最后一次出现的索引位置为:"+lt1.lastIndexOf("one")); //one最后一次出现的索引位置为:3
4.set方法
//6.实现集合中元素的修改
System.out.println("lt1=" + lt1); //lt1=[one, two, 3, one]
Integer it1 = (Integer) lt1.set(2, "three");
System.out.println("被修改的元素是:" + it1); //被修改的元素是:3
System.out.println("修改后集合中的元素有:" + lt1); //修改后集合中的元素有:[one, two, three, one]
String str2 = (String) lt1.set(3, "four");
System.out.println("被修改的元素是:" + str2); //被修改的元素是:one
System.out.println("修改后集合中的元素有:" + lt1); //修改后集合中的元素有:[one, two, three, four]
5.remove方法
//7.使用remove方法将集合中的所有元素删除
/*错误的删除方式
System.out.println("lt1=" + lt1); //lt1=[one, two, three, four]
for (int i = 0; i < lt1.size(); i++) {
System.out.println("被删除的元素是:" + lt1.remove(i)); //被删除的元素是:one 删除元素后,后面的元素补位
//被删除的元素是:three
}
System.out.println("lt1=" + lt1); //lt1=[two, four]
*/
System.out.println("---------分隔符----------");
/*正确的删除方式:正序
for (int i = 0; i < lt1.size(); ) {
System.out.println("被删除的元素是:"+lt1.remove(0));
//被删除的元素是:one
//被删除的元素是:two
//被删除的元素是:three
//被删除的元素是:four
}
System.out.println("lt1=" + lt1); //lt1=[]
*/
System.out.println("---------分隔符----------");
/*正确的删除方式:倒序
System.out.println("lt1=" + lt1); //lt1=[one, two, three, four]
for (int i=lt1.size()-1;i>=0;i--){
System.out.println("被删除的元素是:"+lt1.remove(i));
//被删除的元素是:four
//被删除的元素是:three
//被删除的元素是:two
//被删除的元素是:one
}
System.out.println("lt1="+lt1); //lt1=[]
*/
6.subList获取子集合
注意:需要强调的就是子集合与当前集合共用同一块内存空间
//8.获取当前集合中的子集合,也就是将集合中的一部分内容获取出来,子集合和当前集合共用同一块内存空间
System.out.println("lt1="+lt1); //lt1=[one, two, three, four]
//表示获取当前集合lt1中下标从1开始到3之间的元素,注意:包含1但不包含3
List lt2=lt1.subList(1,3);
System.out.println("lt2="+lt2); //lt2=[two, three]
//删除lt2中元素的数值
String str3=(String)lt2.remove(0);
System.out.println("被删除的元素是:"+str3); //被删除的元素是:two
System.out.println("删除后lt2="+lt2); //删除后lt2=[three]
System.out.println("删除后的lt1="+lt1); //删除后的lt1=[one, three, four]
7.栈(Vector)相关方法
Modifier and Type | Method and Description |
---|---|
boolean | empty() 测试此堆栈是否为空。 |
E | peek() 查看此堆栈顶部的对象,而不从堆栈中删除它。 |
E | pop() 删除此堆栈顶部的对象,并将该对象作为此函数的值返回。 |
E | push(E item) 将项目推送到此堆栈的顶部。 |
int | search(Object o) 返回一个对象在此堆栈上的基于1的位置。 |
众所周知:在数据结构中,"队列"是先进先出的,而"栈"是后进先出的,如何用栈来实现先进先出功能呢?
//1.准备一个Stack类型的对象并打印
Stack s1=new Stack();
Stack s2=new Stack();
System.out.println("s1="+s1); //s1=[]
System.out.println("s2="+s2); //s2=[]
//2.将数据11,22,33,44,55依次入栈并打印
for (int i=1;i<=5;i++){
Object obj=s1.push(i*(11));
// System.out.println("入栈的元素是:"+obj);
System.out.println("栈中的元素是:"+s1);
/*
栈中的元素是:[11]
栈中的元素是:[11, 22]
栈中的元素是:[11, 22, 33]
栈中的元素是:[11, 22, 33, 44]
栈中的元素是:[11, 22, 33, 44, 55]
*/
}
//3.查找栈顶元素值并打印
System.out.println("栈顶元素是:"+s1.peek()); //栈顶元素是:55
//4.对栈中所有元素依次出栈并打印
//栈中的元素是:[11, 22, 33, 44, 55]
for (int i=0;i<s1.size();){
Object temp=s1.pop();
System.out.println("出栈的元素是:"+temp);
s2.push(temp); //将s1出栈的数据放到s2中
/*
出栈的元素是:55
出栈的元素是:44
出栈的元素是:33
出栈的元素是:22
出栈的元素是:11
*/
}
//5.最终打印栈中的所有的元素
System.out.println("最终s1="+s1); //最终s1=[]
System.out.println("最终s2="+s2); //最终s2=[55, 44, 33, 22, 11]
for (int i=0;i<s2.size();){
System.out.println("s2出栈的元素是:"+s2.pop());
/*
s2出栈的元素是:11
s2出栈的元素是:22
s2出栈的元素是:33
s2出栈的元素是:44
s2出栈的元素是:55
*/
}
从代码可知,利用两个栈可以实现队列的先进先出功能,这是一个常见的笔试面试题。
1.6 Queue集合
1.6.1 基本概念
- java.util.Queue集合是Collection集合的子集合,与List集合属于平级关系。
- 该集合主要用于描述具有先进先出特征的数据结构,叫做队列(first in first out FIFO)。
- 该集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势。
1.6.2 常用的方法
Modifier and Type | Method and Description |
---|---|
boolean | add(E e) 将指定的元素插入到此队列中,如果可以立即执行此操作,而不会违反容量限制, 在成功后返回true 如果当前没有可用空间,则抛出IllegalStateException。 |
E | element() 检索,但不删除,这个队列的头。 |
boolean | offer(E e) 如果在不违反容量限制的情况下立即执行,则将指定的元素插入到此队列中。 |
E | peek() 检索但不删除此队列的头,如果此队列为空,则返回 null 。 |
E | poll() 检索并删除此队列的头,如果此队列为空,则返回 null 。 |
E | remove() 检索并删除此队列的头。 |
以下代码实现将数据11,22,33,44,55依次入队并打印,然后查看队首元素并打印,然后将队列中所有数据依次出队并打印。
//1.准备一个Queue集合并打印
Queue q=new LinkedList();
System.out.println("队列中的元素有:"+q); //队列中的元素有:[]
//2.将数据11、22、33、44、55依次入队并打印
for (int i=1;i<=5;i++){
q.offer(i*11);
System.out.println("队列中的元素有:"+q);
/*
队列中的元素有:[11]
队列中的元素有:[11, 22]
队列中的元素有:[11, 22, 33]
队列中的元素有:[11, 22, 33, 44]
队列中的元素有:[11, 22, 33, 44, 55]
*/
}
//3.查看队首元素并打印
System.out.println("队首元素是:"+q.peek()); //队首元素是:11
//4.查看队列中所有数据依次出队并打印
for (int i=1;i<=q.size();)
{
System.out.println("出队的元素是"+q.poll());
/*
出队的元素是11
出队的元素是22
出队的元素是33
出队的元素是44
出队的元素是55
*/
}
//5.最终打印栈中的所有元素
System.out.println("最终队列中的元素有:"+q); //最终队列中的元素有:[]
1.7 泛型机制
1.7.1 基本概念
- 通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看做Object类型放入的,因此从集合中取出元素时也是Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。
- 为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其他类型的元素则编译报错。
- 泛型只在编译时期有效,在运行时期不区分是什么类型。
1.7.2 底层原理
泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,从而使得集合中所有的E被实际参数替换。由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。
如:
//其中i叫做形式参数,复制占位 其中E叫做形式参数,负责占位
//int i=10; E=String;
//int i=20; E=Integer;
public static void show(int i){ public interface List<E>{
... ...
} }
//其中10叫做实际参数,负责给形式参数初始化 //其中String叫做实际参数
show(10); List<String> lt1=...;
show(20); List<String> lt2=...;
1.7.2 自定义泛型接口
- 泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:<E,T,…>等
1.7.3 自定义泛型类
- 泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:<E,T,…>等
- 实例化泛型类时应该指定具体的数据类型,并且是引用数据类型而不是基本数据类型
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person<T> {
private String name;
private int age;
private T gender;
}
public class PersonTest {
public static void main(String[] args) {
//1.声明Person类型的引用指向Person类型
Person p1 = new Person("baojianjia", 22, "男"); //Person(name=baojianjia, age=22, gender=男)
//2.打印对象的特征
System.out.println(p1);
//3.在创建对象的同时指定数据类型,用于给T进行初始化
Person<String> p2=new Person<>();
p2.setGender("女");
System.out.println(p2); //Person(name=null, age=0, gender=女)
//3.使用Boolean类型作为性别的类型
Person<Boolean> p3=new Person<>();
p3.setGender(true);
System.out.println(p3); //Person(name=null, age=0, gender=true)
}
}
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
- 子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
//public class SubPerson extends Person{ //不保留泛型并且没有指定类型,此时Person类中的T默认为Object类型
// public class SubPerson extends Person<String> { //不保留泛型但指定了泛型的类型,此时Person类中的T被指定为String类型
// public class SubPerson<T> extends Person<T>{ //保留父类的泛型,可以在构造对象时来指定T的类型
public class SubPerson<T,T1> extends Person<T>{ //保留父类的泛型,同时在子类中增加新的泛型
}
1.7.4 自定义泛型方法
-
泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时候需要对泛型参数进行实例化;
-
泛型方法的格式:
[访问权限]<泛型> 返回值类型 方法名([泛型标识 参数名称]) {方法体;}
-
在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person<T> {
private String name;
private int age;
private T gender;
//自定义方法实现将参数指定数组中的所有元素打印出来
public static <T1> void printArray(T1[] arr){
for (T1 tt:arr){
System.out.println("tt="+tt);
}
}
}
public class SubPersonTest {
public static void main(String[] args) {
//调用泛型方法进行测试
Integer[] arr={11,22,33,44,55};
Person.printArray(arr);
//tt=11
//tt=22
//tt=33
//tt=44
//tt=55
}
}
1.7.5 泛型在继承上的体现
-
如果B是A的一个子类或子接口,而G是具有泛型声明的类或者接口,则G并不是G的子类型!
比如: String是Object的子类,但是List并不是List的子类。
1.7.6 通配符的使用
-
有时候们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了;
-
如:之前传入的类型要求为Integer类型,但是后来业务需要Integer的父类Number类也可以传入;
-
泛型中有三种通配符形式:
<?>无限制通配符:表示我们可以传入任意类型的参数 <?extends E>表示类型的上界是E,只能是E或者是E的子类 <?super E>表示类型的下界是E,只能是E或者是E的父类
public class Animal {
}
public class Dog extends Animal{
}
public class GenericTest {
public static void main(String[] args) {
//1.声明两个List类型集合进行测试
List<Animal> lt1=new LinkedList<>();
List<Dog> lt2=new LinkedList<>();
//试图将lt2的数值赋值给lt1,也就是发生List<Dog>向List<Animal>类型的转换
// lt1=lt2; Error:两个类型之间不具备父子类关系
//2.使用通配符作为泛型类型的公共父类
List<?>lt3=new LinkedList<>();
lt3=lt1; //可以发生List<Animal>类型到List<?>类型的转换
lt3=lt2; //可以发生List<Dog>类型到List<?>类型的转换
//向公共父类中添加元素或者获取元素
//lt3.add(new Animal()); Error:不能存放Animal类型的对象
//lt3.add(new Dog()); Error:不能存放Dog类型的对象,不支持元素的添加操作
Object o = lt3.get(0); //支持元素的获取操作,全部当做Object类型来处理
//3.使用有限制的通配符进行使用
List<?extends Animal> lt4=new LinkedList<>();
// lt4.add(new Animal()); //不支持元素的添加操作
// lt4.add(new Dog());
// lt4.add(new Object());
Animal animal = lt4.get(0);
List<?super Animal> lt5=new LinkedList<>();
lt5.add(new Animal());
lt5.add(new Dog());
// lt5.add(new Object()); ERROR:超过了Animal类型的范围
Object object = lt5.get(0);
}
}
1.8 Set集合(熟悉)
1.8.1 基本概念
- java.util.Set集合是Collection集合的子集合,与List集合平级;
- 该集合中元素没有先后放入次序(但不代表随机),且不允许重复;
- 该集合的主要实现类是:HashSet和TreeSet类以及LinkedHashSet类;
- 其中HashSet类的底层是采用哈希表进行数据管理的;
- 其中TreeSet类的底层是采用红黑树进行数据管理的;
- 其中LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
1.8.2 常用的方法
-
参考Collection集合中的方法即可!
-
案例题目
准备一个Set集合指向HashSet对象,向该集合中添加元素“two”并打印,再向集合中添加元素“one”并打印,再向集合中添加元素“three”并打印,再向集合中添加“one”并打印;
public class HashSet {
public static void main(String[] args) {
//1.声明一个Set类型的引用指向HashSet类型的对象
Set<String> s1=new java.util.HashSet<>();
System.out.println("s1="+s1); //s1=[]
//2.向集合中添加元素并打印
boolean b1=s1.add("two");
System.out.println("b1="+b1); //b1=true
System.out.println("s1="+s1); //s1=[two]
//从打印结果上可以看到元素没有先后放入次序(表面)
b1=s1.add("one");
System.out.println("b1="+b1); //b1=true
System.out.println("s1="+s1); //s1=[one, two]
b1=s1.add("three");
System.out.println("b1="+b1); //b1=true
System.out.println("s1="+s1); //s1=[one, two, three]
//验证元素不能重复
b1=s1.add("one");
System.out.println("b1="+b1); //b1=false
System.out.println("s1="+s1); //s1=[one, two, three]
//将放入的元素使用双链表连接起来
Set<String> s2=new java.util.LinkedHashSet<>();
//2.向集合中添加元素并打印
boolean b2=s2.add("two");
System.out.println("b2="+b2); //b2=true
System.out.println("s2="+s2); //s2=[two]
//从打印结果上可以看到元素没有先后放入次序(表面)
b2=s2.add("one");
System.out.println("b2="+b2); //b2=true
System.out.println("s2="+s2); //s2=[two, one]
b2=s2.add("three");
System.out.println("b2="+b2); //b2=true
System.out.println("s2="+s2); //s2=[two, one, three]
//验证元素不能重复
b2=s2.add("one");
System.out.println("b2="+b2); //b2=true
System.out.println("s2="+s2); //s2=[two, one, three]
}
}
1.8.3 元素放入HashSet集合的原理
-
使用元素调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置;
-
若该位置没有元素,则将该元素直接放入即可;
-
若该位置有元素,则使用新元素与已有元素比较哈希值,若哈希值不相同,则将该元素直接放入;
-
若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较;
-
若相等则添加元素失败,否则将元素直接放入即可;
-
思考:为什么要求重写equals方法后要重写hashCode方法呢?
-
解析:
当两个元素调用equals方法相等时证明这两个元素相同,重写hashCode方法后保证这两个元素得到的哈希码值相同,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而提高效率并避免重复元素的出现‘
1.8.4 TreeSet集合的概念
-
二叉树主要指每个节点最多只有两个子节点的树形结构。
-
满足以下3个特征的二叉树叫做有序二叉树。
1.左子树中的任意节点元素都小于根节点元素值;
2.右子树中的任意节点元素都大于根节点元素值;
3.左子树和右子树的内部也遵守上述规则;
-
由于TreeSet集合的底层采用红黑树进行数据的管理,当有新元素插入到TreeSet集合时,需要使用新元素与集合中已有的元素依次比较来确定新元素的合理位置;
-
比较元素大小的规则有两种方式:
使用元素的自然排序规则进行比较并排序,让元素实现java.lang.Comparable接口;
使用比较器规则进行比较并排序,构造TreeSet集合时传入java.util.Comparator接口;
-
自然排序的规则比较单一,而比较器的规则比较多元化,而且比较器优先于自然排序;
TreeSet集合放入String对象的实现
public class TreeSetTest {
public static void main(String[] args) {
//1.准备一个TreeSet集合并打印
Set<String> s1=new TreeSet<>();
System.out.println("s1="+s1); //s1=[]
//2.向集合中添加String类型的对象并打印
boolean b1 = s1.add("aa");
System.out.println(b1); //true
System.out.println(s1); //[aa]
boolean b2 = s1.add("cc");
System.out.println(b2); //true
System.out.println(s1); //[aa, cc]
boolean b3 = s1.add("bb");
System.out.println(b3); //true
//由于TreeSet集合的底层是采用红黑树实现的,因此元素有大小次序,默认从小到大打印
System.out.println(s1); //[aa, bb, cc]
}
}
自然排序:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Comparable<Student> {
private String name;
private int age;
@Override
public int compareTo(Student o) {
// return 0; //调用对象和参数对象相等,调用对象就是新增加的对象
// return -1;//调用对象小于参数对象
// return 1; //调用对象大于参数对象
// return this.getName().compareTo(o.getName()); //比较姓名
// return this.getAge()-o.getAge();
//
// if (0==this.getName().compareTo(o.getName())){ //姓名相同时,按年龄比,写法一
// return this.getAge()-o.getAge();
// }
// return this.getName().compareTo(o.getName());
// }
int a = this.getName().compareTo(o.getName()); 姓名相同时,按年龄比,写法二
return 0 != a ? a : this.getAge() - o.getAge();
}
}
public class TreeSetTest {
public static void main(String[] args) {
//准备一个TreeSet集合并放入Student类型的对象并打印
Set<Student> s2=new TreeSet<>();
s2.add(new Student("baojianjia",22));
s2.add(new Student("huayuxinxi",20));
s2.add(new Student("guozhanan",25));
System.out.println("s2="+s2); //比较姓名:s2=[Student(name=baojianjia, age=22), Student(name=guozhanan, age=25), Student(name=huayuxinxi, age=20)]
//比较年龄:s2=[Student(name=huayuxinxi, age=20), Student(name=baojianjia, age=22), Student(name=guozhanan, age=25)]
//姓名相同的情况下比年龄:s2=[Student(name=baojianjia, age=22), Student(name=guozhanan, age=25), Student(name=huayuxinxi, age=20)]
}
}
构造器:
//准备一个比较器对象作为参数传递给构造方法
//匿名内部类:接口/父类类型 引用变量名=new 接口/父类类型(){方法的重写;}
Comparable<Student> comparable=new Comparable<Student>() {
@Override
public int compare(Student o1,Student o2) {
return o1.getAge()-o2.getAge(); //按照年龄进行比较
}
};
从Java8开始支持Lambda表达式(参数列表->{方法体})
Comparable<Student> comparable=(Student o1,Student o2)->{return o1.getAge()-o2.getAge();};
1.9 Map集合(重点)
1.9.1 基本概念
-
java.util.Map<K,V>集合中存取元素的基本单位是:单对元素,其中类型参数如下:
K - 此映射所维护的键(Key)的类型,相当于目录
V - 映射值(Value)的类型,相当于内容
-
该集合中key是不允许重复的,而且一个key只能对应一个value
-
该集合的主要实现类有:HashMap类、TreeMap类、LinkedHashMap类、Properties类
-
其中HashMap类的底层是采用哈希表进行数据管理的
-
其中TreeMap类的底层是采用红黑树进行数据管理的
-
其中LinkedHashMap类与HashMap类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代
-
其中Hashtable类是古老的Map实现类,与HashMap类相比属于线程安全的类,且不允许null作为key或者value的数值。
-
其中Properties类是Hashtable类的子类,该对象用于处理属性文件,key和vale都是String类型的
-
Map集合是面向查询优化的数据结构,在大数据量情况下有着优良的查询性能
-
经常用于根据key检索value的业务场景
1.9.2 常用的方法
方法声明 | 功能介绍 |
---|---|
V put(K key,V value) | 将Key-Value对存入Map,若集合中已经包含该Key,则替换成Key所对应的Value,返回值为该Key原来所对应的Value,若没有则返回null |
V get(Object key) | 返回与参数key所对应的Value对象,如果不存在则返回null |
boolean containsKey(Object key) | 判断集合中是否包含指定的key |
boolean containsValue(Object value) | 判断集合中是否包含指定的value |
V remove(Object key) | 根据参数指定的key进行删除 |
Set keySet() | 返回此映射中包含的键的Set视图 |
Collectionvalues() | 返回此映射中包含的键的Set视图 |
Set<Map.Entry<K,V>>entrySet | 返回此映射中包含的映射的Set视图 |
Map集合实现元素的增加和修改:
public class MapTest {
public static void main(String[] args) {
//1.准备一个Map集合并打印
Map<String,String> m1=new HashMap<>();
System.out.println("m1="+m1); //自动调用HashMap中的toString方法,,默认打印格式为{key1=value, key2=value2, ...} m1={}
//2.向集合中添加元素并打印
String str1=m1.put("bjj","包简嘉");
System.out.println("原来的value数值str1="+str1); //原来的value数值str1=null
System.out.println("m1="+m1); //m1={bjj=包简嘉}
str1=m1.put("huayu","华宇");
System.out.println("原来的value数值str1="+str1); //原来的value数值str1=null
System.out.println("m1="+m1); //m1={huayu=华宇, bjj=包简嘉}
str1=m1.put("xinxi","信息");
System.out.println("原来的value数值str1="+str1); //原来的value数值str1=null
System.out.println("m1="+m1); //m1={huayu=华宇, xinxi=信息, bjj=包简嘉}
str1=m1.put("bjj","厚德载物");
System.out.println("原来的value数值str1="+str1); //原来的value数值str1=包简嘉
System.out.println("m1="+m1); //m1={huayu=华宇, xinxi=信息, bjj=厚德载物}
//3.实现集合中元素的查找操作
boolean b1=m1.containsKey("bjj");
System.out.println(b1); //true
b1=m1.containsKey("baojianjia");
System.out.println(b1); //false
b1=m1.containsValue("包简嘉");
System.out.println(b1); //false
b1=m1.containsValue("厚德载物");
System.out.println(b1); //true
String str2=m1.get("责任");
System.out.println(str2); //null
str2=m1.get("bjj");
System.out.println(str2); //厚德载物
//4.实现集合中元素的删除操作
String removeStr = m1.remove("xinxi");
System.out.println("被删除的元素是:"+removeStr); //被删除的元素是:信息
System.out.println("m1="+m1); //m1={huayu=华宇, bjj=厚德载物}
//5.获取Map集合中所有的key并组成Set视图
Set<String> s1=m1.keySet();
//遍历所有的key
for (String temp:s1){
System.out.println(temp+"="+m1.get(temp));
//huayu=华宇
//bjj=厚德载物
}
//6.获取Map集合中所有的Value并组成Collection视图
Collection<String> co = m1.values();
for (String ts:co){
System.out.println(ts);
//华宇
//厚德载物
}
//7.获取Map集合中所有的键值对并组成Collection视图
Set<Map.Entry<String, String>> entries = m1.entrySet();
for (Map.Entry<String, String> temp:entries){
System.out.println(temp);
//huayu=华宇
//bjj=厚德载物
}
}
}
1.9.3 元素放入HashMap集合的原理
- 使用元素的key调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算在数组中的索引位置
- 若该位置没有元素,则将键值对直接放入即可
- 若该位置有元素,则使用key与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入
- 若key与已有元素的哈希值相同,则使用key调用equals方法与已有元素依次比较
- 若相等则将对应的value修改,否则将键值对直接放入即可
1.9.4 源码中相关的常量
-
DEFAULT_INITIAL_CAPACITY:HashMap的默认容量是16
-
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子是0.75
-
threshold:扩容的临界值,该数值为:容量*填充因子,也就是12
-
TREEIFY_THRESHOLD:若Bucket中链表长度大于该默认值则转化为红黑树存储,该数值是8
-
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,该数值是64
-
案例:
准备一个HashMap集合,统计字符串“123,456,789,123,456”中每个数字字符串出现的次数并打印出来。
如:
123出现了2次
456出现了2次
789出现了1次
1.10 Collections类
1.10.1 基本概念
- java.util.Collections类主要提供了对集合操作或者返回集合的静态方法
1.10.2 常用的方法
方法声明 | 功能介绍 |
---|---|
static<T extends Object & Comparable<?super T>> T max(Collection<?extends T> coll) | 根据元素的自然顺序给定集合的最大元素 |
static T max(Collection<?extends T> coll,Comparator<?super T>comp) | 根据指定比较器引发的顺序返回给定集合的最大元素 |
static <T extends Object & Comparable<?super T>> T min(Collection<?extends T>coll) | 根据元素的自然顺序返回给定集合的最小元素 |
static T min(Collection<?extends T> coll,Comparator<?super T>comp) | 根据指定比较器引发的顺序返回给定集合的最小元素 |
static void copy(List<?super T>dest,List<?extends T> src) | 将一个列表中的所有元素复制到另一个列表中 |
static void reverse(List<?> list) | 反转指定列表中元素的顺序 |
static void shuffle(List<?> list) | 使用默认的随机源随机置换指定的列表 |
static <T extends Comparable<?super T>> void sort(List list) | 根据其元素的自然顺序并指定列表按升序排序 |
static void sort(List list,Comparator <?suer T> c) | 根据指定比较器指定的顺序对指定列表进行排序 |
static void swap(List<?>list,int i,int j) | 交换指定列表中指定位置的元素 |
public class CollectionsTest {
public static void main(String[] args) {
//1.准备一个集合并初始化
List<Integer> lt1 = Arrays.asList(10, 30, 20, 50, 45);
//2.实现集合中元素的各种操作
System.out.println(Collections.max(lt1)); //50
System.out.println(Collections.min(lt1)); //10
//3.实现集合中元素的反转
Collections.reverse(lt1);
System.out.println(lt1); //[45, 50, 20, 30, 10]
//实现两个元素的交换
Collections.swap(lt1,0,4);
System.out.println("交换后="+lt1); //交换后=[10, 50, 20, 30, 45]
//实现元素的排序
Collections.sort(lt1);
System.out.println(lt1); //[10, 20, 30, 45, 50]
//随机置换
Collections.shuffle(lt1);
System.out.println(lt1); //[10, 20, 45, 30, 50]
//实现集合间元素的拷贝
List<Integer> lt2=Arrays.asList(new Integer[10]);
//表示将lt1中的元素拷贝到lt2中
Collections.copy(lt2,lt1);
System.out.println(lt2); //[45, 30, 20, 50, 10, null, null, null, null, null]
}
}
其中Collection.copy的源码是:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
注:被复制对象的size一定要大于被复制对象的size。否则会报Source does not fit in dest
50, 20, 30, 10]
//实现两个元素的交换
Collections.swap(lt1,0,4);
System.out.println(“交换后=”+lt1); //交换后=[10, 50, 20, 30, 45]
//实现元素的排序
Collections.sort(lt1);
System.out.println(lt1); //[10, 20, 30, 45, 50]
//随机置换
Collections.shuffle(lt1);
System.out.println(lt1); //[10, 20, 45, 30, 50]
//实现集合间元素的拷贝
List lt2=Arrays.asList(new Integer[10]);
//表示将lt1中的元素拷贝到lt2中
Collections.copy(lt2,lt1);
System.out.println(lt2); //[45, 30, 20, 50, 10, null, null, null, null, null]
}
}
其中Collection.copy的源码是:
```java
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
注:被复制对象的size一定要大于被复制对象的size。否则会报Source does not fit in dest