集合进阶(十三)
集合体系结构
- 单列集合Collection
- List(接口):添加的元素是有序(指存和取顺序一样),可重复(可存在重复的元素),可索引(可通过索引获取元素)
- ArrayList(实现类)
- LinkedList(实现类)
- Vector(已淘汰)(实现类)
- Set(接口):添加的元素是无序(指存和取顺序可能不一样),不重复(不能存储重复的元素),无索引(不可通过索引获取元素,不可使用普通for循环遍历)
- HashSet(实现类)->LinkedHashSet(实现类)
- TreeSet(实现类)
- List(接口):添加的元素是有序(指存和取顺序一样),可重复(可存在重复的元素),可索引(可通过索引获取元素)
- 双列集合Map
- HashMap (实现类)->LinkedHashMap(实现类)
- Hashtable(实现类)->Properties(实现类)
- TreeMap(实现类)
泛型
若并未个集合指定类型,默认数据类型为Object类,此时可以给集合添加任意数据类型,但我们获取数据时,无法使用子类特有的行为。
泛型:是JDK5中引入的特性,可在编译阶段约束操作的数据类型,并进行检查。
格式:<引用数据类型>
好处:统一数据类型,将运行时期的问题提前到编译期间,避免强制类型转换可能出现的异常,因此在编译阶段就能确定下来,并且能调用特有的行为。
细节:
- 泛型中不能写基本数据类型,需将基本数据类型转换成对应的包装类
- 指定泛型的具体类型后,传送数据时,可传送该类类型或其子类类型
- 若不写泛型,类型默认是Object
- 泛型可在类,方法,接口后进行定义,即泛型类,泛型方法,泛型接口
泛型类
当一个类中,某个变量的数据类型不确定,可定义泛型类。
泛型类在创建对象时,会确定具体类型
格式:
修饰符 class 类名<E>{
}
//此处E可以理解为记录数据类型的对象,可写为:T,E,K,V等
范例:
//MyArrayList.java
import java.util.Arrays;
public class MyArrayList<E> {
int size;
Object[] objects=new Object[10];
public boolean add(E e){
objects[size]=e;
size++;
return true;
}
public E get(int index){
return (E)objects[index];
}
@Override
public String toString() {
return Arrays.toString(objects);
}
}
//Test.java
public class Test{
public static void main(String[] args) {
MyArrayList<String> myArrayList = new MyArrayList<>();
myArrayList.add("aaa");
myArrayList.add("bbb");
myArrayList.add("ccc");
System.out.println(myArrayList);
System.out.println(myArrayList.get(1));
}
}
/*
[aaa, bbb, ccc, null, null, null, null, null, null, null]
bbb
*/
泛型方法
方法中形参类型不确定时:
- 使用类名后面定义的泛型。(所有方法都能用)
- 在方法申明上定义自己的泛型。(只有本方法能用)
格式:
修饰符<T> 返回值类型 方法名(类型 变量名){
}
//此处T可以理解为记录数据类型的对象,可写为:T,E,K,V等
//非静态方法:内部的泛型,会根据类的泛型去匹配
//静态方法:静态方法中若加入泛型,必须声明出自己的独立的泛型,在调用方法传入参数时,会确定具体的类型
范例:
//定义一个工具类,ListUtil
//类中定义一个静态方法addAll,用来添加另一个集合中的元素
//ListUtil.Java
import java.util.ArrayList;
public class ListUtil {
private ListUtil(){}
public static<T> boolean addALL(ArrayList<T> list1, ArrayList<T> list2){
for(int i=0;i<list2.size();i++){
list1.add(list2.get(i));
}
return true;
}
}
//test.java
import java.util.ArrayList;
public class Test{
public static void main(String[] args) {
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("aaa");
arrayList.add("bbb");
arrayList.add("ccc");
ArrayList<String> arrayList1=new ArrayList<>();
arrayList1.add("ddd");
arrayList1.add("eee");
arrayList1.add("fff");
ListUtil.addALL(arrayList, arrayList1);
System.out.println(arrayList);
}
}
/*
[aaa, bbb, ccc, ddd, eee, fff]
*/
泛型接口
格式:
修饰符 interface 接口名<类型>{
}
使用方式:
- 实现类给出具体的类型,创造实现类的对象时不需要给出泛型
- 实现类延续泛型创建实现类对象时再确定类型,创造实现类的对象时需要给出泛型
方式一范例:
//接口类
//ListAdd.java
public interface ListAdd<E>{
public abstract void add(E e);
}
//实现类
//MyArrayList.java
import java.util.Arrays;
public class MyArrayList implements ListAdd<String> {
Object[] objects=new Object[10];
int size=0;
@Override
public void add(String s) {
objects[size]=s;
size++;
}
@Override
public String toString() {
return Arrays.toString(objects);
}
}
//测试类
//Test.java
public class Test{
public static void main(String[] args) {
MyArrayList myArrayList=new MyArrayList();
myArrayList.add("aaa");
myArrayList.add("bbb");
myArrayList.add("ccc");
System.out.println(myArrayList);
}
}
/*
[aaa, bbb, ccc, null, null, null, null, null, null, null]
*/
方式二范例:
//接口类
//ListAdd.java
public interface ListAdd<E>{
public abstract void add(E e);
}
//实现类
//MyArrayList.java
import java.util.Arrays;
public class MyArrayList<E> implements ListAdd<E> {
Object[] objects=new Object[10];
int size=0;
@Override
public void add(E e) {
objects[size]=e;
size++;
}
@Override
public String toString() {
return Arrays.toString(objects);
}
}
//测试类
//Test.java
public class Test{
public static void main(String[] args) {
MyArrayList<String> myArrayList=new MyArrayList();
myArrayList.add("aaa");
myArrayList.add("bbb");
myArrayList.add("ccc");
System.out.println(myArrayList);
}
}
/*
[aaa, bbb, ccc, null, null, null, null, null, null, null]
*/
通配符
通配符可以限定类型的范围
泛型不具备继承性,但泛型的数据具备继承性
import java.util.ArrayList;
public class Test{
public static void main(String[] args) {
ArrayList<Ye> list1=new ArrayList<>();
ArrayList<Fu> list2=new ArrayList<>();
ArrayList<Zi> list3=new ArrayList<>();
//泛型不具有继承性
//method(list1);
//method(list2);会报错
//method(list3);会报错
//数据具有继承性
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
}
public static void method(ArrayList<Ye> list){
}
}
class Ye{
}
class Fu extends Ye{
}
class Zi extends Fu{
}
通配符:
**?**表示不确定的类型,也可以进行类型的限定
- ? extends E
- 表示可以传递E或E所有的子类类型
- ?super E
- 表示可以传递E或者E的所有父类类型
package com.ljsblog.domain12;
import java.util.ArrayList;
public class Test{
public static void main(String[] args) {
ArrayList<Ye> list1=new ArrayList<>();
ArrayList<Fu> list2=new ArrayList<>();
ArrayList<Zi> list3=new ArrayList<>();
method1(list1);
method1(list2);
method1(list3);
method2(list1);
method2(list2);
method2(list3);
}
public static void method1(ArrayList<? extends Ye> list){
}
public static void method2(ArrayList<? super Zi> list){
}
}
class Ye{
}
class Fu extends Ye{
}
class Zi extends Fu{
}
使用场景:
- 若在定义类,方法,接口时,类型不确定,就可以定义,泛型类,泛型方法,泛型接口。
- 若类型不确定,但只传递某个继承体系,可使用泛型的通配符
范例
/*需求
定义一个继承结构:
动物
/ \
猫 狗
/ \ / \
波斯猫 狸花猫 泰迪 哈士奇
属性:名字,年龄
行为:吃东西
波斯猫方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干
狸花猫方法体打印:一只叫做xxx的,x岁的狸花猫,正在吃鱼
泰迪方法体打印:一只叫做xxx的,x岁的泰迪,正在吃骨头,边吃边蹭
哈士奇方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家
测试类中定义一个方法用于饲养动物
public static void keepPet(ArrayList<???> list){
遍历集合,调用动物的eat方法
}
要求1:该方法能养所有品种的猫,但是不能养狗
要求2:该方法能养所有品种的狗,但是不能养猫
要求3:该方法能养所有的动物,但是不能传递其他类型
*/
//Animal.java
public abstract class Animal {
private String name;
private int age;
public Animal(){}
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age=age;
}
public int getAge(){
return age;
}
public abstract void eat();
}
//Dog.java
public abstract class Dog extends Animal{
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
}
//Cat.java
public abstract class Cat extends Animal {
public Cat(){}
public Cat(String name,int age){
super(name,age);
}
}
//Bosi.java
public class Bosi extends Cat {
public Bosi(){}
public Bosi(String name,int age){
super(name,age);
}
@Override
public void eat(){
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的波斯猫,正在吃小饼干");
}
}
//Lihua.java
public class Lihua extends Cat {
public Lihua() {
}
public Lihua(String name, int age) {
super(name, age);
}
@Override
public void eat(){
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的狸花猫,正在吃鱼");
}
}
//Taidi.java
public class Taidi extends Dog{
public Taidi() {
}
public Taidi(String name, int age) {
super(name, age);
}
@Override
public void eat(){
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的泰迪,正在吃骨头,边吃边蹭");
}
}
//Hashiqi.java
public class Hashiqi extends Dog{
public Hashiqi() {
}
public Hashiqi(String name, int age) {
super(name, age);
}
@Override
public void eat(){
System.out.println("一只叫做"+getName()+"的,"+getAge()+"岁的哈士奇,正在吃骨头,边吃边拆家");
}
}
//Test.java
import java.util.ArrayList;
public class Test{
public static void main(String[] args) {
ArrayList<Bosi> list1=new ArrayList<>();
Bosi bosi1=new Bosi("波斯猫甲",7);
Bosi bosi2=new Bosi("波斯猫乙",9);
list1.add(bosi1);
list1.add(bosi2);
keepPet1(list1);
ArrayList<Lihua> list2=new ArrayList<>();
Lihua lihua1=new Lihua("狸花猫甲",1);
Lihua lihua2=new Lihua("狸花猫乙",2);
list2.add(lihua1);
list2.add(lihua2);
keepPet1(list2);
ArrayList<Hashiqi> list3=new ArrayList<>();
Hashiqi hashiqi1=new Hashiqi("哈士奇甲",2);
Hashiqi hashiqi2=new Hashiqi("哈士奇乙",3);
list3.add(hashiqi1);
list3.add(hashiqi2);
keepPet2(list3);
ArrayList<Taidi> list4=new ArrayList<>();
Taidi taidi1=new Taidi("泰迪甲",12);
Taidi taidi2=new Taidi("泰迪乙",6);
list4.add(taidi1);
list4.add(taidi2);
keepPet2(list4);
keepPet3(list1);
keepPet3(list2);
keepPet3(list3);
keepPet3(list4);
}
//要求1:该方法能养所有品种的猫,但不能养狗
public static void keepPet1(ArrayList<? extends Cat> list){
for(int i=0;i<list.size();i++){
list.get(i).eat();
}
}
//要求2:该方法能养所有品种的狗,但不能养猫
public static void keepPet2(ArrayList<? extends Dog> list){
for(int i=0;i<list.size();i++){
list.get(i).eat();
}
}
//要求3:该方法能养所有动物
public static void keepPet3(ArrayList<? extends Animal> list){
for(int i=0;i<list.size();i++){
list.get(i).eat();
}
}
}
数据结构
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
常见的数据结构:
- 栈
- 队列
- 数组
- 链表
- 二叉树
- 二叉查找树
- 平衡二叉树
- 红黑树
栈
栈只能在表的一个固定端进行数据结点的插入和删除操作。
特点:后进先出,先进后出。
先插入的数据将被压入栈底,最后插入的数据在栈顶,读出数据时,从栈顶开始逐个读出。
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
队列
队列只允许在表的一端进行插入操作,而在另一端进行删除操作。
特点:先进先出,后进后出。
进行插入操作的一端称为队尾,进行删除操作的一端称为队头。
数据从队尾进入队列模型的过程称为:入队列
数据从队头离开队列模型的过程称为:出队列
数组
特点:
- 查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同(元素在内存中是连续存储的)。
- 删除效率低:要将原始数据删除,同时后面每个数据前移
- 添加效率极低:添加位置的每个数据后移,再添加数据。
链表
链表中的结点是独立的对象,在内存中是不连续的。
单向链表每个节点包含数据值和下个结点的地址。
双向链表每个节点包含上个结点的地址,数据值和下个结点的地址。
链表查询慢,无论查询哪个数据都要从头找,但首尾操作极快。
链表增删相对较快。
树
二叉树
二叉树是指树中节点(结点)的度不大于2的有序树,即二叉树是每个结点最多有两个子树的树结构。通常子树被称作左子树和右子树。
二叉树的结点包括:父节点地址,左子节点地址,右子节点地址,值。
度:每个节点的子节点数量。
树高:树的总层数。
根节点:最顶层的结点。
左子节点:左下方的结点。
右子节点:右下方的结点。
二叉查找树
二叉查找树,又称二叉排序树或二叉搜索树。
特点:
- 每个结点上最多有两个子节点
- 任意结点左子树上的值都小于当前节点
- 任意结点右子树上的值都大于当前节点
添加结点规则:
- 小的存左边
- 大的存右边
- 一样的不存
弊端:二叉查找树可能退化成链表,相应的,二叉搜索树的查找操作是和这棵树的高度相关的,而此时这颗树的高度就是这颗树的节点数n。
平衡二叉树
任意结点的左右子树高度不超过1,任意结点的左右两个子树都是一颗平衡二叉树。
遍历方式
所有二叉树都可使用
- 前序遍历:从根节点开始,按照当前节点,左子节点,右子节点的顺序遍历。
- 中序遍历:从最左边的子节点开始,按照左子节点,当前节点,右子节点的顺序遍历。
- 后续遍历:从最左边的子节点开始,按照左子节点,右子节点,当前节点的顺序遍历。
- 层序遍历:从根节点开始一层一层遍历。
旋转机制:左旋和右旋。
触发时机:当添加一个结点之后,该数不再是一颗平衡二叉树
左旋
确定支点:从添加的结点开始,不断的往父节点找不平衡的节点。
步骤:
- 以不平衡的点为支点
- 把支点左旋降级,变成左子节点
- 晋升原来的右子节点
或
- 以不平衡的点为支点
- 将根节点的右侧向左拉
- 原本的右子节点变成新的父节点,并把多余的左子节点出让,给已降级的根节点当右子节点
右旋
确定支点:从添加的结点开始,不断的往父节点找不平衡的节点。
步骤:
- 以不平衡的点为支点
- 把支点右旋降级,变成右子节点
- 晋升原来的左子节点
或
- 以不平衡的点为支点
- 将根节点的左侧向右拉
- 原本的左子节点变成新的父节点,并把多余的右子节点出让,给已降级的根节点当左子节点
需旋转的四种情况
- 左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡,需进行一次右旋。
- 左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡,需先局部左旋,再整体右旋。
- 右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡,需进行一次左旋。
- 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡,需先局部右旋,再整体左旋。
红黑树
红黑树:是一种自平衡的二叉查找树,是一种特殊的二叉查找树,红黑树上的每一个结点都有存储位表示结点的颜色,每一个结点可以是红或者黑,红黑树不是高度平衡的,它的平衡是通过红黑规则进行实现的。
红黑树增删改查的性能都很好。
节点:父节点地址,左子节点地址,右子节点地址,值,颜色。
红黑规则:
- 每个节点或是红色,或是黑色
- 根节点必须是黑色
- 若一个节点没有子节点或父节点,则该节点响应的指针属性为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的。
- 若某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径(只能向下走,不能往回走)上,均包含相同数目的黑色节点
添加结点规则:添加的节点默认是红色(效率高)。
根:直接变为黑色
非根:
- 父黑色:则不需要任何操作
- 父红色:
- 叔叔红色:将“父”设为黑色,将“叔叔”设为黑色,将”祖父“设为”红色“。若祖父为根,则将根变回黑色;若祖父非根,将祖父设置为当前节点再进行其他判断
- 叔叔黑色,当前节点是父的右孩子:把父作为当前节点并左旋(旋转时忽视Nil节点),再进行判断
- 叔叔黑色,当前节点是父的左孩子:将”父“设为”黑色“,将”祖父“设为”红色“,以祖父为支点进行右旋
Collection
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
常用方法
add
public boolean add(E e)
把给定的对象添加到当前集合中
若向List系列集合中添加数据,由于List允许元素重复,永远返回true。
若向Map系列集合中添加数据,由于Map不允许元素重复,若当前添加元素不存在,返回true,表示返回成功;若当前添加元素存在,返回false,表示添加失败。
clear
public void clear()
清空集合中所有元素
remove
public boolean remove(E e)
删除集合中指定元素,若有,返回true,表示删除成功;若无,返回false,表示删除失败。
因为Collection中定义的是共性方法,List有索引,Map无索引,所以不能通过索引进行删除,只能通过元素对象进行删除。
contains
public boolean contains(Object obj)
判断当前集合中是否包含给定对象
注:底层是依赖equals方法判断元素是否存在,若集合中存储的是定义对象,需在Javabean类中重写equals方法。
isEmpty
public boolean isEmpty()
判断当前集合是否为空
size
public int size()
判断集合中元素个数/集合的长度
注意事项
remove和contains底层依赖对象的equals方法。
范例
//Test.java
import java.util.ArrayList;
import java.util.Collection;
public class Test{
public static void main(String[] args){
//以多态的形式创建集合,调用单列集合的公共方法
Collection<String> collection=new ArrayList<>();
System.out.println("add方法");
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
System.out.println(collection+"\n");
System.out.println("clear方法");
collection.clear();
System.out.println(collection+"\n");
System.out.println("remove方法");
System.out.println(collection.remove("aaa"));
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
System.out.println(collection.remove("aaa"));
System.out.println(collection+"\n");
System.out.println("contains方法");
System.out.println(collection.contains("bbb"));
System.out.println(collection.contains("aaa"));
Student student1=new Student(19,"张三");
Collection<Student> collection1=new ArrayList<>();
collection1.add(student1);
Student student2=new Student(19,"张三");
System.out.println(collection1.contains(student2)+"\n");
System.out.println("isEmpty方法");
System.out.println(collection.isEmpty()+"\n");
System.out.println("size方法");
System.out.println(collection);
System.out.println(collection1);
System.out.println(collection.size());
System.out.println(collection1.size());
}
}
//Student.java
import java.util.Objects;
public class Student {
private int age;
private String name;
public Student(){}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
}
/*
add方法
[aaa, bbb, ccc]
clear方法
[]
remove方法
false
true
[bbb, ccc]
contains方法
true
false
true
isEmpty方法
false
size方法
[bbb, ccc]
[com.ljsblog.domain12.Student@14ae5a5]
2
1
*/
遍历方式
- 迭代器遍历
- 增强for遍历
- Lambda表达式遍历
迭代器遍历
迭代器在Java之法的类是Iterator,迭代器是集合专用的遍历方式。
获取迭代器:
- Iterator iterator()
- 返回迭代器对象,默认指向当前集合的0索引
常用方法:
- boolean hashNext()
- 判断当前位置是否有元素,有元素,返回true;无元素,返回false
- E next()
- 获取当前位置的元素,并将迭代器对象移向下一个位置
- 若当前位置没有更多的元素可供访问,会出现NoSuchElementException异常
- 在循环过程中next方法最好只调用一次
- default void remove()
- 从基础集合中移除这个迭代器返回的最后一个元素(可选操作)。
范例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test{
public static void main(String[] args){
Collection<String> collection=new ArrayList<>();
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
Iterator<String> iterator=collection.iterator();
while(iterator.hasNext()){
String str=iterator.next();
System.out.println(str);
}
}
}
注:
- 若当前位置没有元素,还要强行获取,则会NoSuchElementException
- 迭代器遍历完毕,指针不会复位,若想重新遍历,需创建新的迭代器对象
- 循环中只能用一次next方法,否则可能会越界
- 迭代器遍历时,不能用集合的方法进行增加或删除。若必须要删除,则可以用迭代器提供的remove方法进行删除。
增强for遍历
JDK5之后出现,内部原理就是一个Iterator迭代器,增强for的底层就是迭代器,为简化迭代器的代码书写的,所有的单列集合和数组才能用增强for进行遍历。
格式:
for(元素的数据类型 变量名:数组或单列集合){
}
注:修改增强for中的变量,不会改变集合中原本的数据。
范例:
import java.util.ArrayList;
import java.util.Collection;
public class Test{
public static void main(String[] args){
Collection<String> collection=new ArrayList<>();
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
for(String s:collection){
System.out.println(s);
}
}
}
Lambda表达式遍历
方法名称:
default void forEach(Consumer<? super T> action)
说明:结合lambda遍历集合
范例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
public class Test{
public static void main(String[] args){
Collection<String> collection=new ArrayList<>();
collection.add("aaa");
collection.add("bbb");
collection.add("ccc");
//匿名内部类
//底层原理:
//遍历集合,得到每个元素
//每个元素,传给accept方法。s依次表示集合中的每一个数据
collection.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//lambda省略版
collection.forEach(s-> System.out.println(s));
}
}
List
Collection的方法List全部继承,List集合因为有索引,所以多了很多索引操作的方法。
常用方法
add
void add(int index,E element)
在此集合的指定位置插入指定的元素
remove
格式1:
E remove(int index)
删除指定索引处的元素,原来索引的上的元素会依次后移,返回被删除的元素
格式2:
boolean remove(Object o)
直接删除元素
注:在调用方法时,若方法出现重载现象,优先调用实参形参一致的方法
set
E set(int index,E element)
修改指定索引处的元素,返回被修改的元素
get
E get(int index)
返回指定索引处的元素
范例
import java.util.ArrayList;
import java.util.List;
public class Test{
public static void main(String[] args){
List<String> list=new ArrayList<>();
System.out.println("add方法");
list.add("aaa");
list.add("bbb");
list.add("ccc");
System.out.println(list+"\n");
System.out.println("remove方法,通过索引删除");
System.out.println(list.remove(1));
System.out.println(list+"\n");
System.out.println("remove方法,优先级判定");
List<Integer> list1=new ArrayList<>();
list1.add(1);
list1.add(2);
list1.add(3);
Integer i=1;
//此处调用的是boolean remove(Object o)方法
list1.remove(i);
System.out.println(list1);
List<Integer> list2=new ArrayList<>();
list2.add(1);
list2.add(2);
list2.add(3);
//此处调用的是E remove(int index)方法
list2.remove(1);
System.out.println(list2);
System.out.println("set方法");
System.out.println(list.set(1,"bbb"));
System.out.println(list+"\n");
System.out.println("get方法");
System.out.println(list.get(1));
}
}
/*
add方法
[aaa, bbb, ccc]
remove方法,通过索引删除
bbb
[aaa, ccc]
remove方法,优先级判定
[2, 3]
[1, 3]
set方法
ccc
[aaa, bbb]
get方法
bbb
*/
List集合遍历方式
- 迭代器遍历:在遍历的过程中需要删除元素,使用迭代器
- 使用迭代器遍历集合的过程中,调用集合对象的添加,删除方法,就会出现并发修改异常ConcurrentModificaException
- 迭代器遍历过程中,不允许使用集合的对象的添加或删除,那就使用迭代器自己的添加和删除方法
- 迭代过程中删除:使用Iterator自带的remove方法
- 迭代过程中添加:使用ListIterator自带的add方法
- 列表迭代器:ListIterator在遍历的过程中需要增加元素,使用列表迭代器
- 增强for遍历:仅仅想遍历,使用增强for或lambda表达式
- Lambda表达式
- 普通for:若遍历时想操作索引,可以使用普通for
范例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class Test{
public static void main(String[] args){
List<String> list=new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
//迭代器遍历
Iterator<String> iterator=list.iterator();
while (iterator.hasNext()){
String str=iterator.next();
if("ddd".equals(str)){
iterator.remove();
}
System.out.println(str);
}
System.out.println(list+"\n");
//增强for遍历
for(String s:list){
System.out.println(s);
}
System.out.println();
//lambda表达式
list.forEach(s-> System.out.println(s));
System.out.println();
//普通for
for (int i = 0; i <list.size() ; i++) {
System.out.println(list.get(i));
}
System.out.println();
//列表迭代器
ListIterator<String> listIterator=list.listIterator();
while (listIterator.hasNext()){
String str=listIterator.next();
if("ccc".equals(str)){
listIterator.add("ddd");
}
System.out.println(str);
}
System.out.println(list);
}
}
/*
aaa
bbb
ccc
ddd
[aaa, bbb, ccc]
aaa
bbb
ccc
aaa
bbb
ccc
aaa
bbb
ccc
aaa
bbb
ccc
[aaa, bbb, ccc, ddd]
*/
ArrayList和LinkedList
ArrayList
- 利用空参创建的集合,在底层创建一个默认长度为0的数组。
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 数组存满时,会扩容1.5倍,若一次添加多个元素,1.5倍装不下,则新创建数组的长度以实际为为准。
LinkedList
底层数据结构是双链表,查询慢,增删快,若操作的是首尾元素,速度也是极快的。
LinkedList本身多出很多直接操作首尾元素的方法。
Set
Set接口中的方法基本上与Collection的API一致。
实现类:
- Hashset:无序、不重复、无索引
- LinkedHashSet:有序,不重复,无索引
- TreeSet:可排序,不重复,无索引
若要数据去重,默认使用HashSet,若要求去重且存取有序,使用LinkedHashSet。
范例:
/*
利用Set系列的集合,添加字符串,并使用多种方式遍历
迭代器
增强for
lambda表达式
*/5
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Set<String> s=new HashSet<>();
System.out.println(s.add("aaa"));
System.out.println(s.add("aaa"));//不可重复
System.out.println(s.add("bbb"));
System.out.println(s.add("ccc"));
System.out.println(s);//打印无序
System.out.println("\n"+"迭代器");
Iterator<String> iterator=s.iterator();
while(iterator.hasNext()){
String s1=iterator.next();
System.out.println(s1);
}
System.out.println("\n"+"for增强遍历");
for(String s1:s){
System.out.println(s1);
}
System.out.println("\n"+"lambda表达式");
s.forEach(s1-> System.out.println(s1));
}
}
/*
true
false
true
true
[aaa, ccc, bbb]
迭代器
aaa
ccc
bbb
for增强遍历
aaa
ccc
bbb
lambda表达式
aaa
ccc
bbb
*/
HashSet
HashSet集合底层采取哈希表存储数据
哈希表和哈希值
哈希表是一种对增删改查数据性能都较好的结构
哈希表组成:
- JDK8之前:数组+链表
- JDK8开始:数组+链表+红黑树
哈希值:
- 根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中,所有对象都可调用,默认使用地址值进行计算
- 一般情况下,会重写hashCode的方法,利用对象内部的属性值计算哈希值
对象的哈希值的特点:
- 若并未重写hashCode方法,不同对象计算出的哈希值是不同的
- 若并重写hashCode方法,不同对象只要属性值相同,计算出的哈希值是一样的
- 在小部分情况下,不同的属性值或不同的地址值计算出来的哈希值也有可能一样。(哈希碰撞)
//Test.java
public class Test {
public static void main(String[] args) {
Student student1=new Student("张三",18);
Student student2=new Student("张三",18);
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
//哈希碰撞
System.out.println("abc".hashCode());
System.out.println("acD".hashCode());
}
}
//Student.java
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
/*
24022538
24022538
96354
96354
*/
底层原理
- 创建一个默认长度16,默认加载因为0.75的数组,数组名为table。
- 加载因即当数组存储元素为16*0.75=12时,会扩容一倍,数组长度变为32
- 根据元素的哈希值跟数组的长度计算出应存入的位置i
- int index=(数组长度-1)&哈希值;
- 判断当前位置是否为null,若是null直接存入
- 若位置不为null,表示有元素,则调用equals方法比较属性值
- 一样,不存;不同:存入数组形成链表
- JDK8以前:新元素存入数组,老元素挂在新元素下面
- JDK8以后:新元素直接挂在老元素下面
注:jdk8以后,当链表长度超过8,且数组长度大于等于64时,自动转换为红黑树。
若集合中存储的是自定义对象,必须重写hashCode和equals方法。
范例
/*
需求:创建一个存储学生对象的集合,存储多个学生对象
使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
*/
//Student.java
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
//Test.java
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
Student student1=new Student("张三",18);
Student student2=new Student("张三",18);
Student student3=new Student("李四",18);
Student student4=new Student("王五",19);
HashSet<Student> hashSet=new HashSet<>();
System.out.println(hashSet.add(student1));
System.out.println(hashSet.add(student2));
System.out.println(hashSet.add(student3));
System.out.println(hashSet.add(student4));
System.out.println(hashSet);
}
}
/*
true
false
true
true
[Student{name = 王五, age = 19}, Student{name = 张三, age = 18}, Student{name = 李四, age = 18}]
*/
LinkedHashSet
有序,不重复,无索引
底层原理
底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存取的顺序。
范例
//Student.java
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
//Test.java
import java.util.LinkedHashSet;
public class Test {
public static void main(String[] args) {
Student student1=new Student("张三",18);
Student student2=new Student("张三",18);
Student student3=new Student("李四",18);
Student student4=new Student("王五",19);
LinkedHashSet<Student> lhs=new LinkedHashSet<>();
System.out.println(lhs.add(student1));
System.out.println(lhs.add(student2));
System.out.println(lhs.add(student3));
System.out.println(lhs.add(student4));
System.out.println(lhs);
}
}
/*
true
false
true
true
[Student{name = 张三, age = 18}, Student{name = 李四, age = 18}, Student{name = 王五, age = 19}]
*/
TreeSet
不重复,无索引,可排序
可排序:按照元素的默认规则(从小到大)排序。
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都比较好。
范例:
import java.util.Iterator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Integer> treeSet=new TreeSet<>();
treeSet.add(99);
treeSet.add(13);
treeSet.add(123);
treeSet.add(113);
treeSet.add(69);
treeSet.add(59);
System.out.println(treeSet);
System.out.println("\n迭代器遍历");
Iterator<Integer> iterator=treeSet.iterator();
while(iterator.hasNext()){
int i=iterator.next();
System.out.println(i);
}
System.out.println("\n增强for遍历");
for(int n:treeSet){
System.out.println(n);
}
System.out.println("\nlambda表达式");
treeSet.forEach(i-> System.out.println(i));
}
}
/*
[13, 59, 69, 99, 113, 123]
迭代器遍历
13
59
69
99
113
123
增强for遍历
13
59
69
99
113
123
lambda表达式
13
59
69
99
113
123
*/
TreeSet集合默认的规则:
- 对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
- 对于字符,字符串类型,按照字符在ASCII码表中的数字升序进行排序
TreeSet的两种比较方式
- 默认排序/自然排序
- 比较器排序
使用原则:默认使用第一种,若第一种不能满足当前需求,则使用第二种。
当方式一和方式二重复时,以方式二为准。
默认排序/自然排序
JavaBean类实现Comparable接口,指定比较规则。
范例:
/*
需求:创建TreeSet集合,并添加4个学生对象
学生对象属性:
姓名,年龄
要求按照学生的年龄进行排序,同年龄按照字母排序
同姓名,同年龄认为是同一个人
*/
//Student.java
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public int compareTo(Student o) {
//this:表示当前要添加的元素
//o:表示已在红黑树存在的元素
//返回值:
//负数:表示当前要添加的元素是小的,存左边
//正数:表示当前要添加的元素是大的,存右边
//0:表示当前添加的元素已存在,舍弃
int i=this.getAge()-o.getAge();
return i==0?this.getName().compareTo(o.getName()):i;
}
}
//Test.java
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Student> treeSet=new TreeSet<>();
Student student1=new Student("zs",23);
Student student2=new Student("ls",19);
Student student3=new Student("bw",19);
Student student4=new Student("zz",25);
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
treeSet.add(student4);
System.out.println(treeSet);
}
}
/*
[Student{name = bw, age = 19}, Student{name = ls, age = 19}, Student{name = zs, age = 23}, Student{name = zz, age = 25}]
*/
比较器排序
创建TreeSet对象时,传送比较器Comparator指定规则。
/*
要求:存入四个字符串,"asadsa""dasddw""rgreg""feg",按照长度排序,若等长则按照首字母排序
*/
//import java.util.Comparator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
/*
TreeSet<String> treeSet=new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int i = o1.length()-o2.length();
return i==0?o1.compareTo(o2):i;
}
});
*/
//o1:表示当前要添加的元素
//o2:表示已在红黑树中存在的元素
//返回值规则跟之前一样
TreeSet<String> treeSet=new TreeSet<>((o1,o2)->{
int i = o1.length()-o2.length();
return i==0?o1.compareTo(o2):i;
});
treeSet.add("asadsa");
treeSet.add("dasddw");
treeSet.add("rgreg");
treeSet.add("feg");
System.out.println(treeSet);
}
}
//[feg, rgreg, asadsa, dasddw]
范例
/*
创建5个学生对象
属性:姓名,年龄,语文成绩,数学成绩,英语成绩
按照总分从高到低输出
若总分一样,按语文排
若语文一样,按数学排
若数学一样,按英语排
若英语一样,按年龄排
若年龄一样,按姓名排
若全部一样,认为是同一学生,不存
*/
//Student.java
package com.ljsblog.domain1;
public class Student implements Comparable<Student>{
private String name;
private int age;
private int chinese_score;
private int math_score;
private int english_score;
public Student() {
}
public Student(String name, int age, int chinese_score, int math_score, int english_score) {
this.name = name;
this.age = age;
this.chinese_score = chinese_score;
this.math_score = math_score;
this.english_score = english_score;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return chinese_score
*/
public int getChinese_score() {
return chinese_score;
}
/**
* 设置
* @param chinese_score
*/
public void setChinese_score(int chinese_score) {
this.chinese_score = chinese_score;
}
/**
* 获取
* @return math_score
*/
public int getMath_score() {
return math_score;
}
/**
* 设置
* @param math_score
*/
public void setMath_score(int math_score) {
this.math_score = math_score;
}
/**
* 获取
* @return english_score
*/
public int getEnglish_score() {
return english_score;
}
/**
* 设置
* @param english_score
*/
public void setEnglish_score(int english_score) {
this.english_score = english_score;
}
public int getSum(){
return chinese_score+math_score+english_score;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", chinese_score = " + chinese_score + ", math_score = " + math_score + ", english_score = " + english_score + ", sum = " + getSum() + "}";
}
@Override
public int compareTo(Student o) {
if(o.getSum()!=this.getSum()){
return o.getSum()-this.getSum();
}else if(o.getChinese_score()!=this.getChinese_score()){
return o.getChinese_score()-this.getChinese_score();
}else if(o.getMath_score()!=this.getMath_score()){
return o.getMath_score()-this.getMath_score();
}else if(o.getEnglish_score()!=this.getEnglish_score()){
//因为总分相等,语文相等,数学相等,英语必然相等,这里的英语可以不写,但还是写上了
return o.getEnglish_score()-this.getEnglish_score();
}else if(o.getAge()!=this.getAge()){
return o.getAge()-this.getAge();
}else{
return o.getName().compareTo(this.getName());
}
}
}
//Test.java
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
TreeSet<Student> treeSet=new TreeSet<>();
Student student1=new Student("one",19,88,100,99);
Student student2=new Student("two",19,88,100,99);
Student student3=new Student("three",20,76,26,48);
Student student4=new Student("four",23,97,46,18);
Student student5=new Student("five",18,65,95,26);
Student student6=new Student("one",19,88,100,99);
treeSet.add(student1);
treeSet.add(student2);
treeSet.add(student3);
treeSet.add(student4);
treeSet.add(student5);
treeSet.add(student6);
for(Student student:treeSet){
System.out.println(student);
}
}
}
/*
Student{name = two, age = 19, chinese_score = 88, math_score = 100, english_score = 99, sum = 287}
Student{name = one, age = 19, chinese_score = 88, math_score = 100, english_score = 99, sum = 287}
Student{name = five, age = 18, chinese_score = 65, math_score = 95, english_score = 26, sum = 186}
Student{name = four, age = 23, chinese_score = 97, math_score = 46, english_score = 18, sum = 161}
Student{name = three, age = 20, chinese_score = 76, math_score = 26, english_score = 48, sum = 150}
*/
使用场景
- 若想集合中元素可重复
- 用ArrayList集合,基于数组的(用的最多)
- 若想集合中元素可重复,且当前的增删操作明显多于查询
- 用LinkedList集合,基于链表的
- 若想对集合中元素去重
- 用HashSet集合,基于哈希表的(用的最多)
- 若想对集合中元素去重,而且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
- 若想对集合中的元素进行排序
- 用TreeSet集合,基于红黑树,后续也可用List集合实现排序
Map
双列集合特点:
- 双列集合一次需存储一对数据,分别为键和值
- 键不能重复,值可以重复
- 键和值是一一对应的,每个键只能找到自己对应的值
- 键和值这个整体称之为键值对或键值对对象,在Java中叫做Entry对象
Map是双列集合的顶层接口,它的功能是全部双列集合都可继承使用的。
体系结构
- Map(接口)
- HashMap (实现类)->LinkedHashMap(实现类)
- Hashtable(实现类)->Properties(实现类)
- TreeMap(实现类)
常用方法
put
格式:
V put(K key,V value)
说明:添加元素。
- 在添加数据时,若键不存在,直接把键值对对象添加到map集合当中,方法返回是null
- 在添加数据时,若键存在,会把原有的键值对对象覆盖,并将被覆盖的值进行返回。
remove
格式:
V remove(Object Key)
说明:根据键删除键值对元素
clear
格式:
void clear()
说明:移除所有键值对元素
containsKey
格式:
boolean containsKey
说明:判断集合是否包含指定的键
containsValue
格式:
boolean containsValue
说明:判断集合是否包含指定的值
isEmpty
格式:
boolean isEmpty()
说明:判断集合是否为空
size
格式:
int size()
说明:集合的长度,也是集合中键值对的个数
范例
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String,String> m=new HashMap<>();
System.out.println("put方法");
m.put("aaa","AAA");
m.put("bbb","BBB");
m.put("ccc","CCC");
System.out.println(m);
String value1=m.put("aaa","sss");
System.out.println(m);
System.out.println(value1);
String value2=m.put("ddd","DDD");
System.out.println(value2);
System.out.println(m);
System.out.println("\nremove方法");
m.remove("bbb");
System.out.println(m);
System.out.println("\ncontainsKey方法");
System.out.println(m.containsKey("aaa"));
System.out.println(m.containsKey("bbb"));
System.out.println("\ncontainsValue方法");
System.out.println(m.containsValue("DDD"));
System.out.println(m.containsValue("AAA"));
System.out.println("\nsize方法");
System.out.println(m.size());
System.out.println("\nisEmpty方法");
System.out.println(m.isEmpty());
System.out.println("\nclear方法");
m.clear();
System.out.println(m.isEmpty());
System.out.println(m);
}
}
遍历方式
- 键找值
- 键值对
- Lambda表达式
键找值
利用keySet方法将所有键放入一个单列集合中,遍历得到每一个键,通过get方法得到每个键对应的值。
keySet方法:
Set keySet()
get方法:
V get(Object key)
范例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Map<String,String> m=new HashMap<>();
m.put("aaa","AAA");
m.put("bbb","BBB");
m.put("ccc","CCC");
Set<String> set=m.keySet();
System.out.println("迭代器遍历");
Iterator<String> it=set.iterator();
while(it.hasNext()){
String str=it.next();
System.out.println(str+" = "+m.get(str));
}
System.out.println("\n增强for");
for(String str:set){
System.out.println(str+" = "+m.get(str));
}
System.out.println("\nlambda");
set.forEach(str-> System.out.println(str+" = "+m.get(str)));
}
}
/*
迭代器遍历
aaa = AAA
ccc = CCC
bbb = BBB
增强for
aaa = AAA
ccc = CCC
bbb = BBB
lambda
aaa = AAA
ccc = CCC
bbb = BBB
*/
键值对
通过entrySet方法获取所有键值对对象,在通过键值对对象调用getkey和getValue获取键和值。
entrySet方法:
Set<Map.Entry<K,V>> entrySet()
getkey方法:
K getKey()
getValue方法:
V getValue()
范例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Map<String,Integer> map=new HashMap<>();
map.put("a",97);
map.put("b",98);
map.put("c",99);
Set<Map.Entry<String,Integer>> entries=map.entrySet();
//若导入import java.util.Map.Entry;
//则可以直接Set<Entry<String,Integer>> entries=map.entrySet();
System.out.println("迭代器");
Iterator<Map.Entry<String,Integer>> it=entries.iterator();
while(it.hasNext()){
Map.Entry<String,Integer> entry=it.next();
String key=entry.getKey();
int value=entry.getValue();
System.out.println(key+" = "+value);
}
System.out.println("\nfor增强");
for(Map.Entry<String,Integer> entry:entries){
String key=entry.getKey();
int value=entry.getValue();
System.out.println(key+" = "+value);
}
System.out.println("\nLambda");
entries.forEach(entry-> System.out.println(entry.getKey()+" = "+entry.getValue()));
}
}
/*
迭代器
a = 97
b = 98
c = 99
for增强
a = 97
b = 98
c = 99
Lambda
a = 97
b = 98
c = 99
*/
Lambda表达式
方法名称:
default void forEach(BiConsumer<? super K, ? super V> action)
说明:结合Lambda遍历Map集合
范例:
import java.util.HashMap;
import java.util.Map;
//import java.util.function.BiConsumer;
public class Test{
public static void main(String[] args){
Map<String,Integer> map=new HashMap<>();
map.put("a",97);
map.put("b",98);
map.put("c",99);
/*
map.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String s, Integer integer) {
System.out.println(s+" = "+integer);
}
});
*/
map.forEach((s,integer)-> System.out.println(s+" = "+integer));
}
}
/*
a = 97
b = 98
c = 99
*/
选择方式
默认:HashMap(效率最高)
若要保证存取有序,LinkedHashMap
若要进行排序:TreeMap
HashMap
- HashMap是Map里的一个实现类,并无额外需要学习的特殊方法,直接使用Map的方法即可
- 特点是由键决定:无序,不重复,无索引
- HashMap和HashSet底层原理一样,都是哈希表结构
- 依赖hashCode和equals方法保证键的唯一
- 若键存储的是自定义对象,需重写hashCode和equals方法;若值存储的是自定义对象,不需重写hashCode和equals方法。
范例一
/*
创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)
存储三个键值对元素,并遍历。
要求:同姓名,同年龄认为是同一个学生
*/
//Student.java
import java.util.Objects;
public class Student{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
//Test.java
import java.util.HashMap;
import java.util.Set;
public class Test {
public static void main(String[] args) {
HashMap<Student,String> hm=new HashMap<>();
Student stu1=new Student("zs",18);
Student stu2=new Student("lisi",19);
Student stu3=new Student("zs",18);
String s1="河北";
String s2="河南";
String s3="北京";
hm.put(stu1,s1);
hm.put(stu2,s2);
hm.put(stu3,s3);
System.out.println("lambda");
hm.forEach((stu,s)-> System.out.println(stu+" = "+ s));
System.out.println("键找值");
Set<Student> set=hm.keySet();
for(Student stu:set){
System.out.println(stu +" = "+hm.get(stu));
}
System.out.println("键值对");
Set<HashMap.Entry<Student,String>> entries=hm.entrySet();
for(HashMap.Entry<Student,String> entry:entries){
System.out.println(entry.getKey()+ " = "+entry.getValue());
}
}
}
/*
lambda
Student{name = lisi, age = 19} = 河南
Student{name = zs, age = 18} = 北京
键找值
Student{name = lisi, age = 19} = 河南
Student{name = zs, age = 18} = 北京
键值对
Student{name = lisi, age = 19} = 河南
Student{name = zs, age = 18} = 北京
*/
范例二
/*
某个班级80个学生,现需组成春游活动
班长提供四个景区A,B,C,D
每个学生只能选择一个景区,并统计出哪个景区想去的人数最多
*/
package com.ljsblog.domain1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
import java.util.Set;
public class Test {
public static void main(String[] args) {
String[] arr={"A","B","C","D"};
ArrayList<String> list=new ArrayList<>();
Random random=new Random();
for(int i=0;i<80;i++){
int index=random.nextInt(arr.length);
list.add(arr[index]);
}
HashMap<String,Integer> hm=new HashMap<>();
for(String name:list){
if(hm.containsKey(name)){
int c=hm.get(name);
c++;
hm.put(name,c);
}else{
hm.put(name,1);
}
}
Set<HashMap.Entry<String,Integer>> entries=hm.entrySet();
int max=0;
for(HashMap.Entry<String,Integer> entry:entries){
int value=entry.getValue();
if(max<value){
max=value;
}
}
System.out.println(hm);
System.out.println("最大为"+max);
for(HashMap.Entry<String,Integer> entry:entries){
if(max==entry.getValue()){
System.out.println(entry.getKey());
}
}
}
}
/*
{A=19, B=18, C=21, D=22}
最大为22
D
*/
LinkedHashMap
由键决定:有序,不重复,无索引
底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表的机制记录存取的顺序。
范例:
import java.util.LinkedHashMap;
public class Test {
public static void main(String[] args) {
LinkedHashMap<String ,Integer> lhm=new LinkedHashMap<>();
lhm.put("a",96);
lhm.put("a",97);
lhm.put("b",98);
lhm.put("c",99);
System.out.println(lhm);
}
}
/*
{a=97, b=98, c=99}
*/
TreeMap
TreeMap跟TreeSet底层原理一样,都是红黑树结构的。
由键决定特性:不重复,无索引,可排序。
可排序:对键进行排序、
注:默认按照键的从小到大进行排序,也可自己规定键的排序规则
代码书写的两种排序规则:
- 实现Comparable接口,指定比较规则。
- 创建集合时传递Comparator比较器对象,指定比较规则
范例一
/*
键:整数表示id
值:字符串表示商品名称
要求:按照id的升序排序,按照id的降序排序
*/
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
//默认升序
TreeMap<Integer,String> tm1=new TreeMap<>();
tm1.put(6,"足球");
tm1.put(5,"篮球");
tm1.put(10,"橄榄球");
System.out.println(tm1);
//降序
TreeMap<Integer,String> tm2=new TreeMap<>((o1, o2)->o2-o1);
tm2.put(7,"足球");
tm2.put(6,"篮球");
tm2.put(10,"橄榄球");
System.out.println(tm2);
}
}
范例二
/*
键:学生对象(姓名和年龄)
值:籍贯
要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排序,同姓名通年龄视为同一人
*/
//Student.java
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public int compareTo(Student o) {
int i=this.getAge()-o.getAge();
return i==0?this.getName().compareTo(o.getName()):i;
}
}
//Test.java
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
TreeMap<Student,String> tm=new TreeMap<>();
Student s1=new Student("zs",19);
Student s2=new Student("ww",18);
Student s3=new Student("zs",19);
Student s4=new Student("ls",19);
tm.put(s1,"河北");
tm.put(s2,"河南");
tm.put(s3,"湖南");
tm.put(s4,"湖北");
System.out.println(tm);
}
}
范例三
/*
统计字符串"aababcabcdabcde"每个字符出现的次数,并按以下格式输出:
a(5)b(4)c(3)d(2)e(1)
*/
import java.util.TreeMap;
public class Test {
public static void main(String[] args) {
String str="aababcabcdabcde";
TreeMap<Character,Integer> tm=new TreeMap<>();
for(int i=0;i<str.length();i++){
Character c=str.charAt(i);
if(tm.containsKey(c)){
int count=tm.get(c);
count++;
tm.put(c,count);
}else {
tm.put(c,1);
}
}
StringBuilder sb=new StringBuilder();
tm.forEach((character, integer)->sb.append(character).append("(").append(integer).append(")"));
System.out.println(sb);
}
}
/*
a(5)b(4)c(3)d(2)e(1)
*/
可变参数
可变参数本质上是一个数组。
作用:在形参中接收多个数据。
格式:
数据类型…参数名称
注意事项:
- 形参列表中可变参数只有一个
- 可变参数必须放在形参列表的最后面
范例:
public class Test {
public static void main(String[] args) {
System.out.println(sum(1, 2, 3, 4));
System.out.println(sum(1, 2, 3, 4, 5));
System.out.println("大于7的数字有"+compare(7, 11, 9, 4, 6, 7)+"个");
}
public static int sum(int...args){
int s=0;
for (int num:args){
s+=num;
}
return s;
}
public static int compare(int num,int...args){
int c=0;
for(int i=0;i<args.length;i++){
if(args[i]>num){
c++;
}
}
return c;
}
}
/*
10
15
大于7的数字有2个
*/
Collections
java.util.Collections是集合工具类。
作用:Collections不是集合,而是集合的工具类。
常用方法
addAll
格式:
public static boolean addAll(Collection c,T…elements)
说明:
批量添加元素
shuffle
格式:
public static void shuffle(List<?> list)
说明:
打乱List集合元素的顺序
范例
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
System.out.println("addAll方法");
Collections.addAll(list, "saf","fasf","df","fsa");
System.out.println(list+"\n");
System.out.println("shuffle方法");
Collections.shuffle(list);
System.out.println(list);
}
}
/*
addAll方法
[saf, fasf, df, fsa]
shuffle方法
[saf, df, fsa, fasf]
*/
范例
例1:
/*
班级里有一些学生。
要求:被点到的学生不会再被点到。
若班级中所有学生点完,则开始下一轮点名
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
Collections.addAll(list,"张三","李四","王五","甲","乙","丙","丁");
ArrayList<String> list1=new ArrayList<>();
int i=1;
Scanner s=new Scanner(System.in);
Random r=new Random();
while(true){
System.out.print("若想退出输入0,非零继续循环,请输入:");
String in=s.nextLine();
if(in.compareTo("0")==0){
break;
}
System.out.println("第"+i+"轮点名");
list1.addAll(list);
while(list1.size()!=0){
int num=r.nextInt(list1.size());
System.out.println(list1.get(num));
list1.remove(num);
}
list1.clear();
i++;
}
}
}
例2:
/*
定义一个map集合,键用表示省份名称province,值表示市city,但是市有多个,添加完毕后,遍历结果格式如下:
江苏省=南京市,扬州市,苏州市
湖北省=武汉市,孝感市,宜昌市
河北省=唐山市,邢台市,廊坊市
*/
import java.util.*;
public class Test {
public static void main(String[] args) {
ArrayList<String> city1=new ArrayList<>();
city1.add("南京市");
city1.add("扬州市");
city1.add("苏州市");
ArrayList<String> city2=new ArrayList<>();
city2.add("武汉市");
city2.add("孝感市");
city2.add("宜昌市");
ArrayList<String> city3=new ArrayList<>();
city3.add("唐山市");
city3.add("邢台市");
city3.add("廊坊市");
HashMap<String,ArrayList<String>> m=new HashMap<>();
m.put("江苏省",city1);
m.put("湖北省",city2);
m.put("河北省",city3);
Set<Map.Entry<String,ArrayList<String>>> entries=m.entrySet();
for(Map.Entry<String,ArrayList<String>> entry:entries){
StringJoiner sj=new StringJoiner(",");
ArrayList<String> value = entry.getValue();
for(String s:value){
sj.add(s);
}
System.out.println(entry.getKey()+"="+sj);
}
}
}
/*
江苏省=南京市,扬州市,苏州市
湖北省=武汉市,孝感市,宜昌市
河北省=唐山市,邢台市,廊坊市
*/
不可变集合
不可变集合:集合不可变,不能添加,修改,删除。
格式
在List,Set,Map接口中,均存在静态of方法,可获得一个不可变的集合。
- 方法:static List of(E…element)
- 创建一个具有指定元素的List集合对象
- 方法:static Set of(E…element)
- 创建一个具有指定元素的set集合对象,元素不能重复
- 方法:static <K,V> Map<K,V> of(E…element)
- 创建一个具有指定元素的Map集合对象,元素不能重复,键值对数量最多10个,超过10个用ofEntries方法
Properties
properties是一个双列集合,拥有Map集合的所有特点,本身也有一些特有方法,可把集合的数据按照键值对的形式写到配置文件(后缀.properties)当中,也可把配置文件中的数据,读取到集合中。
propertise集合没有泛型,可以添加任意类型数据,但一般添加字符串类型数据。
常用方法
store
格式1:
public void store(Writer writer,String comments)
格式2:
public void store(OutputStream out,String comments)
说明:把集合中的数据以键值对的形式写入到文件中,第二个参数为写入的注释
load
格式1:
public void load(Reader reader)
格式2:
public void load(InputStream inStream)
说明:读取本地文件里的数据。
范例
import java.io.*;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws IOException {
Properties p1=new Properties();
p1.put("user","zhangsan");
p1.put("password","123456");
BufferedWriter bw=new BufferedWriter(new FileWriter("a.properties"));
p1.store(bw,"用户和密码");
bw.close();
Properties p2=new Properties();
BufferedReader br=new BufferedReader(new FileReader("a.properties"));
p2.load(br);
System.out.println(p2);
}
}
/*a.properties内容
#用户和密码
#Tue Apr 04 17:00:17 CST 2023
password=123456
user=zhangsan
*/
/*打印内容
{password=123456, user=zhangsan}
*/