List
List接口和常用方法
基本介绍
List接口是Collection接口的子接口
- List集合类中的元素有序–即添加顺序和取出顺序一致、且可重复
public class Journey {@SuppressWarnings({"all"})public static void main(String[] args) {List list = new ArrayList();list.add("kerwin");list.add("A");list.add("B");list.add("C");list.add("kerwin");list.add(true);System.out.println("list="+list);}
}
//输出
list=[kerwin, A, B, C, kerwin, true]
- List集合中的每一个元素都有其对应的顺序索引,即支持索引
@SuppressWarnings({"all"})public static void main(String[] args) {List list = new ArrayList();list.add("kerwin");list.add("A");list.add("B");list.add("C");list.add("kerwin");list.add(true);System.out.println("list="+list);System.out.println(list.get(2));//B}
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- List接口的实现类有ArrayList 、 LinkedList 、Vector
常用方法
import java.util.ArrayList;
import java.util.List;public class Journey {@SuppressWarnings({"all"})public static void main(String[] args) {List list = new ArrayList();list.add("kerwin");list.add("A");list.add(true);list.add(0,"Blue");System.out.println("list="+list);//输出如下
// list=[Blue, kerwin, A, true]List list2 = new ArrayList();list2.add(false);list2.add(1110);list2.addAll(0,list);System.out.println("list2="+list2);//输出如下
// list2=[Blue, kerwin, A, true, false, 1110]System.out.println(list.get(0));//BlueSystem.out.println(list2.get(4));//falseSystem.out.println(list.indexOf("Blue"));//0list.add("kerwin");System.out.println("第二次新加了个元素kerwin后的list="+list);//输出如下
// 第二次新加了个元素kerwin后的list=[Blue, kerwin, A, true, kerwin]System.out.println(list.lastIndexOf("kerwin"));//4System.out.println(list.indexOf("kerwin"));//1list.remove("Blue");System.out.println("移除了一个元素后的list="+list);
// 移除了一个元素后的list=[kerwin, A, true, kerwin]System.out.println(list.set(0, "A"));//设置指定的index 0 位置处的元素为 A,输出 kerwin//如果.set(index,ele)中的index不存在则会报数组越界异常,所以index必须存在才行System.out.println(list);//此时输出如下
// [A, A, true, kerwin]System.out.println(list.subList(1, 3));//输入如下,取[1,3)区间的元素
// [A, true]}
}
练习
public class Journey {@SuppressWarnings({"all"})public static void main(String[] args) {List list = new ArrayList();list.add("kerwin");list.add(1110);list.add(true);list.add("hello");list.add(1110);list.add(3.14);System.out.println("list="+list);
// list=[kerwin, 1110, true, hello, 1110, 3.14]//在3号位插入hsp,注意3号位是指第三个元素,其索引是2,list中的索引是从0开始的哈list.add(2,"hsp");System.out.println("添加了hsp后的list="+list);
// 添加了hsp后的list=[kerwin, 1110, hsp, true, hello, 1110, 3.14]//获得第5个元素System.out.println(list.get(4));//hello//删除第6个元素list.remove(5);//1110System.out.println("list="+list);
// list=[kerwin, 1110, hsp, true, hello, 3.14]//修改第7个元素System.out.println(list.size());//6list.add("我是第七个元素");System.out.println(list);
// [kerwin, 1110, hsp, true, hello, 3.14, 我是第七个元素]list.set(6,"我是被改动之后的七号元素");System.out.println("此刻的list="+list);
// 此刻的list=[kerwin, 1110, hsp, true, hello, 3.14, 我是被改动之后的七号元素]//使用迭代器遍历集合System.out.println("使用迭代器遍历,可得:=======================");//创建迭代器,注意不是直接new哈Iterator iterator = list.iterator();//联想到迭代器的结构,判断是否还有下一个元素,若有,则下移,并return取出//顺便一提,可以用快捷键一键生成ititwhile (iterator.hasNext()) {Object obj = iterator.next();System.out.println(obj);}//输出如下/*** kerwin* 1110* hsp* true* hello* 3.14* 我是被改动之后的七号元素*/}
}
List的三种遍历方式
【ArrayList、LinkedList、Vector】
说明:使用LinkedList完成 ,其使用方式和ArrayList一样
public class Journey {@SuppressWarnings({"all"})public static void main(String[] args) {//下面三种List接口的实现子类都可以完美适配底下三种遍历方式
// LinkedList list = new LinkedList();
// List list = new Vector();List list = new ArrayList();for (int i = 0; i < 5; i++) {list.add(i);}list.add("bingo!!");System.out.println(list);//1.使用迭代器遍历Iterator iterator = list.iterator();while (iterator.hasNext()) {Object obj = iterator.next();System.out.println("obj=" + obj);}System.out.println("+++++++++++++++++++++++++++++++++++++++");//2.使用增强for循环for (Object o : list) {System.out.println("增强for得到元素-->" + o);}System.out.println("========================================");//3.使用普通for循环for (int i = 0; i < list.size(); i++) {System.out.println("normalFor===》" + list.get(i));}}
}
练习
public class Journey {@SuppressWarnings({"all"})public static void main(String[] args) {//使用ArrayList实现
// List list = new ArrayList();//使用Vector实现
// List list = new Vector();//使用LinkedList实现List list = new LinkedList();list.add(new Book("红楼梦","曹雪芹",100));list.add(new Book("西游记","吴承恩",10));list.add(new Book("水浒传","施耐庵",19));list.add(new Book("三国志","罗贯中",80));list.add(new Book("西游记","吴承恩",10));for (Object o :list) {System.out.println(o);}//遍历结果如下:/*** 名称:红楼梦 价格:100.0 作者:曹雪* 名称:西游记 价格:10.0 作者:吴承恩* 名称:水浒传 价格:19.0 作者:施耐庵* 名称:三国志 价格:80.0 作者:罗贯中* 名称:西游记 价格:10.0 作者:吴承恩*///按价格排序,从高到低//如何对集合进行排序呢?sort(list);System.out.println("======排序后的输出=======");for (Object o :list) {System.out.println(o);}//输出结果如下/*** ======排序后的输出=======* 名称:西游记 价格:10.0 作者:吴承恩* 名称:西游记 价格:10.0 作者:吴承恩* 名称:水浒传 价格:19.0 作者:施耐庵* 名称:三国志 价格:80.0 作者:罗贯中* 名称:红楼梦 价格:100.0 作者:曹雪芹*/}//静态方法@SuppressWarnings({"all"})public static void sort(List list){//使用冒泡排序框架int size = list.size();for(int e=size-1;e>0;e--){for(int j=0;j<e;j++){//取出对象
// Object o = list.get(j);//注意这里要向下转型,不然不是Book对象,咋获得其属性呢?//这里不需要使用中间变量哈,可以直接用set实现互换Book book1 = (Book)list.get(j);//原先的list.get(j)的返回类型是Object类型的Book book2 = (Book)list.get(j+1);if(book1.getPrice()>book2.getPrice()){list.set(j,book2);list.set(j+1,book1);}}}}
}
ArrayList的注意事项
- 可以存放所有类型的元素,甚至是空元素null,ArrayList并且可以放入多个元素
public static void main(String[] args) {ArrayList list = new ArrayList();list.add(null);list.add("kerwin");list.add(null);for (Object o :list) {System.out.println(o);}/*** null* kerwin* null*/}
-
ArrayList是由数组来实现数据存储的
-
ArrayList基本等同于Vector,除了ArrayList是线程不安全(但是其执行效率高啊hhhh)的外;
在多线程情况下,不建议使用ArrayList
//ArrayList是线程不安全的,以下是其add方法源码,可见并没有同步关键字修饰public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;return true;}//而这是Vector底层的add方法源码,可以看到是有同步关键字synchronized修饰的public synchronized void addElement(E obj) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = obj;}
ArrayList底层操作机制源码分析
@SuppressWarnings({"all"})
public class Journey {public static void main(String[] args) {//使用无参构造器创建ArrayList对象ArrayList list = new ArrayList();
// ArrayList list = new ArrayList(8);for (int i = 1; i <=10; i++) {list.add(i);//这里会自动装箱,进入Integer的valueOf方法//之后,再执行这个add方法,而add方法内部也不是上来就把元素丢进数组里,//而是先通过ensureCapacityInternal方法来确保加了一个元素后,不会越界//然后才放进elementData数组中}for (int i = 11; i <= 15; i++) {list.add(i);}list.add(100);list.add(200);list.add(null);for (Object o :list) {System.out.println(o);}}
}
-
ArrayList中维护了一个Object类型的数组elementData
拓展:transient–瞬间的、短暂的,其表示被transient修饰的属性不会被序列化,会被忽略
public ArrayList() {//创建了一个空的elementData数组={}this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//这里再进入,就会发现起初这里传给它的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个{}}
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
//calculateCapacity()--->grow()--->ensureExplicitCapacity()-->ensureCapacityInternal()--->add()
/**
调用顺序总结:
calculateCapacity() 计算出所需的最小容量。
结果传递给 ensureExplicitCapacity(),判断是否需要扩容。
如果需要扩容,ensureExplicitCapacity() 调用 grow() 方法进行扩容。
扩容完成后,所有调用链返回到 add() 方法,最终新元素被成功添加。
*/public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!elementData[size++] = e;//最后完成第一次扩容后,elementData就有了10个空间,size起初等于0,//把要放的数据放进去,然后size++,变为1,表示此时的数组中有了一个数据return true;}private void ensureCapacityInternal(int minCapacity) {//注意这里是最少需要的容量,比如你第一次add,那么最少需要一个//容量,嗯哼?如果你加了10个元素了,那么再add一次,底层到这儿就会是11个,因为只有11个才能将你之前的10个和//现在准备新add的那一个元素全放进去;ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}private static int calculateCapacity(Object[] elementData, int minCapacity) {//如果当前数组是默认的空数组,则直接返回max(默认初始化的容量10,当前所需要的容量),否则返回所需容量if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//DEFAULT_CAPACITY这里是10,而minCapacity是1return Math.max(DEFAULT_CAPACITY, minCapacity);}//这里的minCapacity是当前需要的最小容量,你当前要加进去一个元素,自然这里的值就是1了return minCapacity;}private void ensureExplicitCapacity(int minCapacity) {//这里记录的是被修改的次数modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)//10-0>0,所以进入动态扩容机制grow(minCapacity);}private void grow(int minCapacity) {// overflow-conscious code//获取当前数组的容量int oldCapacity = elementData.length;//0//新数组扩容为原来的1.5倍,这里我们是第一次扩容,所以走的是第一个if逻辑分支int newCapacity = oldCapacity + (oldCapacity >> 1);//0+0/2=0//检查新的容量是否满足最小需求minCapacity,//如果不满足,也就是扩容后小于所需容量,则直接将 newCapacity //设置为 minCapacity。这样可以确保扩容后的数组至少能容纳当前所有的元素。if (newCapacity - minCapacity < 0)//0<10,所以newCapacity就被赋成10了;newCapacity = minCapacity;//检查是否超过最大允许的数组大小//如果新的容量超过了这个最大值,就调用 hugeCapacity(minCapacity),//可能会根据系统的限制或特定情况处理超大数组的分配。if (newCapacity - MAX_ARRAY_SIZE > 0)//此时不满足,所以跳过newCapacity = hugeCapacity(minCapacity);//复制数组到新容量,elementData起初是{},newCapacity是10,//随后elementData容量就成了10,里面是空数据elementData = Arrays.copyOf(elementData, newCapacity);//这里的扩容是保留原有数据的前提下,再给你多搞几个空间出来,//类似于给你搬家,换个大房子,原先的东西也一并带过来}
扩容机制的要点
初始容量:在首次添加元素时,ArrayList
会初始化其内部数组到默认容量(10)。
动态扩容:每当数组需要扩容时,ArrayList
会扩展到原来容量的 1.5 倍,以减少频繁扩容的开销。
最小容量保证:扩容后的新容量至少满足当前所需的最小容量 minCapacity
。
最大容量检查:确保扩容后的容量不超过 JVM 所能支持的最大数组大小。
数组复制:通过扩展数组并复制原有数据,ArrayList
保证了动态数组的灵活性和稳定性。
- 如果使用的是指定大小的构造器,则初始elementData的容量大小为指定大小,如果需要再次扩容,则直接扩容elementData为1.5倍
@SuppressWarnings({"all"})
public class Journey {public static void main(String[] args) {//使用有参构造器创建ArrayList对象ArrayList list = new ArrayList(8);for (int i = 1; i <=10; i++) {list.add(i);//这里会自动装箱,进入Integer的valueOf方法//之后,再执行这个add方法,而add方法内部也不是上来就把元素丢进数组里,//而是先通过ensureCapacityInternal方法来确保加了一个元素后,不会越界//然后才放进elementData数组中}for (int i = 11; i <= 15; i++) {list.add(i);}list.add(100);list.add(200);list.add(null);for (Object o :list) {System.out.println(o);}}
}
//debug源码,进入ArrayList的内部,如下,此时指定的容量是8,所以走第一个分支,给elementData扩容成8public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}