java集合

发布时间 2023-03-26 19:24:58作者: wdadwa

集合的体系结构

集合介绍

集合是一个内容可变的容器,集合长度会根据存储情况进行自动扩容

集合可以存储引用数据类型,但是在存储基本数据类型的时候需要通过存储包装类来存储引用数据类型

集合主要分为两类:

  • Collection单列集合
  • Map双列集合

单列集合:在添加数据的时候每次只能添加一个数据的集合

双列集合:在添加数据的时候每次可以添加两个数据的集合

示意图如下:

Collection体系

Collection集合体系如下图:

  1. List系列集合:添加的元素是有序的,可重复的,有索引的

    有序:存放和拿取得顺序一致

    可重复:集合中存储得元素可重复

    有索引:可以通过索引获取集合中的每一个元素

  2. Set系列集合:添加的元素是无序,不重复,没有索引的

    无序:存放和拿取得顺序不一致

    不重复:集合中得元素是不可重复得

    没有索引:不能通过索引获取集合中的每一个元素

Collection

Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

即Collection接口存放的方法都是所有单列集合中共有的方法

Collection常用方法

方法名 说明
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中的所有元素
public boolean remove(E e) 把给定的对象在当前集合中删除,删除失败返回false
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数/集合的长度

注意要点:

  1. Collection是接口,不能创建这玩意的对象而是创建它的实现类的对象

  2. add方法中

    如果我们往List系列添加元素那么返回值永远为true

    如果我们往Set系列添加元素并且这个元素集合中已经存在,那么会返回false

  3. contains方法在底层是依赖equals方法判断对象是否一致的

    如果我们是自己定义的对象并且我们没有在这个对象中重写equals方法,那么默认使用Object类中的equals方法进行判断,而Object方法中的equals方法是判断地址值的。

    解决方法:在自定义的类中重写equals方法即可

    import java.util.ArrayList;
    import java.util.Collection;
    public class Main {
        public static void main(String[] args){
            Collection<User> arr=new ArrayList<>();
            User u1=new User(1,2);
            User u2=new User(3,4);
            arr.add(u1);
            System.out.println(arr.contains(u2));//如果没有在User类中重写equals方法那么调用的就是Object类中的equals就返回false
        }
    }
    
    

Collection系列集合的遍历方式

之所以用新的遍历方式是因为在Collection系列集合中存在Set系列集合,Set系列集合的特点有无索引!故使用新的遍历方式。

迭代器遍历

迭代器最大的特点:不依赖索引

迭代器在Java中的类是Iterator迭代器是集合专用的遍历方式

  • Collection集合获取迭代器

    ArrayList<String> list=new ArrayList<>();
    Iterator<String> it=list.iterator();
    
  • Iterator中常用的方法

    方法名 说明
    boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有返回false
    E next() 获取当前位置的元素,并且迭代器对象移动到下一个位置
    void remove() 删除当前迭代器指向的元素
  • 举例

    ArrayList<String> list=new ArrayList<>();
    list.add("a");
    list.add("aa");
    list.add("aaa");
    list.add("aaaa");
    Iterator<String> it=list.iterator();//获取当前集合的Iterator对象,需要注意迭代器的E和集合的E要一致
    while(it.hasNext()){//hasNext是判断当前位置(初始为0)是否有元素!!
    	System.out.println(it.next());//next是获取当前位置的元素的内容!并将迭代器移动到下一个(0->1)
    }
    

    好吧学过数据结构的都知道,这玩意底层就链表。。。迭代器可以看做c语言的指针

    hasNext不要看到有next就认为是判断下一个元素!!!

    注意事项:

    1. 如果迭代器已经获取完了最后一个元素,我们再次指向next方法,会报错NoSunchElementException

    2. 迭代器遍历完吧,指针不会复位!

      如果还想再次遍历只能再次获取迭代器对象(因为底层是单向链表)

    3. 循环中只能使用一个next方法

    4. 迭代遍历的时候,不能用集合的方式进行增加或删除!

增强for遍历

  • 增强for的底层就是迭代器,为了简化迭代器的书写!
  • 它是在JDK5之后出现的,其内部原理就是一个Iterator迭代器
  • 所有的单列集合数组才能使用增强for进行遍历

格式:

for(元素类型 变量名 : 数组或集合){
    
}
for(String s:list){
    sout(s);
}
//s其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
//idea可以使用 集合名.for来快捷键生成

小细节:

  1. 修改增强for中的变量,不会改变集合中原本的数据

    for(String s:list){
        s="q";
    }
    list内容不会改变!
    

Lambda表达式遍历

得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单,更直接的遍历集合的方式!

方法名 说明
default void forEach(Consumer<? super T> action): 结合lambda遍历集合
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("aa");
list.add("aaa");
list.add("aaaa");
list.forEach(new Consumer<String>(){//Consumer是一个接口,并且是函数式接口!
    @Override
    //这里的s就依次表示集合中的每个数据
    public void accept(String s){
        System.out.println(s);//这里的方法体就代表我们要对集合进行的操作
    }
});
//写Lambda表达式的时候如果不太熟可以先写匿名内部类,然后删减多余部分即可!
list.forEach(s->System.out.println(s));//简化写法

forEatch方法的底层:遍历集合依次得到每一个元素,每得到一个集合中的元素就会把数据传递给我们自己写的accept方法

List系列的集合

  • List系列的集合是Collection单列集合中的一种,故Collection系列的所有方法List系列都可以使用就不在常用方法中写出了!
  • List集合因为有索引,所有多了很多对索引操作的特有的方法!
方法名 说明
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引的元素,返回被删除的元素
E set(int index,E element) 修改指定索引的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素

细节:

  1. add方法:添加后,原来索引上的元素会依次往后移!

  2. remove方法:存在删除索引和删除指定元素的方法一个是重载Collection的一个是Collection的方法

    list.remove(1)此时是删除1索引上的元素!而不是元素1

    理由:在调用方法的时候,如果方法出现了重载现象,优先调用实参和形参一致的那个方法!

    解决方法:手动装箱

public class Main {
    public static void main(String[] args){
        List<Integer> list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.remove(1);//删除的是1索引内容
        System.out.println(list);
        Integer i=Integer.valueOf(1);//手动装箱
        list.remove(i);//删除的是元素1
        System.out.println(list);
    }
}

List集合的五种遍历方式

List是继承于Collection接口,故List也存在Collection的三种遍历方式,又List存在索引,故List也有自己特有的两种遍历方式

  1. 迭代器遍历
  2. 列表迭代器遍历
  3. 增强for遍历
  4. Lambda表达式遍历
  5. 普通for循环(因为List存在索引)

五种遍历方式的举例:

public class Main {
    public static void main(String[] args){
        List<Integer> list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        //1.迭代器
        Iterator<String> it=list.iterator();
        while(it.hasNext()){
            String str=it.next();
            System.out.println(str);
        }
        //2.增强for
      	for(String s:list){
            System.out.println(s);
        }
        //3.Lambda表达式
        list.forEach(s->System.out.println(s));
        //4.普通for
        for(int i=0;i<list.size();i++){
            String s=list.get(i);
            System.out.println(s);
        }
    }
}
  • 列表迭代器:是迭代器的子接口(迭代器的方法它都可以使用)

  • 获取列表迭代器

    List<String> arr=new ArrayList<>();
    ListIterator<Stirng> it=arr.listIterator();
    
  • 常用方法:

方法名 说明
boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有返回false
E next() 获取当前位置的元素,并且迭代器对象移动到下一个位置
void remove() 删除当前迭代器指向的元素
void set(E e) 修改当前迭代器指向的元素
void add(E e) 给指针指向的元素添加元素在下一个位置上!
public class Main {
    public static void main(String[] args){
        List<String> list=new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        ListIterator<String> it = list.listIterator();
        while(it.hasNext()){
            String i=it.next();
            if("bbb".equals(i)){
                it.add("qqq");
            }
        }
        System.out.println(list);
    }
}

迭代器遍历:在遍历的过程中需要删除索引,使用迭代器遍历

列表迭代器:在遍历的过程中需要添加元素,使用列表迭代器

增强for遍历,Lambda表达式:只是想遍历,就用这两个

普通for:如果遍历的时候需要操作索引,使用普通for遍历

ArrayList集合

  • ArrayList集合是Collection系列集合中的List系列集合的实现类

    所有Collection和List中的方法在ArrayList中可以直接使用

    Array代表数组,List代表List系列。ArrayList意思就是底层是数组的List系列集合

  • 创建方法

    ArrayList<String> list=new ArrayListM<>();
    
  • 常用方法

    ArrayList的常用方法就是Collection的常用方法+List的常用方法

    ArrayList<String> list1=new ArrayListM<>();
    list1.add("aaa");
    list1.add("bbb");
    list1.add("ccc");
    ArrayList<String> list2=new ArrayListM<>();
    list2.addAll(list1);//直接将list1集合的全部内容添加到list2中
    

LinkedList集合

  • 底层数据结构是双链表,查询慢,增删块,但是如果操作的是首位元素,速度也是很快的。

  • LinkedList本身多了很多直接操作首位元素的特有API

  • LinkedList是Collection和List的实现类,这两个类的方法LinkedList都有,所有记住特有的方法即可

    Linked链表,List代表List系列。LinkedList意思就是底层是链表的List系列集合

LinkedList创建方法

LinkedList<String> list=new LinkedList<>();

LinkedList独有的方法

方法名 说明
public void addFirst(E e) 在该列表的开头插入指定元素
public void addLast(E e) 将指定元素插入该列表的末尾
public E getFirst() 返回此列表的第一个元素
public E getLast() 返回此列表的最后一个元素
public E removeFirst() 从该列表中删除第一个元素并返回该元素
public E removeLast() 从该列表中删除最后一个元素并返回该元素

泛型

泛型介绍

  • 泛型:是JDK5中引用的特性,可以在编译阶段约束操作的数据类型,并检查。

​ 泛型的格式:<数据类型>

​ 注:泛型只能支持引用数据类型

  • JDK5引用泛型的原因。

​ 如果没有给集合指定类型,默认认为所有的数据类型都是Object类型。

​ 这样在获取数据的时候,无法使用它特有的行为。

  • 泛型的好处

    统一了数据的类型

    把运行期间的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定。

扩展:Java中的泛型是伪泛型

解释:

​ Java的泛型只是在编译阶段检查集合的数据类型,在数据存入集合后,数据在集合中还是Object类型。

​ 当取出数据的时候,Java会在底层将取出的数据的数据类型强制转化为泛型限定的那个数据类型。

​ 在编写.java文件的时候,泛型是存在的,但是当,java文件编译为.class文件的时候,泛型会消失。这个过程叫泛型的擦除。

image

泛型细节

  1. 泛型中不能写基本数据类型

    因为数据在集合中是转化为Object类型存储的,基本数据类型不能转化为Object类型

  2. 指定泛型的具体类型后,传递数据时,可以传入该类类型或其子类类型。

  3. 如果不写泛型,类型默认是Object

泛型可以写在:类后面,方法上面,接口后面

写在类后面-->泛型类

写在方法上面-->泛型方法

写在接口后面-->泛型接口

泛型类

使用场景:当一个类中,某个变量的数据类型无法确定时,就可以定义带有泛型的类。

格式:

修饰符 class 类名<类型1,类型2,类型3...>{
    
}
public class ArrayList<E>{
    //这里的E表示不确定的数据类型,只有创建该类对象时我们手动指定这个E的类型
    //这里的E可以理解为变量,但不是用来记录数据的,而是记录数据类型的,可以写T,E,K,V等表示
    //当我们定义了泛型类后,这个E就代表一种数据类型了!!可以和其他数据类型一样使用。
     Object[] obj=new Object[10];
    int size;
    public boolean add(E e){
        obj[size]=e;
        size++;
        return true;
    }
    public E get(int index){
        return (E)obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}
//我们在创建这个类的对象的时候需要手动指定这个E的类型!
ArrayList<String> list=new ArrayList<>();//这里手动指定E为String类型了!

泛型方法

使用场景:当方法中形参类型不确定的时候,可以使用泛型

格式:

修饰符 <类型1,类型2,类型3...> 返回值类型 方法名(类型 变量名){    
}

例:

public class ArrayList<E>{
    public boolean add(E e){//类名后面定义的泛型
        obj[size]=e;
        size++;
        return true;
    }
}
public class ArrayList{
    public <E> boolean add(E e){//在方法上面声明的泛型,这个泛型只有本方法可以使用!
        obj[size]=e;
        size++;
        return true;
    }
}

如果只是单一的一个方法不知道形参类型,建议使用泛型方法。

泛型方法的类型是当方法被调用的时候确定的!

泛型接口

使用场景:当一个接口中数据类型不确定的时候使用

格式:

//定义单个泛型
修饰符 interface 接口名<类型>{
    
}
//定义多个泛型
修饰符 interface 接口名<类型1,类型2,类型3...>{
    
}

使用方式

//泛型接口
public interface text<E>{
   
}
//1.实现类给出具体的类型
public arry implements text<String>{
    
}
//2.实现类延续泛型接口的泛型,当创建实现类对象的时候再确定泛型类型
public arry<E> implements text<E>{
    
}

泛型的继承和通配符

  • 泛型不具备继承性,但数据具备继承性。
import java.util.ArrayList;

public class Main {
    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{}

虽然上述method方法可以使用泛型方法来解决但是,利用泛型方法会出现问题,该方法可以接收任意的数据类型。

一般的希望:本方法虽然不确定类型,但以后希望只能传递 Ye Fu Zi这种具备继承结构的类型

解决方法:使用泛型的通配符

通配符?表示不确定的类型,可以进行类型的限定!

? extends E//表示可以传递E或者E的所有子类类型
? super E//表示可以传递E或者E的所有父类类型   
? //表示可以接收所有类型

故上面问题可写出

public class Main {
    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);
    }
    public static void method1(ArrayList<? extends Ye> list){//这个表示只能接收Ye类和Ye类的子类
    }
    public static void method2(ArrayList<? super Zi> list){//这个表示只能接收Zi类和Zi类的父类
    }
    public static void method2(ArrayList<?> list){//这个表示可以接收所有数据类型
    }
}
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}

应用场景:

  1. 如果我们在定义类,方法,接口的时候,类型无法确定,就可以定义泛型类,泛型方法,泛型接口

  2. 如果类型不确定,但是能找到只能传递某个继承体系中的数据,就可以使用泛型的通配符了!

    通配符关键的:可以限定类的范围

  3. 泛型的通配符一般是用在方法中

  4. 通配符也可用做集合中

    ArrayList<? super Number> list=new ArrayList<>();
    

Set系列集合

  • Set系列集合添加的元素是无序不重复无索引

    无序:存和取得顺序不一致,即:存入1,2,3可能取出2,3,1

    不存放:集合内元素不重复

    无索引:没有带索引的方法,不能用普通for循环遍历,无法使用索引获取元素

  • Set集合的实现类:

    HashSet:无序,不重复,无索引

    LinkedHashSet:有序,不重复,无索引

    TreeSet:可排序,不重复,无索引

  • Set接口是基础Collection接口的,故基本方法和Collection一致

  • Set集合可以用Collection的三种遍历方式进行遍历

  • Set集合的add方法如果第二次添加相同的元素结果会返回false

HashSet

只需记住HashSet的特点:无序不可重复无索引的即可。

HasSet并无额外的方法

HashSet底层采用哈希表存储数据,哈希表是一种对于增删改查数据性能都比较好的数据结构

HashSet底层:

  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树

数据存储是无序的:数据存入位置公式int index=(数组长度-1) & 哈希值

哈希值:

  • 根据hashCode方法计算出的int类型的整数
  • 该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
  • 一般情况下会重写hashCode方法,利用对象内部的属性值计算哈希值

哈希值特点

  • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
  • 如果重写了hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
  • 在小部分情况下,不同属性值或不同地址值计算出的哈希值也可能一样(哈希碰撞)

HashSet添加元素的情况

image

  • 扩容时机:当数组内元素个数为:当前数组长度*默认加载因子16*0.75=12的时候扩容为原数组的两倍

  • 当链表长度>8且数组长度>64,链表就会转化为红黑树

    image

  • 注意点:如果HashSet存储的自定义对象,必须重写hashCode和equals方法!

    重写hashCode目的是想根据属性值去计算哈希值

    重写equals目的是想比较对象内部属性

    Object类中的hashCode和equals都是通过地址值进行计算和比较的一般不用

  • HashSet存取顺序不一致:因为是根据哈希值存入的,通过数组0索引开始查,每个数据的哈希值不一致,不可能按照顺序存入

  • HashSet为何不用索引:因为哈希表用上了链表和红黑树,无法定义同一个数组下哪一个链表或红黑树节点为0索引,故不用

  • HashSet利用什么机制去重的:利用hashCode得到哈希值从而确定当前元素在数组中的位置,利用equals比较对象内部的属性值是否相同。这就是为何自定义对象的时候需要重写方法

LinkedHashSet

  • LinkedHashSet是HashSet的子类,具有全部的HashSet的方法,不需要多记其API只需记住特性即可
  • 特性:有序,不重复,无索引

LinkedHashSet会保证数据的存入和取出的顺序一致!

  • 原理:底层数据结构依旧是哈希表,只是每个元素又额外多了一个双链表机制记录存储的顺序

image

8索引添加的是第一个元素,0索引是添加的最后一个元素,两个单独的指针代表头节点和尾节点

当要求数据去重且存取有序的时候才使用LinkedHashSet。要不然默认使用HashSet

因为LinkedHashSet效率较低

TreeSet

  • LinkedHashSet是Set接口的子类,具有全部的Set接口的方法,不需要多记其API只需记住特性即可

  • 特性:可排序,不重复,无索引

    可排序:默认情况按照从小到达排序

  • TreeSet底层是基于红黑树的数据结构实现排序的,增删改查的性能都比较好

    因为TreeSet底层是红黑树,故不需要重写equals方法和hashCode方法在自定义类中,但是要指定排序规则

  • TreeSet集合默认的规则

    1. 对于数值类型,Integer,Double,默认按照从小到大排序

    2. 对于字符,按照字符在ASCII码表中的数字升序排序

    3. 对于字符串:比如说aaa和ab,从第一个字母开始比较,如果第一相同就比较第二个,在比较的过程中只要有一个字母更大后面就不需要再次比较了,这就是aaa在ab的前面

      ab和aba,因为第三个字母不存在故aba在ab的后面、

    4. 对于自定义的类型,需要手动添加排序规则!

    TreeSet的两种比较方式

    1. 默认排序/自然排序:javabean类实现Comparable接口指定比较规则
    public class Student implements Comparable<Student>{//实现接口
        int age;
        String name;
        @Override//重写接口中的方法
        public int compareTo(Student o) {
            //指定排序规则
            //只看年龄,按照年龄的升序排序
            return this.getAge()-o.getAge();
            //this表示当前要添加的元素,o表示在已经在红黑树中存在的元素
            //返回值:
            //1.负数:认为要添加的元素是小的,存左边
            //2.正数:认为要添加的元素是大的,存右边
            //3.0:认为要添加的元素已经存在,舍弃
        }
    }
    
    1. 比较器排序:创建TreeSet对象的时候,传递比较器Comparator指定规则

      使用原则:默认使用第一种,如果第一种无法满足需求才使用第二种

    例:存入四个字符串"c","ab",”df“,”qwer",按照长度排序,长度一致按照首字母排序

    import java.util.*;
    public class Main {
        public static void main(String[] args) {
            TreeSet<String> ts=new TreeSet<>(new Comparator<String>() {
                @Override
                //o1当前要添加的元素,o2已经在红黑树存在的元素
                //返回规则一样
                public int compare(String o1, String o2) {
                    int i=o1.length()-o2.length();
                    i= i== 0 ? o1.compareTo(o2):i;//比较长度,如果长度一致使用默认排序规则compareTo,否则以长度为准
                    return i;
                }
            });
            //这个Comparator接口是函数式接口,可以用Lambda表达式简化
            TreeSet<String> ts=new TreeSet<>((o1,o2)->{
                int i=o1.length()-o2.length();
                i= i== 0 ? o1.compareTo(o2):i;
                return i;
            });
            ts.add("c");
            ts.add("ab");
            ts.add("df");
            ts.add("qwer");
            System.out.println(ts);
        }
    }
    

    如果两种方式都存在,方式二优先使用

Map

Map简述

  • 双列集合特点:

    1. 双列集合一次需要存一对数据,分为键和值

    2. 键不能重复,值可以重复

    3. 键和值是一一对应的,每一个键只能找到自己对于的值

    4. 键+值这个整体,叫键值对或键值对对象,在Java中交Entry对象

    5. Map集合体系如下图:

  • Map系列集合常用API

    1. Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的

      方法名 说明
      V put(K key,V value) 添加元素
      V remove(Object key) 根据键删除键值对元素
      void clear() 移除所有键值对元素
      boolean containsKey(Object key) 判断集合是否包含指定的键
      boolean containsValue(Object value) 判断集合是否包含指定的值
      boolean isEmpty() 判断集合是否为空
      int size() 集合的长度,即集合中键值对的个数
    2. put方法的细节:

      在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合中,返回null

      在添加数据的时候,如果键是存在的,那么会把原有的数据覆盖,并把被覆盖的值进行返回

    3. remove方法细节:

      删除后返回被删除的值

Map系列集合遍历方式

键找值方式遍历

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        map.put("a","A");
        map.put("b","B");
        map.put("c","C");
        map.put("d","D");
        //获取所有的键,把键放入单列集合中
        Set<String> keys = map.keySet();//keySet方法就是获取所有的key并返回Set集合
        for (String key : keys) {//Set集合可以使用的三种遍历方式,Lambda,增强for,迭代器iterator
            String value = map.get(key);//Map系列集合的get方法,通过key获取value
            System.out.println(key+"="+value);
        }
        //可以简化,不需要keys这个变量
        for (String key : map.keySet()) {
            String value = map.get(key);
            System.out.println(key+"="+value);
        }
    }
}

键值对方式遍历

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        map.put("a","A");
        map.put("b","B");
        map.put("c","C");
        map.put("d","D");
        //通过entrySet方法获取键值对对象
        //Entry是Map接口中的内部接口
        //可以通过外部接口.内部接口方式使用内部接口,也可也通过import java.util.Map.Entry方式导入
        //这样就直接Entry<K,V>即可
        Set<Map.Entry<String, String>> entries = map.entrySet();
        //通过entries这个单列集合获取键值对对象内部的k和v
        //因为是set集合,可以使用set集合的三种遍历方式,这里暂时采用Lambda方式,因为最简单
        entries.forEach(s-> System.out.println(s.getKey()+"="+s.getValue()));
        //getKey是键值对对象的方法,获取key
        //getValue是键值对对象的方法,获取value
    }
}

Lambda表达式方式遍历

使用Map集合中下列方法遍历元素

方法名 说明
default void forEach(BiConsumer<? suoer K,? super V> action) 结合Lambda表达式遍历Map集合
import java.math.BigDecimal;
import java.util.*;
import java.util.function.BiConsumer;

public class Main {
    public static void main(String[] args) {
        Map<String,String> map=new HashMap<>();
        map.put("a","A");
        map.put("b","B");
        map.put("c","C");
        map.put("d","D");
        //这里写内部类形式方便理解
        map.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String key, String value) {
                System.out.println(key+"="+value);
            }
        });
        //简化为Lambda表达式
         map.forEach((key,value)->System.out.println(key+"="+value));
    }
}

forEatch方法的底层:

就是理由第二种方式进行遍历,依次得到每一个键和值

再调用accept方法

HashMap

  • HashMap特点:

    1. HashMap是Map里面的一个实现类

    2. 没有额外需要学习的特有方法,直接使用Map中的方法即可

    3. 特点都是由键决定的:无序,不重复,无索引(这里都是关于键的特性,值无要求)

    4. HashMap和HashSet底层原理是一模一样的,都是采用哈希表结构

      存入的方式和HashSet也一样,只不过HashMap是根据键进判断的

    5. 如果存储的是自定义对象,需要重写hashCode和equals方法

    6. 如果值存储的是自定义对象,不需要重写

    7. 依赖hashCode和equals方法保证键的唯一

    LinkedHashMap

  • LinkedHashMap特点:

    1. 由键绝对:有序,不重复,无索引。
    2. 原理:和LinkedHashSet一致。。。
    3. 方法和Map方法一样,不需要额外记。。

TreeMap

  • TreeMap和TreeSet一样都是红黑树结构

  • 由键决定:不重复,无索引,可排序

    可排序:对键进行排序

  • 这玩意和TreeSet唯一的区别是多了个Value其他用法和TreeSet一致,只需看TreeSet即可

  • 默认按照键的从小到大排序,也可自定义排序规则

    排序规则实现方式和TreeSet一样

    1. 在自定义类中实现Comparable接口,指定比较规则

    2. 创建集合的时候传递Comparator比较器对象,指定比较规则

可变参数

//计算n个数据的和
//jdk5之前写法
int[] arr={1,2,3,4,5,6,7,8,9,10};
int sum=getSum(arr);
int get Sum(int[] arr){
    int sum=0;
    for(int i:arr){
        sum=sum+i
    }
    return i;
}
//jdk5开始的写法:
//可变参数:方法的形参的个数是可变的
//格式:数据类型...形参名
//int...nums
//举例
int sum=getSum(1,2,3,4,5,6,7,8,9,10);
int get Sum(int...nums){
    int sum=0;
    for(int i:nums){
        sum=sum+i
    }
    return i;
}
//底层:可变参数就是一个数组,是Java帮助我们创建好的数组,只需把nums当作数组调用即可

可变参数的小细节:

  1. 在方法的形参中,最多只能定义一个可变参数

    int get Sum(int...nums,int...a){//错误写法,无法判断前面参数应该接收多少个参数
    }
    
  2. 在方法当中,如果除了可变参数以外的形参数,那么可变参数必须在最后!

    int get Sum(int...nums,int a){//错误写法,无法判断前面参数应该接收多少个参数
    }
    int get Sum(int nums,int...a){//正确写法
    }
    

Collections类

  • Collections是java.util.Collections的类,是集合的工具类

  • 常用方法:

    方法名 说明
    public static boolean addAll(Collection c,T...elements) 批量添加元素
    public static void shuffle(List<?> list) 打乱List集合元素的顺序
    public static void sort(List list) 排序
    public static void sort(List,Comparator c) 根据指定规则排序
    public static int binarySearch(List list,T key) 以二分查找算法查找元素(元素必须有序)
    public static void copy(List dest,List src) 拷贝集合中的元素
    public static int fill(List list,T obj) 使用指定元素填充集合
    public static void max/min(Collection coll) 根据默认的自然排序获取最大/最小值
    public static void swap(List<?> list,int i,int j) 交换指定索引的元素
  • 小细节:

    第一个方法的参数

    1. Collection代表只能给单列集合添加元素
    2. T...elements代表可以批量添加,这是一个可变参数

    第二个方法的参数:

    1. List<?> list代表只能打乱list系列的集合的元素

    sort方法的Comparator规则,是一个函数式接口,用Lambda表达式写即可

    例题:

    //实现概率抽取集合元素
    //随机抽取集合中元素且,男占70%女占30%
    
    import java.lang.reflect.Array;
    import java.util.*;
    
    public class Main {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            Collections.addAll(list, 1, 1, 1, 1, 1, 1, 1);//1代表男生占比,6个即百分之60
            Collections.addAll(list, 0, 0, 0, 0);//0代表女生占比,4代表百分之40
            Collections.shuffle(list);//打乱
            Random r = new Random();
            int index = r.nextInt(list.size());//这样获取到男生占比60%女生占比40%
            int nums = list.get(index);
            System.out.println(nums);
    
            ArrayList<String> boy = new ArrayList<>();
            ArrayList<String> girl = new ArrayList<>();
            Collections.addAll(boy, "男1", "男2", "男3", "男4", "男5", "男6");
            Collections.addAll(girl, "女1", "女2", "女3", "女4");
            if (nums == 1) {
                int boyIndex = r.nextInt(boy.size());
                System.out.println(boy.get(boyIndex));
            } else if (nums == 0) {
                int girIndex = r.nextInt(girl.size());
                System.out.println(girl.get(girIndex));
            }
        }
    }