第33讲 单列集合

集合概述

集合类:提供一种存储空间可变的存储模型

List系列集合特点

元素有序、可重复、有索引。

  • 有序:存和取的顺序是一致的;
  • 可重复:集合中的元素可重复;
  • 有索引:可以通过索引获取集合中的元素。

Set系列集合特点

元素无序、不可重复、无索引。

  • 无序:存和取的顺序有可能不一致;
  • 不可重复:元素不可重复;
  • 无索引:不能通过索引获取元素。

一、单列集合

单列集合特点

  • 单列集合一次只能存一个元素。

单列集合继承体系

红色:接口

蓝色:实现类

在这里插入图片描述

单列集合的选择

条件选择
默认ArrayList
存储元素不重复HashSet
元素不重复且存取一致LinkedHashSet
元素不重复且可指定排序规则TreeSet

1 Collection

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。

  • JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set、List)实现

导包:java.util.Collection

Collection方法及说明

方法说明
boolean add(E e)添加元素,并返回是否添加成功
boolean remove(E e)从集合中移除指定的元素
boolean contains(Object o)判断集合中是否存在指定的元素
boolean isEmpty()判断集合是否为空(长度是否为0)
int size()计算集合长度,即元素个数
void clear()清空集合中的元素(慎用)

注意:

  1. 方法add(E e)
  • 在List系列中,返回值永远是true,因为List系列允许数据重复。
  • 在Set系列,如果添加不重复元素返回true,反之返回false。
  1. 方法remove(Object o)
  • Collection里面定义的是共性的方法,所以只能提供元素名删除。
  1. 方法contains(Object o)
  • 该方法的底层是依赖equals方法判断的。

​ 要想拿来判断自定义类型,必须在自定义类型的JavaBean类中重写equals方法。

Collection遍历方式

场景使用方式
仅遍历增强for、Lambda
遍历的同时需要删除某元素迭代器

1 迭代器

2 增强for

其底层就是迭代器。目的是为了简化代码。

所有的单列集合、数组才能用增强for遍历。

格式:

//快速生成:集合名.for
for (E e : 数组或集合) {
    ...
}
import java.util.ArrayList;
import java.util.Collection;

public class 增强forDemo {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("Hello");
        coll.add("World");

        for (String s : coll) {
            System.out.println(s);
        }
    }
}

3 Lambda表达式

JDK8以后,更简单更方便的遍历方式。

方法:forEach(Consumer<E> c)

​ 其底层是使用for遍历集合,并将值传给accept方法。

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;

public class LambdaDemo {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("Hello");
        coll.add("World");

        //匿名内部类
        coll.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

        //Lambda
        coll.forEach(s -> System.out.println(s));
    }
}

2 List

List是一个接口。

导包:java.util.List

List集合特点

  • 有序集合

    ​ 存储和取出的元素顺序一致。

  • 可重复

    ​ 与Set集合不同,List集合允许重复元素

  • 有索引

​ 可以精确的控制列表中每个元素的插入位置,可以通过整数索引访问元素,搜索列表中的元素.

List常用方法

来自父类Collection

Collection方法说明
boolean add(E e)添加元素,并返回是否添加成功
boolean remove(E e)从集合中移除指定的元素
boolean contains(Object o)判断集合中是否存在指定的元素
boolean isEmpty()判断集合是否为空(长度是否为0)
int size()计算集合长度,即元素个数
void clear()清空集合中的元素(慎用)

在List系列中, add(E e)方法返回值永远是true,因为List系列允许数据重复。

List特有方法

方法说明
void add(索引,元素)在指定位置插入指定元素
E remove(索引)删除指定位置的元素,并返回该元素
E set(索引,元素)修改指定位置元素,返回被修改的元素
E get(索引)返回指定索引处的元素

注意:

对于list系列集合remove()方法有两种:remove(对象), remove(索引)

public class Demo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        
        list.remove(1);	//这里删除的是 索引1 上的元素
        /*
        	如果方法有重载,优先调用实参与形参类型一致的那个方法。
        	实参 1 是 int 类型,传递索引的remove方法的形参就是int类型。所以会优先调用它。
        */
        
        //如果想删除 元素1 ,可以删除 索引0 , 也可以手动装箱。
        Integer i = Integer.valueOf(1);
        list.remove(i);
    }
}

List遍历方式

除了可以使用Collection集合中的三种遍历方式,还有以下两种独有遍历方法。

场景使用方式
仅遍历增强for、Lambda
遍历的同时需要删除元素迭代器
遍历的同时需要添加元素列表迭代器
需要对索引操作普通for

普通for

for (int i=0; i<list.size(); i++) {
    String s = list.get(i);
    System.out.println(s);
}

列表迭代器

ListIterator:列表迭代器。

列表迭代器对象可以通过List集合的ListIterator()方法得到,是List集合特有的迭代器。

  • 该迭代器可以从反方向遍历List系列集合,但是要先把指针移至最后。
常用方法
方法说明
boolean hasNext()如果迭代还有元素,则返回true
E next()获取当前迭代元素,并后移指针
boolean hasPrevious()如果迭代器在反方向遍历列表时有更多元素,返回true
E previous()获取当前迭代元素,并前移指针
void add(E e)将指定的元素插入列表
void remove()删除迭代器所指向的元素

特有操作: 如果在该集合中有某元素,就插入另一元素

ListIterator<String> list = new list.ListIterator();
while (list.hasNext()) {
    String s = list.next();
    if (s.equals("world")) {
        list.add("hello");
    }
}

3 ArrayList

ArrayList是一种大小可变的数组。

  • <E> 是一种特殊的数据类型:泛型

  • 每个 ArrayList 对象都有一个容量。该容量是指用来存储列表元素的数组的大小,它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。ArrayList底层实现

导包:java.util.ArrayList

ArrayList 构造方法

构造方法说明
public ArrayList();创建一个初始容量为10的空集合对象
import java.util.ArrayList
    
public class Demo {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
    	//JDK7以后右侧尖括号内容可不写
    	ArrayList<String> array = new ArrayList<>();

		sout: array;  // 输出 []
    	/*
        ArrayList是java已经写好的类,这个类在底层做了处理,使得:
	       1.直接打印对象打印的不是地址值,而是集合中存储的元素
    	   2.打印的元素会用[]包裹起来
    	*/
    }
}

ArrayList 成员方法

ArrayList继承Collection和List的所有方法。

方法说明
public boolean add(E e);将指定元素追加到此集合的末尾
public void add(int index, E e);在指定位置(索引)插入指定的元素
public boolean remove(E e);删除指定元素e,返回是否删除成功
public E remove(int index);根据索引删除元素,返回被删除元素
public E set(int index, E e);修改索引指向的元素,返回被修改元素
public E get(int index);返回索引处的元素
public int size();返回集合长度
import java.util.ArrayList
    
public class Demo {
    public static void main(String[] args) {
		ArrayList<String> array = new ArrayList<String>();
        //将指定元素追加到此集合的末尾
		array.add("hello");
        array.add("java");	
        Sout: array;					  // 输出:[hello, java]
        
        //在指定位置(索引)插入指定的元素
		array.add(1, "world");
		Sout: array;					  // 输出:[hello , world , java]

        //删除指定元素e,返回是否删除成功
		Sout: array.remove("world");	  // 输出:true
		Sout: array;					 // 输出 [hello , java]

        //根据索引删除元素,返回被删除元素
		Sout: array.remove( 1 );		 // 输出 world
		Sout: array;					 // 输出 [hello , java]

        //修改索引指向的元素,返回被修改元素
		Sout: array.set(1 , "javase");	  // 输出 world
		Sout: array;					 // 输出 [hello , javase , java]

        //返回索引处的元素
		Sout: array.get( 1 );			 // 输出 world

        //返回集合长度
		Sout: array.size();				 // 输出 3
    }
}

4 LinkedList

LinkedList的底层数据结构是双向链表。查询慢,增删快。

  • 提供了操作首尾元素的特有API,所以操作首尾元素,速度也快。

LinkedList底层实现

导包:java.util.LinkedList

LinkedList特有方法

LinkedList除了继承Collection和List的所有方法外,还有自己的特有方法。

  • 并不常用。Collection和List里的方法完全可以实现这些功能。
特有方法说明
void addFirst(E e)在链表的开头插入元素
void addLast(E e)在链表尾追加元素(和默认add方法一样)
E getFirst()返回首元素
E getLast()返回尾元素
E removeFirst()删除首元素
E removeLast()删除尾元素

5 Set

Set是一个接口。

导包:java.util.Set

Set集合特点

  • 无序集合

    ​ 存和取的顺序有可能不一致。

  • 不可重复

    ​ 与List集合不同,Set集合不允许重复元素。

  • 无索引

​ 没有带索引的方法,不能使用普通for循环遍历。

Set常用方法

Set基本没有特有的方法,直接使用Collection方法即可。

Collection方法说明
boolean add(E e)添加元素,并返回是否添加成功
boolean remove(E e)从集合中移除指定的元素
boolean contains(Object o)判断集合中是否存在指定的元素
boolean isEmpty()判断集合是否为空(长度是否为0)
int size()计算集合长度,即元素个数
void clear()清空集合中的元素(慎用)

在Set系列,add(E e)方法如果添加不重复元素返回true,反之返回false。

6 HashSet

导包:java.util.HashSet

HashSet集合特点

  • 底层数据结构是哈希表

  • 无序集合

    ​ 没有带索引的方法,不能使用普通for循环遍历。

    ​ 存和取的顺序有可能不一致。

  • 元素不可重复

如果HashSet集合存入的是自定义对象,必须重写hashCode和equals方法。

重写:

自动生成:按下alt + insert -> 选择equals() and hashCode() -> 模板选择7+ -> 全默认

HashSet基本没有特有的方法,直接使用Collection(或Set)方法即可。

哈希值

哈希值:是JDK根据对象的地址字符串数字算出来的int类型的数值

获取哈希值

定义在Object类中的 hashCode() 方法可以返回使用对象的地址值计算出的哈希值。

对象.hashCode()

一般选择重写 hashCode() 方法,使其能根据对象的属性值计算哈希值。

哈希值的特点

  • 同一个对象多次调用hashCode()方法哈希值是相同的

  • 没有重写hashCode()方法,不同对象的哈希值不同。

    重写hashCode()方法,让有相同属性的不同对象的哈希值相同。

  • 哈希碰撞:不同属性值或不同地址值计算出的哈希值有可能会一样。

HashSet底层原理

根据哈希表在Java里的实现方式的不同,底层原理也不同:

  • JDK7以前:采用数组+链表实现,是一个元素为链表的数组
  • JDK8以后:数组+链表+红黑树
  1. 创建一个默认长度16,默认加载(负载)因子0.75的数组table。

    ​ 当集合长度到达 16 * 加载因子 时,数组会扩容。

在这里插入图片描述

  1. 根据元素的哈希值与数组的长度,计算出该元素应存入的位置。

    ​ 计算公式: int index = (数组长度 - 1) & 哈希值

  2. 判断计算出的索引位置是否为null

  3. 如果是null,直接添加。

  4. 如果不是null,调用equals方法比较属性值。

  • 属性值一样:不存。(元素不可重复)

  • 属性值不一样:在该索引形成链表(或红黑树),将元素存入。

    • JDK7以前:将新元素存入数组,老元素挂在新元素下面。
    • JDK8以后:将新元素挂在老元素下面。

JDK7以前

在这里插入图片描述

JDK8以后

在这里插入图片描述

当链表长度大于8,且数组长度大于等于64时,该链表会转换为红黑树(JDK8以后)。

在这里插入图片描述

案例

使用集合存储学生对象,存储的对象不能重复。如果属性值一致则认为是同一个对象。

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;
    }
    
    //省略get set方法
    
    //重写equals hashCode方法
    @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);
    }
}
import java.util.HashSet;

public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();
        Student s1 = new Student("zhangsan", 18);
        Student s2 = new Student("lisi", 19);
        Student s3 = new Student("zhangsan", 18);

        System.out.println(set.add(s1));	//true
        System.out.println(set.add(s2));	//true
        System.out.println(set.add(s3));	//false,重复元素,不添加

        //遍历
        set.forEach(s -> System.out.println(s.getName() + ", " + s.getAge()));
    }
}

7 LinkedHashSet

LinkedHashSet是哈希表和双向链表实现的Set接口。继承了HashSet.

  • 链表保证元素有序,元素的存储和取出顺序一致

  • 哈希表保证元素唯一,元素不重复

导包:java.util.LinkedHashSet

LinkedHashSet集合特点

  • 有序集合

    ​ 存储和取出的元素顺序一致。

  • 元素不可重复

  • 无索引

LinkedHashSet基本没有特有的方法,直接使用Collection(或Set或HashSet)方法即可。

LinkedHashSet底层原理

在HashSet基础上多了一条记录顺序的双向链表,
在这里插入图片描述

在遍历时会根据该链表记录的顺序遍历,从而保证了存储和取出的元素顺序一致。

8 TreeSet

底层是红黑树。

导包:java.util.TreeSet

TreeSet集合特点

  • 可排序:元素按照一定的规则排序,默认升序,可指定。

  • 元素不可重复

  • 无索引

​ 没有带索引的方法,不能使用普通for循环遍历。

TreeSet基本没有特有的方法,直接使用Collection(或Set)方法即可。

TreeSet排序规则

类型排序规则
数值类型:Integer,Double等默认按照从小到大的顺序排序。
字符类型默认按照字符在ASCLL码表中对应的值从小到大排序。
字符串类型默认排序规则与字符串长度无关,是从首个字符开始往后按字符ASCLL码表中对应的值比。
自定义数据类型(或要指定规则)需要手动指定排序规则。方法1:自然排序/默认排序;方法2:比较器排序

优先选择方法1,当方法1不能满足要求时,再用方法2

当方法1和方法2同时存在时,以方法2为准。

自然排序

步骤:

  1. 让元素所属的JavaBean类实现泛型接口Comparable导包:java.lang.Comparable

  2. 重写compareTo(T o1, T o2)方法指定排序规则。

重写方法时,要注意排序规则必须按照要求的主次条件来写

例:存储的学生按照年龄从小到大排序,当年龄相同时,按照姓名首字母排序

public class TreeSetDemo {
    public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<>();

		Student s1 = new Student(...)  ;
		Student s2 = new Student(...)  ;
		Student s3 = new Student(...)  ;
		ts.add(s1) ;
		ts.add(s2) ;
		ts.add(s3) ;
        
		//遍历
		ts.forEach(s -> System.out.println(s.getName() + ", " + s.getAge()));
    }
}

//学生类
class Student implements Comparable<Student> { //1.实现泛型接口Comparable<E>
    
    //省略JavaBean
    
    //2.重写compareTo() 方法
    compareTo(Student s) {
        //按照年龄从小到大排序
        int result = this.age - s.age;
        
        //年龄相同时,按照姓名首字母排序
        result = result == 0? this.name.compareTo(s.name) : result;
        return result;
    }
}

底层原理:

首先要清楚其底层是存储在红黑树里。

第25行:把 this 理解为当前要存储的元素, s 理解为 ts集合 里已经存储的元素(比如存储 s2 时, s2 就是接下来要存储的元素, s1 相对来说就是已经存储的元素)。

该方法的返回值:

  • 如果是正数,说明将要存储的元素 s2 比已经存储的元素 s1 大,就把 s2 放在已经存储的元素 s1 的左边;

  • 如果是负数,说明比已经存储的元素 s1 小,就把 s2 放在已经存储的元素 s1 的右边。

  • 如果是0,说明要存储的元素已经存在,不存。

同理s3、s4…按此规则排好就是正序。

相反,如果规则是 s.age - this.age 排出来的结果就是倒序。

比较器排序

带参构造方法使用的是比较器排序

步骤:

  1. 让集合构造方法接收比较器接口Comparator的实现类对象,

该接口是函数式接口,可用Lambda表达式简写。

  1. 重写compare(T o1, T o2)指定排序规则。
// 测试类

//采用匿名内部类
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        // s1 相当于自然排序中的 this,s2 相当于自然排序中的 s
        int result = s1.getAge() - s2.getAge();
        result = result == 0? s1.getName().compareTo(s2.getName()) : result;
        return result;
    }
});

//使用Lambda表达式改写
TreeSet<Student> ts = new TreeSet<>((s1, s2) -> {
        int result = s1.getAge() - s2.getAge();
        result = result == 0? s1.getName().compareTo(s2.getName()) : result;
        return result;
    }
});

底层原理: 与自然排序一样。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值