java基础:13.5 集合框架 - List 表(ArrayList)

本文详细介绍了Java中的List接口及ArrayList实现,包括List的特点、常用方法、ArrayList的工作原理及优缺点。ArrayList基于动态数组实现,适合随机访问,但在元素插入和删除时效率较低。文章还提到了泛型在ArrayList中的应用以及遍历ArrayList的三种方式,并提供了相关练习题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 
List (表)接口继承自Collection 接口, 定义了一个用于顺序存储元素的合集。
允许重复元素。
可以使用它的两个具体类 [ 线性表类-- ArrayList ] 或者 [ 链表类 - LinkedList ] 来创建一个线性表( list ) 。

 

1、概述

List是一种有序集合,有时也被称为序列,可以有重复的元素.List集合相比Collection,除了直接继承的方法外,有以下拓展的操作方法

  • 位置访问—可以基于元素索引来操作元素,比如get,set,add,addAll和remove方法都支持这一点
  • 搜索—在集合中搜索一个特定对象,并返回它的索引,如indexOf和lastIndexOf方法
  • 迭代—除了继承自Collection中的迭代器,List还提供一个基于Iterator拓展的ListIterator迭代器
  • 视图—subList方法提供任意的范围操作方法
      
    同Set一样,List也要求强化equal和hashCode以使两个集合元素可以进行逻辑上的比较,而不考虑他们具体实现类的类型.当两个List有相同的元素时,他们被认为是相等的.

2、方法

在这里插入图片描述
 

3、实现

List的实现也分两种,通用实现和专用实现

1.通用实现

通用实现类主要有两个ArrayList、LinkedList.

通常我们拿最多使用的是ArrayList,它对元素访问花费常量时间。但是如果频繁的对一个List头部插入元素,或者遍历集合删除元素的时候应该考虑使用LinkedList。这些操作上,LinkedList花费常量时间,ArrarList花费线性时间。

在元素的访问上LinkedList花费线性时间,而ArrayList花费常量时间。这是因为ArrayList底层通过数组结构来实现,当对元素的增删时,,需要重新开辟一份内存空间,并拷贝原来的数据。而LinkedList底层是链表结构,每个元素都是一个节点,节点在内存中的存储是无序随机的,但每个节点都包含前一个节点和后一个节点的内存地址,从而实现查找,,在增删元素的时候,只需要修改特定节点和其对其他节点的联系。

综合考虑,当要使用LinkedList的时候最好测试一下LinkedList和ArrayList的性能,通常ArrayList表现更好些.

List还有两个遗留实现Vector、Stack。

Vector相比ArrayList是线程安全的,Stack是一种后进先出的数据结构(LIFO),继承自Vector。这两个实现都不推荐使用,他们是java早期版本的遗留,仅为向上兼容,可用Collections提供的静态同步封装器封装ArrayList代替Vector,虽然Vector性能要稍好些,使用Deque的通用实现代替Stack。

2.专用实现

List专用实现类主要有CopyOnWriteList,它和CopyOnWriteSet类似,实现读写分离,不需要进行同步设置,适合用来维持一个频繁读取而很少修改的事件处理列表。.
 
 

4、ArrayList

java基础:6.0 ArrayList 中,初步了解了ArrayList,知道了ArrayList的特点:

  1. ArrayList 的大小灵活,所以无须提前给定它的大小。而当创建一个数组时,它的大小必须给定。
  2. ArrayList 包含许多有用的方法。比如,可以使用contains 方法来测试某个元素是否在列表中。如果使用数组,则需要编写额外代码来实现该方法。
  3. 元素必须是一种对象,不能是int,double…,可以是Integer.
  4. ArrayList 可以用来存储多个自定义的对象
    ArrayList<String> cityList = new ArrayList<>();    // <>中的元素必须是一种对象,不能是int,double..,可以是Integer.
    cityList.add("London");
    cityList.add("Miami");
    System.out.println(cityList.Size() + cityList.contains("Miami") + cityList.remove("Miami");
    System.out.println(cityList.toString());   //  == System.out.println(cityList);
}

分配空间注意的点

  1. 如果能估计出可能你存储元素的数量,可以在填充之前调用 staff.ensureCapacity(100);
    另外还可以把初始容量传递给 ArrayList<Employee> staff = new ArrayList<>(100);

  2. 一旦能确认数组列表的大小不再发生变化,可以调用trimSize 方法。它将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。(当要再次添加新元素时,要花时间移动存储块)

 
特点

ArrayList用数组存储元素,但是动态创建的。超出限制时会增加50%容量,用System.arraycopy()复制到新的数组中。

因此最好能给出数组大小的预估值。默认第一次插入元素时创建大小为10的数组。

LinkedList 在一个链表( linked list) 中存储元素。

要选用这两种类中的哪一个依赖于特定需求。

如果只需要按下标随机访问元素,而不会在线性表中插入或删除元素,ArrayList的效率很高!
通过 list.get(i),或者 list.set(1,“London”) 这两个方法可直接访问和修改;
直接通过list.add(“China”);在线性表末尾加元素的性能也很好,这是ArrayList的优点。

但是,如果按下标插入、删除元素–add(i,e), remove(i), remove(e),
则要用System.arraycopy()来移动部分受影响的元素,性能就变差了!!!这是ArrayList的缺点。

ArrayList 不能自动减小。可以使用方法 trimToSize() 将数组容量减小到线性表的大小。
 

toArray

toArray 可以把一个ArrayList对象转换为数组。
需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组。

        ArrayList heros = new ArrayList();
        for (int i = 0; i < 5; i++) {        // 初始化5个对象
            heros.add(new Hero("hero " + i));
        }
        Hero specialHero = new Hero("special hero");
        heros.add(specialHero);
        System.out.println(heros);
        
        Hero hs[] = (Hero[])heros.toArray(new Hero[]{});
        System.out.println("数组:" + hs);

 
使用泛型与不使用泛型

学习别人的程序时发现有人是第一种写法,有人是第二种。

  1. ArrayList heros = new ArrayList();
    对于不使用泛型的容器,可以往里面放英雄,也可以往里面放物品
    heros.add(new Hero(“盖伦”));
    heros.add(new Item(“冰杖”)); // 放别的类的对象并不会报错

  2. ArrayList<Hero> heros = new ArrayList<>();
    使用泛型后, heros.add(new Item(“冰杖”));则会系统报错,因此此时只可以放属性是Hero的对象,或者Hero的子类

  3. 如果要求ArrayList只能放两种对象,则可以把这两个对象继承在同一个父类。利用了2中的知识点。

 
 

遍历

有三种方法。
for 用for循环遍历
iterator 迭代器遍历
for: 用增强型for循环

package collection;

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


public class TestCollection {

    public static void main(String[] args) {


        ArrayList<Hero> heros = new ArrayList<>();
        
        // 初始化5个对象
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i));
        }
        
        Hero specialHero = new Hero("special hero");
        heros.add(specialHero);
        System.out.println(heros);
        
        // 第一种遍历 for循环
        System.out.println("--------for 循环-------");
        for (int i = 0; i < heros.size(); i++) {
            Hero h = heros.get(i);
            System.out.println(h);
        }
        
        //第二种遍历,使用迭代器
        System.out.println("--------使用while的iterator-------");
        Iterator<Hero> it= heros.iterator();
        //从最开始的位置判断"下一个"位置是否有数据
        //如果有就通过next取出来,并且把指针向下移动
        //直到"下一个"位置没有数据
        while(it.hasNext()){
            Hero h = it.next();
            System.out.println(h);
        }
        //迭代器的for写法
        System.out.println("--------使用for的iterator-------");
        for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
            Hero hero = (Hero) iterator.next();
            System.out.println(hero);
        }

        // 第三种,增强型for循环
        System.out.println("--------增强型for循环-------");
        for (Hero h : heros) {
            System.out.println(h);
        }
    }
     
}

 

练习

练习1 -判断是否相同
如果就是要判断集合里是否存在一个 name等于 "hero 1"的对象,应该怎么做?
疑问 为什么方法1和方法2的答案不一样???

package collection;

import java.util.ArrayList;


public class TestCollection {

    public static void main(String[] args) {


        ArrayList<Hero> heros = new ArrayList<>();
        
        // 初始化5个对象
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i));
        }
        
        Hero specialHero = new Hero("special hero");
        heros.add(specialHero);
        System.out.println(heros);
        
        Hero testhero = new Hero("hero 1");
        for(Hero h: heros) {
        	if ( h.getName() == testhero.getName() )
        		 System.out.println("存在");
        	else System.out.println("不存在");
        }
          
        System.out.println("方法2");
    
       for(int i=0 ; i<heros.size(); i++) {
    	   if(heros.get(i).toString().equals("hero 2"))
    		   System.out.println("存在");
       }
    }     
}

练习2 - 首先初始化一个Hero集合,里面放100个Hero对象,名称分别是从
hero 0
hero 1
hero 2

hero 99.
通过遍历的手段,删除掉名字编号是8的倍数的对象

public class TestCollection {
    public static void main(String[] args) {
        ArrayList<Hero> heros = new ArrayList<>();        
        // 初始化5个对象
        for (int i = 0; i < 100; i++) {
            heros.add(new Hero("hero " + i));
        }        
        for(int i=0; i < heros.size();i++) {
        	int n = Integer.parseInt(heros.get(i).getName().substring(5));
        	if( n%8 ==0 )
        		heros.remove(i);
        }
        
        for(Hero a:heros)
        	System.out.println(a);
        
    }     
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值