【Java 集合框架】List 的使用

x33g5p2x  于2021-10-20 转载在 Java  
字(8.0k)|赞(0)|评价(0)|浏览(423)

1. List

1.1 List 的常见方法

方法描述
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置的元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置的元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list

1.2 代码示例

注意: 下面的示例都是一份代码分开拿出来的,上下其实是有逻辑关系的

示例一: 用 List 构造一个元素为整形的顺序表

List<Integer> list = new ArrayList<>();

示例二: 尾插 e

list.add(1);
list.add(2);
System.out.println(list);
// 结果为:[1, 2]

示例三: 将 e 插入到 index 位置

list.add(0,10);
System.out.println(list);
// 结果为:[10, 1, 2]

示例四: 尾插 c 中的元素

List<Integer> list1=new LinkedList<>();
list1.add(99);
list1.add(100);
list.addAll(list1);
System.out.println(list);
// 结果为:[10, 1, 2, 99, 100]

只要是继承于 Collection 的集合类的元素都可以被插入进去,但要注意传过来的具体的类型要么是和 list 的具体类型是一样的,要么是 list 具体类型的子类

/*示例五: /*删除 index 位置的元素

System.out.println(list.remove(0));
System.out.println(list);
// 结果为:10 和 [1, 2, 99, 100]

示例六: 删除遇到的第一个 o

System.out.println(list.remove((Integer) 100));
System.out.println(list);
// 结果为:true 和 [1, 2, 99]

示例七: 获取下标 index 位置的元素

System.out.println(list.get(0));
// 结果为:1

示例八: 将下标 index 位置元素设置为 element

System.out.println(list.set(2,3));
System.out.println(list);
// 结果为:99 和 [1, 2, 3]

示例九: 判断 o 是否在线性表中

System.out.println(list.contains(1));
// 结果为:true

示例十: 返回第一个 o 所在下标

System.out.println(list.indexOf(1));
// 结果为:0

示例十一: 返回最后一个 o 的下标

list.add(1);
System.out.println(list.lastIndexOf(1));
// 结果为:3

示例十二: 截取部分 list

List<Integer> list2=list.subList(1,3);
System.out.println(list2);
// 结果为:[2, 3]

注意,当我们将 list2 通过 set 更改元素,其实对 list 也会有影响

list2.set(0,5);
System.out.println(list2);
System.out.println(list);
// 结果为:[5, 3] 和 [1, 5, 3, 1]

通过 subList 方法进行的截取,得到的集合的数值指向的地址和原集合中数值的地址是一样的

2. ArrayList

2.1 介绍

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。其继承了 AbstractList,并实现了 List 接口。LinkedList 不仅实现了 List 接口,还实现了 Queue 和 Deque 接口,可以作为队列去使用。

ArrayList 类位于 java.util 包中,使用前需要引入它。

2.2 ArrayList 的构造方法

方法描述
ArrayList()无参构造
ArrayList(Collection<? extends E> c)利用其他 Collection 构建 ArrayList
ArrayList(int initialCapacity)指定顺序表初始容量

示例一:

ArrayList<Integer> list1 = new ArrayList<>();

示例二:

ArrayList<Integer> list2 = new ArrayList<>(10);
// 该构造方法就是在构建时就将底层数组大小设置为了10

示例三:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
ArrayList<Integer> list3 = new ArrayList<>(list);

Collection<? extends E> c 只要是具体类型都和 list3 是一样的集合都可以放入转化成 ArrayList

2.3 ArrayList 底层数组的大小

当我们使用 add 方法给 ArrayList 的对象进行尾插时,突然想到了一个问题:既然 ArrayList 的底层是一个数组,那么这个数组有多大呢?

为了解决这个问题,我进行了如下探索

  1. 跳转到 ArrayList 的定义,我们看到了 elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. 跳转到 elementData 的定义,我们可以了解 ArrayList 底层是数组的原因

  3. 跳转到 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的定义,初步分析得到这个数组其实是空的

为什么这个数组是空的但存储元素的时候没有报异常呢?我们再去了解下 add 是怎样存储的

  1. 通过转到 ArrayList 的 add 方法的定义

  2. 通过定义,不难发现,数组容量和 ensureCapacityInternal 这个东西有关,那我们就看看它的定义

  3. 我们看里面的 calculateCapacity ,他有两个参数,此时数组为空,那么 minCapacity 就为 1。我们再转到 calculateCapacity 看看它的定义

  4. 此时我们就好像可以与之前串起来了,当数组为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,就返回 DeFauLt_CAPACITYminCapacity(此时为1) 的最大值。DeFauLt_CAPACITY 其实是默认容量的意思,我们可以转到它的定义看看有多大

  5. DeFauLt_CAPACITY 的值是10,故 calculateCapacity 函数此时的返回值为10,最后我们再确定一下 ensureExplicitCapacity 是干啥的

  6. 此时 minCapacity 的值是10,而数组为空时数组长度为0,所以进入 if 语句,执行 grow 方法,我们继续转到 grow 的定义

此时我们就可以了解,当我们创建一个 ArrayList 时,其底层数组大小其实是0。当我们第一次 add 的时候,经过 grow ,数组的大小就被扩容为了10。并且这大小为10的容量放满以后,就会按1.5倍的大小继续扩容。至于这个数组最大能存放多少,大家可以再转到 MAX_ARRAY_SIZE 的定义去查看。

3. LinkedList

3.1 介绍

LinkedList 类是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。

Java 的 LinkedList 底层是一个双向链表,位于 java.util 包中,使用前需要引入它

3.2 LinkedList 的构造方法

方法描述
LinkedList()无参构造
LinkedList(Collection<? extends E> c)利用其他 Collection 构建 LinkedList

示例一:

LinkedList<Integer> list1 = new LinkedList<>();

示例二:

List<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
LinkedList<Integer> list2 = new LinkedList<>(list);

Collection<? extends E> c 只要是具体类型都和 list2 是一样的集合都可以放入转化成 LinkedList

4. 练习题

习题一

题目描述:
霍格沃茨学院有若干学生(学生对象放在一个 List 中),每个学生有一个姓名(String)、班级(String)和考试成绩(double)。某次考试结束后,每个学生都获得了一个考试成绩。遍历 list 集合,并把每个学生对象的属性都打印出来

本题代码:

class Student{
    private String name;
    private String classes;
    private double score;
	// 重写构造方法
    public Student(String name, String classes, double score) {
        this.name = name;
        this.classes = classes;
        this.score = score;
    }
	// 构造 get 和 set 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClasses() {
        return classes;
    }

    public void setClasses(String classes) {
        this.classes = classes;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
	// 重写 toString 方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", classes='" + classes + '\'' +
                ", score=" + score +
                '}';
    }
}
public class TestDemo {
    public static void main(String[] args) {
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student("哈利波特","大二班",95.5));
        students.add(new Student("赫敏格兰杰","小三班",93));
        students.add(new Student("罗恩韦斯莱","小二班",91));
        for(Student s: students){
            System.out.println(s);
        }   
    }
}
// 结果为:
// Student{name='哈利波特', classes='大二班', score=95.5}
// Student{name='赫敏格兰杰', classes='小三班', score=93.0}
// Student{name='罗恩韦斯莱', classes='小二班', score=91.0}

习题二

题目描述:
有一个 List 当中存放的是整形的数据,要求使用 Collections.sort 对 List 进行排序

该题代码:

public class TestDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(7);
        list.add(1);
        list.add(6);
        list.add(2);
        Collections.sort(list);
        System.out.println(list);
    }
}
// 结果为:[1, 2, 3, 6, 7]

补充:

Collections 是一个工具类,sort 是其中的静态方法,它是用来对 List 类型进行排序的

注意:

如果具体的类是类似于习题一那样的 Student 类,该类中含有多个属性,那就不能直接使用这个方法。要对 comparator 或者 comparable 接口进行重写,确定比较的是哪个属性才行
*
习题三

题目描述:
输出删除了第一个字符串当中出现的第二个字符串中的字符的字符串,例如

String str1 = "welcome to harrypotter";
String str2 = "come";
// 结果为:wl t harrypttr

希望本题可以使用集合来解决

该题代码:

public static void removeS(String str1, String str2){
    if(str1==null || str2==null){
        return;
    }
    List<Character> list = new ArrayList<>();
    int lenStr1=str1.length();
    for(int i=0; i<lenStr1; i++){
        char c = str1.charAt(i);
        if(!str2.contains(c+"")){
            list.add(c);
        }
    }
    for(char ch: list){
        System.out.print(ch);
    }
}

5. 扑克牌小游戏

我们可以通过上述所学,运用 List 的知识,去写一个关于扑克牌的逻辑代码(如:获取一副牌、洗牌、发牌等等)

class Card{
    private String suit;   // 花色
    private int rank;       // 牌面值
    public Card(String suit, int rank){
        this.suit=suit;
        this.rank=rank;
    }
    @Override
    public String toString() {
        return "[ "+suit+" "+rank+" ] ";
    }
}
public class TestDemo {
    public static String[] suits = {"♣", "♦", "♥", "♠"};
    // 获取一副牌
    public static List<Card> getNewCards(){
        // 存放 52 张牌
        List<Card> card = new ArrayList<>();
        for(int i=0; i<4; i++){
            for(int j=1; j<=13; j++) {
                card.add(new Card(suits[i], j));
            }
        }
        return card;
    }
    public static void swap(List<Card> card, int i, int j){
        Card tmp = card.get(i);
        card.set(i, card.get(j));
        card.set(j, tmp);
    }
    // 洗牌
    public static void shuffle(List<Card> card){
        int size = card.size();
        for(int i=size-1; i>0; i--){
            Random random = new Random();
            int randNum = random.nextInt(i);
            swap(card, i, randNum);
        }
    }
    public static void main(String[] args) {
        // 得到一副新的牌
        List<Card> cardList = getNewCards();
        System.out.println("已获取新的扑克牌");
        System.out.println("洗牌:");
        shuffle(cardList);
        System.out.println(cardList);
        System.out.println("抓牌:(3个人,每人轮流抓牌总共抓5张)");
        List<Card> hand1 = new ArrayList<>();
        List<Card> hand2 = new ArrayList<>();
        List<Card> hand3 = new ArrayList<>();
        List<List<Card>> hands = new ArrayList<>();
        hands.add(hand1);
        hands.add(hand2);
        hands.add(hand3);
        for(int i=0; i<5; i++){
            for(int j=0; j<3; j++){
                Card card = cardList.remove(0);
                hands.get(j).add(card);
            }
        }
        System.out.println("第一个人的牌:"+hand1);
        System.out.println("第二个人的牌:"+hand2);
        System.out.println("第三个人的牌:"+hand3);
    }
}
/** 结果为: 已获取新的扑克牌 洗牌: [[ ♥ 9 ] , [ ♦ 6 ] , [ ♣ 8 ] , [ ♦ 2 ] , [ ♣ 6 ] , [ ♦ 4 ] , [ ♣ 11 ] , [ ♣ 9 ] , [ ♠ 8 ] , [ ♣ 5 ] , [ ♦ 8 ] , [ ♦ 10 ] , [ ♦ 1 ] , [ ♦ 12 ] , [ ♥ 10 ] , [ ♥ 7 ] , [ ♠ 12 ] , [ ♥ 12 ] , [ ♦ 7 ] , [ ♣ 13 ] , [ ♠ 6 ] , [ ♠ 5 ] , [ ♥ 3 ] , [ ♦ 5 ] , [ ♦ 11 ] , [ ♣ 12 ] , [ ♠ 7 ] , [ ♦ 3 ] , [ ♥ 5 ] , [ ♦ 13 ] , [ ♣ 1 ] , [ ♥ 8 ] , [ ♠ 10 ] , [ ♠ 4 ] , [ ♣ 4 ] , [ ♣ 7 ] , [ ♥ 1 ] , [ ♠ 1 ] , [ ♣ 3 ] , [ ♥ 11 ] , [ ♥ 13 ] , [ ♦ 9 ] , [ ♠ 13 ] , [ ♣ 10 ] , [ ♥ 6 ] , [ ♠ 11 ] , [ ♠ 3 ] , [ ♣ 2 ] , [ ♠ 2 ] , [ ♥ 2 ] , [ ♥ 4 ] , [ ♠ 9 ] ] 抓牌:(3个人,每人轮流抓牌总共抓5张) 第一个人的牌:[[ ♥ 9 ] , [ ♦ 2 ] , [ ♣ 11 ] , [ ♣ 5 ] , [ ♦ 1 ] ] 第二个人的牌:[[ ♦ 6 ] , [ ♣ 6 ] , [ ♣ 9 ] , [ ♦ 8 ] , [ ♦ 12 ] ] 第三个人的牌:[[ ♣ 8 ] , [ ♦ 4 ] , [ ♠ 8 ] , [ ♦ 10 ] , [ ♥ 10 ] ] */

上述代码中有一处代码是这样写的 List<List<Card>>,其实不难理解,这个类型其实就是 List 中存放的每个元素都是一个 List 类型的,并且每一个 List 元素中的元素都是 Card 类型,类似于二维数组。

相关文章