Java集合框架List接口

39次阅读

共计 10271 个字符,预计需要花费 26 分钟才能阅读完成。

第三阶段 JAVA 常见对象的学习

集合框架——List 接口

按照集合框架的继承体系,我们先从 Collection 中的 List 接口开始学习

(一) 概述及功能(ArrayList 演示)

(1) 概述

List 在 Collection 中充当着一个什么样的身份呢?——有序的 collection(也称为序列)

实现这个接口的用户以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。与 set 不同,列表通常允许重复的元素。

(2)List 集合功能

A:基本功能:(继承而来)
// 添加功能
boolean add(E e)向集合中添加一个元素
    
// 删除功能
void clear():移除集合中的所有元素
boolean remove(Object o):从集合中移除指定的元素
boolean removeAll(Collection<?> c): 从集合中移除一个指定的集合元素(有一个就返回 true)

// 获取功能
Iterator iterator(): 就是用来获取集合中每一个元素。// 判断功能
boolean isEmpty():判断集合是否为空
boolean contains(Object o):判断集合中是否包含指定元素
boolean containsAll(Collection<?> c):判断集合中是否包含指定的一个集合中的元素

// 长度功能
int size(): 获取集合中元素的个数

// 集合转换为数组
Object[] toArray()
B:特有功能:

// 添加功能: 在指定位置添加元素
void add(int index,Object element)
   
// 获取功能: 获取指定位置的元素
Object get(int index)
   
// 列表迭代器:List 集合特有的迭代器
ListIterator listIterator()
   
// 删除功能:根据索引删除元素, 返回被删除的元素
Object remove(int index)
   
// 修改功能: 根据索引修改元素,返回被修饰的元素。Object set(int index,Object element)
A:add() 使用方法:

我们还是先写一个 List 遍历字符串的代码

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Demo1 {public static void main(String[] args) {
        // 创建集合对象
        List list = new ArrayList();


        // 存储元素
        list.add("I");
        list.add("love");
        list.add("you");
        list.add("❤");
        list.add("❤");

        // 遍历集合
        Iterator it = list.iterator();
        while (it.hasNext()) {String s = (String) it.next();
            System.out.print(s + " ");
        }
    }
}

// 运行结果
I love you ❤ ❤ 

通过这段代码我们可以看到,List 集合的特点——有序(存储和去取出的元素一直),可重复

我们再使用 List 存储学生对象并遍历看看

//Student 自行补充

//StudentDemo 类
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class StudentDemo {public static void main(String[] args) {List list = new ArrayList();

        Student s1 = new Student("张三", 20);
        Student s2 = new Student("李四", 30);
        Student s3 = new Student("王五", 40);

        list.add(s1);
        list.add(s2);
        list.add(s3);

        Iterator it = list.iterator();
        while (it.hasNext()) {Student s = (Student) it.next();
            System.out.println(s.getName() + "---" + s.getAge());
        }
    }
}

// 运行结果
张三 ---20
李四 ---30
王五 ---40

//Student s = (Student)it.next();       
//it.next()返回的是 Object 类型。//(Student)it.next(); 是将 Object 类型强制转换成 Student 类型。// 这样才能够调用 getName()方法和 getAge()方法。
B:add() 使用方法:
package cn.bwh_02_List.ArrayList;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class StudentDemo {public static void main(String[] args) {List list = new ArrayList();

        Student s1 = new Student("张三", 20);
        Student s2 = new Student("李四", 30);
        Student s3 = new Student("王五", 40);

        list.add(s1);
        list.add(s2);
        list.add(s3);
        // 迭代器遍历
        Iterator it = list.iterator();
        while (it.hasNext()) {Student s = (Student) it.next();
            System.out.println(s.getName() + "---" + s.getAge());
        }

        // 利用 List 的功能遍历
        for (int x = 0; x < list.size(); x++){Student s = (Student) list.get(x);
            System.out.println(s.getName() + "---" + s.getAge());
        }
    }
}

上面几个实例中,我们使用了 Iterator 迭代器遍历,下面我们介绍一种特别的迭代器

C:列表迭代器:

ListIterator listIterator():列表迭代器:List 集合特有的迭代器

列表迭代器继承于 Iterator 迭代器,可以 直接使用 hasNext() 和 next()方法

基本功能

// 将指定的元素插入列表(可选操作)void add(E e) 

// 返回 true 如果遍历正向列表,列表迭代器有多个元素
boolean hasNext() 

// 返回列表中的下一个元素,并且前进光标位置
E next() 

// 从列表中删除由 next()或 previous()返回的最后一个元素(可选操作)void remove() 

// 用 指定的元素替换由 next()或 previous()返回的最后一个元素(可选操作)void set(E e) 

特有功能

// 获取上一个元素
Object previous()

// 判断是否有元素
boolean hasPrevious()

列表迭代器的好处是相比 Iterator 提供了更多的方法,并且可以实现正向遍历,才能逆向遍历,所以一般来说意义不大。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class Demo1 {public static void main(String[] args) {
        // 创建集合对象
        List list = new ArrayList();

        // 存储元素
        list.add("I");
        list.add("love");
        list.add("you");
        list.add("❤");
        list.add("❤");

        ListIterator lit= list.listIterator();
        // 正向遍历
        while(lit.hasNext()){String s = (String)lit.next();
            System.out.print(s + " ");
        }

        System.out.println();
        // 逆向遍历
        while (lit.hasPrevious()) {String s = (String) lit.previous();
            System.out.print(s + " ");

        }
    }
}

// 运行结果
I love you ❤ ❤ 
❤ ❤ you love I 

(二) 并发修改异常问题

我创建了一个集合,并且遍历它,使用 if 语句判断 是否集合中存在 ”love” 这个字符串,若存在,就增加一个 ”❤” 元素, 我们先顺着这个思路写一下代码:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
 *  并发修改异常
 */
public class Demo2 {public static void main(String[] args) {
        // 创建集合对象
        List list = new ArrayList();

        // 存储元素
        list.add("I");
        list.add("love");
        list.add("you");

        Iterator it = list.iterator();
        while (it.hasNext()) {String s = (String) it.next();
            if ("love".equals(s)) {list.add("❤");
            }
            System.out.println(s);
        }
    }
}

// 运行结果(节选)
Exception in thread "main" java.util.ConcurrentModificationException

我们贴出 JDK 中对这个异常的解释:

This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.

当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。

(1) 原因解释:

当我们对集合进行遍历的时候,我们会获取当前集合的迭代对象

//List 为例,获取集合的迭代对象
Iterator it = list.iterator();

这个迭代对象中,封装了迭代器的方法与集合本身的一些方法,当我们在迭代中使用集合本身的 add 方法的时候,就产生了 ConcurrentModificationException 异常,通俗的说就是,在判断成功后,集合中元素增加了,但是迭代器不清楚,所以就报错,如果迭代器中含有这一种方法(假设),我们是用迭代器添加元素就不会有问题了。

针对这个问题,我们给出两个解决方案

(2) 解决方案:

##### 方式 1:迭代器迭代元素,迭代器修改元素

我们假想如果 Iterator 迭代器中有添加功能就好了,但很遗憾并没有,但是它的子接口 ListIterator 却拥有这个功能

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Demo2 {public static void main(String[] args) {
        // 创建集合对象
        List list = new ArrayList();


        // 存储元素
        list.add("I");
        list.add("love");
        list.add("you");

        ListIterator lit = list.listIterator();
        while (lit.hasNext()) {String s = (String) lit.next();
            if ("love".equals(s)) {lit.add("❤");
            }
            System.out.print(s + " ");
        }
    }
}

// 运行结果
I love you 
2:集合遍历元素,集合修改元素(普通 for)
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class Demo2 {public static void main(String[] args) {
        // 创建集合对象
        List list = new ArrayList();

        // 存储元素
        list.add("I");
        list.add("love");
        list.add("you");

        for (int x = 0; x < list.size(); x++){String s = (String)list.get(x);
            if ("love".equals(s)){list.add("❤");
            }
            System.out.print(s + " ");
        }
    }
}

// 运行结果
I love you ❤ 

两者均可以解决并发修改异常的问题,但是通过运行结果也可以看出,方法一添加后,在本次遍历中不会输出添加的结果,而方法二却可以。

补充:增强 for 循环实现将集合进行遍历,也产生了并发修改异常,这是因为增强 for 在底层也是调用的集合本身的 remove

(3) 总结:

在迭代器遍历时,如果需要对集合进行增删操作时,要调用迭代器本身的 remove 方法,或者选择使用普通 for 进行遍历

(三) Vector (过时,不推荐)

特有功能
    a: 添加
        public void addElement(E obj)        --    add()
    b: 获取
        public E elementAt(int index)        --    get()
        public Enumeration<E> elements()    --  iterator()

(四) List 案例练习

下面的案例题目个别来源于网络,我们来整理,书写一下。

案例(一):集合嵌套存储和遍历元素的案例

需求:

我们班有学生,每一个学生是不是一个对象。所以我们可以使用一个集合表示我们班级的学生。ArrayList<Student>

但是呢,我们旁边是不是还有班级,每个班级是不是也是一个 ArrayList<Student>

而我现在有多个 ArrayList<Student>。也要用集合存储,怎么办呢 ?

按照我们的想法就是这个样子的:ArrayList<ArrayList<Student>>

案例 (二):获取 10 个 1 -20 之间的随机数,要求不能重复

package cn.bwh_02_List.ArrayList.RandomDemo;

import java.util.ArrayList;
import java.util.Random;
/*
 *  分析:*      A:创建产生随机数的对象
 *      B:创建一个存储随机数的集合
 *      C:定义一个统计变量,从 0 开始
 *      D:判断统计遍历是否小于 10
 *          小于 10:*              若不存在:就添加,同时统计变量 ++
 *              若不存在:则不作处理
 *          大于 10;*              不作处理
 *      E:遍历集合
 */
public class ArrayDemo {public static void main(String[] args) {Random r = new Random();
        ArrayList<Integer> a = new ArrayList<Integer>();
        int count = 0;
        while (count < 10) {int number = r.nextInt(20) + 1;
            if (!a.contains(number)){a.add(number);
                count++;
            }
        }

        for (Integer i : a){System.out.print(i + " ");
        }

    }
}

案例 (三) 键盘录入多个数据

键盘录入多个数据,以 0 结束,要求在控制台输出这多个数据中的最值

package cn.bwh_02_List.ArrayList.InputMore;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

/*
 *  分析:*      A:创建键盘录入数据对象
 *      B:键盘录入多个数据, 不知道数量,所以用集合存储
 *      C:以 0 结束——只要键盘录入的数据是 0,就不继续录入数据了
 *      D:把集合转成数组
 *      E:对数组排序
 *      F:获取该数组中的最大索引的值
 */
public class AeeayDemo {public static void main(String[] args) {Scanner sc = new Scanner(System.in);
        System.out.println("请输入数据");
        ArrayList<Integer> a = new ArrayList<Integer>();
        while (true) {int number = sc.nextInt();
            if (number != 0) {a.add(number);
            } else {break;}
        }

        Integer[] i = new Integer[a.size()];
        a.toArray(i);
        Arrays.sort(i);
        System.out.println(i[i.length - 1]);

    }
}

案例 (五) 登录注册案例(使用集合)

package cn.bwh.pojo;

/**
 * 这是用户基本描述类
 *
 * @author BWH_Steven
 * @version v1.0
 */

public class User {
    private String username;
    private String password;

    public User() {}

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {return username;}

    public void setUsername(String username) {this.username = username;}

    public String getPassword() {return password;}

    public void setPassword(String password) {this.password = password;}
}
package cn.bwh.dao;

import cn.bwh.pojo.User;

/**
 * 这是针对用户进行操作的接口
 *
 * @author BWH_Steven
 * @version v1.0
 */
public interface UserDao {
    /**
     * 这是用户登录功能
     *
     * @param username 用户名
     * @param password 密码
     * @return 返回登录是否成功
     */
    public abstract boolean isLogin(String username, String password);

    /**
     * 这是用户注册功能
     *
     * @param user 要注册的用户信息
     */
    public abstract void regist(User user);
}

因为注册的时候很可能还要填写
例如地址,性别,爱好等等信息(而登录功能的时候往往只需要用户名和密码),所以这个注册功能传进来的参数过多,因此用对象代替过多的参数,也就是说,通过传递对象(User user)包含众多信息避免了直接传递 String username,password 等等变量过多的问题

package cn.bwh.dao.impl;

import cn.bwh.dao.UserDao;
import cn.bwh.pojo.User;

import java.util.ArrayList;

/**
 * 这是用户操作的具体实现类(集合类)*
 * @author BWH_Steven
 * @version v1.0
 */
public class UserDaoImpl implements UserDao {
    // 为了多个集合能够使用同一个集合,就把集合定义为成员变量
    // 为了不让外人看到,用 private
    private static ArrayList<User> array = new ArrayList<User>();

    @Override
    public boolean isLogin(String username, String password) {

        // 遍历集合,获取每一个用户,并且判断用户的用户名和密码是否和传递过来的匹配
        boolean flag = false;

        for (User u : array) {if (u.getUsername().equals(username) && u.getPassword().equals(password)) ;
            flag = true;
            break;
        }

        return flag;
    }

    @Override
    public void regist(User user) {
        // 把用户信息存入集合内
        array.add(user);
    }
}
package cn.bwh.test;

import cn.bwh.dao.UserDao;
import cn.bwh.dao.impl.UserDaoImpl;
import cn.bwh.pojo.User;

import java.util.Scanner;

/**
 * 用户测试类
 *
 * @author BWH_Steven
 * @version v1.0
 */
public class UserTest {public static void main(String[] args) {
        // 为了能够到主界面
        while (true) {System.out.println("------------ 初次见面,请多指教 ------------");
            System.out.println("1 登录");
            System.out.println("2 注册");
            System.out.println("3 退出");
            System.out.println("请输入你的选择");

            Scanner sc = new Scanner(System.in);
            // 为了后面录入信息方便,所有的数据录入均使用字符串接受
            //Switch 语句的多个地方要使用,对象就定义到外面
            String choicString = sc.nextLine();

            // 调用注册功能,使用多态
            UserDao ud = new UserDaoImpl();

            switch (choicString) {
                case "1":
                    System.out.println("---------------- 登录系统 ----------------");
                    System.out.println("请输入用户名");
                    String username = sc.nextLine();
                    System.out.println("请输入密码");
                    String password = sc.nextLine();

                    // 调用登录功能
                    boolean flag = ud.isLogin(username, password);
                    if (flag) {System.out.println("登陆成功");
                        System.exit(0);
                        //break; 这里 break 结束的是 switch
                    } else {System.out.println("用户名或者密码错误,登录失败");
                    }
                    break;
                case "2":
                    System.out.println("-------------- 新用户注册 ---------------");
                    System.out.println("请输入用户名");
                    String newUsername = sc.nextLine();
                    System.out.println("请输入密码");
                    String newPassword = sc.nextLine();

                    // 把用户名和密码封装到一个类中
                    User user = new User();
                    user.setUsername(newUsername);
                    user.setPassword(newPassword);

                    ud.regist(user);
                    System.out.println("注册成功");
                    break;
                case "3":
                default:
                    System.out.println("谢谢使用,感谢你曾经来过过");
                    System.exit(0);
                    break;
            }

        }
    }
}
小结:

dao 层主要连接数据库,封装增删改查的数据库语句

daoimpl 是实现 dao 层方法的接口,所以可以把具体实现的方法写在 daoimpl 中,dao 层只写方法名就可以。

Pojo 代表简单的 Java 对象

(五) List 子类的特点(总结)

ArrayList:

​ 底层数据结构是 数组 查询快 增删慢

线程不安全 效率高

Vector:

​ 底层数据结构是 数组 查询快 增删慢

线程安全 效率低

LinkedList:

​ 底层数据结构是 链表 查询慢 增删快

线程不安全 效率高

使用具体情况

保证安全:Vector

​ (即使要安全,也不用这个,后面有替代的)

不保证安全:ArrayList 或者 LinkedList

查询多:ArrayList

增删多:LinkedList

结尾:

如果内容中有什么不足,或者错误的地方,欢迎大家给我留言提出意见, 蟹蟹大家!^_^

如果能帮到你的话,那就来关注我吧!(系列文章均会在公众号第一时间更新)

在这里的我们素不相识,却都在为了自己的梦而努力 ❤

一个坚持推送原创 Java 技术的公众号:理想二旬不止

正文完
 0