List;List的子类(ArrayList,LinkedList);泛型;泛型通配符 (Java Day14)
一,List
-
概述:是单列集合中的有序集合的顶层接口,属于 Collection 的子接口,除了拥有 Collection 的通用方法,还有自己的特有方法【有序集合的共性功能】。
-
特点:
- 存取有序【存放数据的顺序和取出数据的顺序一致】
- 有索引【和数组相似 底层使用数组结构构成的】
- 存放数据可以重复
-
特有方法:
- add(int inde,E e):在指定的索引位置上添加元素e
- remove(int index ):删除指定索引位置的元素
- set(int index,E e):修改指定索引位置的值
- get(int index):获取指定索引位置的值
- 使用他的实现类 ArrayList 来使用
- add 方法和set 方法对比:【小的面试题】
add 方法:是一个插入方法,会使集合的元素增加不是覆盖行为
set 方法:是一个修改方法,不会使集合的元素增加是一个覆盖行为
代码示例
import java.util.ArrayList;
import java.util.List;
public class Demo_List {
public static void main(String[] args) {
List list = new ArrayList();
list.add("春天");
list.add("夏天");
list.add("秋天");
list.add("冬天");
list.add("四季");
System.out.println(list); // 对应的索引值 0,1,2,3,4 [春天, 夏天, 秋天, 冬天, 四季]
list.add(3, "夏至");
System.out.println(list); // [春天, 夏天, 秋天, 夏至, 冬天, 四季]
list.remove(0);
System.out.println(list); // [夏天, 秋天, 夏至, 冬天, 四季]
list.set(4, "冬至");
System.out.println(list); // [夏天, 秋天, 夏至, 冬天, 冬至]
list.get(2);
System.out.println(list.get(2)); //夏至, 获取第二索引上面的值,需在集合的范围内
}
}
- 遍历的方式
方式一:转变成数组,进行遍历 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Demo_List02 { public static void main(String[] args) { List list = new ArrayList(); list.add("法师"); list.add("盖伦"); list.add("蛮王"); list.add("豹女"); list.add("龙龟");//集合转换为数组 使用 Collection 中的共性方法 toArray Object[] arr = list.toArray(); //遍历数组 for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } 方式二:使用迭代器遍历 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Demo_List02 { public static void main(String[] args) { List list = new ArrayList(); list.add("法师"); list.add("盖伦"); list.add("蛮王"); list.add("豹女"); list.add("龙龟");//获取迭代器对象 使用collection 共性方法 iterator Iterator it = list.iterator(); //结合while循环开始遍历 while (it.hasNext()) { //取值 Object next = it.next(); System.out.println(next); } } }
- 迭代器遍历原理:
- hasNext():只有判断的行为
- next():取值,同时移动迭代器的指针
- 指针就是迭代器用来指向下一个位置的【寻找位置的】
- 并发修改异常:java.util.ConcurrentModificationException
- 产生的原因就是迭代器在遍历的过程中使用集合的对象进行元素的增删
- 注意:迭代器遍历的时候不允许使用集合的对象对元素进行增删操作。
- 解决并发修改异常的方案
- 使用迭代器对象进行增删操作
1.1 删除:直接使用Iterator删除
1.2 增加:使用子ListIterator的对象增加
2. 不使用迭代器去遍历增删操作
- 解决并发修改异常代码:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class Demo_List02 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("法师");
list.add("盖伦");
list.add("蛮王");
list.add("豹女");
list.add("龙龟");
Iterator it = list.iterator();
ListIterator lit = list.listIterator();
//结合while循环开始遍历
while (lit.hasNext()) {
//取值
Object next = lit.next();
if (next.equals("盖伦")) {
//list.remove(next);//报错 ConcurrentModificationException
//list.add("寒冰");
lit.remove();//解决并发修改异常
lit.add("寒冰");
}
System.out.println(next);
}
System.out.println(list);
}
}
-
方式三:直接使用for循环遍历【集合中只有有序集合可以使用】
- 因为我们的有序集合有索引,提供方法get(int index )来获取数值
代码示例
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class Demo_List02 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("法师");
list.add("盖伦");
list.add("蛮王");
list.add("豹女");
list.add("龙龟");
//for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
二,list的子类:
- 常用的实现类:ArrayList、LinkedList
-
ArrayList
- 概述:是List接口的典型的实现类,他的出现就是为了实现List接口中所有的功能。他没有自己的特有功能。
- 特点:有序集合,查询比较快,增删比较慢【因为底层是数组结构构成的】
- 解释:使用索引直接就可以找到位置,增删的时候底层需要重新创建数组需要过程的增删就比较慢
-
LinkedList
- 概述:他也是有序集合的一个实现类,但是这个类主要提供对集合首尾进行操作功能的一个类。共性方法可以使用,但是实际不怎么去用,基本使用自己首尾操作特有方法。
- 特点:有序集合,查询比较慢,增删比较快【底层是链表结构组成】
- 解释:增删的时候只需要把节点打开就可以了,查询的时候每一个环节都一样需要一个一个的去查看索引就慢
- 特有的方法:
- addFirst(E e):在集合的头部添加元素
- addLast(E e):在集合的尾部添加元素
- removeFirst():删除集合头部元素
- removeLast():删除集合尾部的元素
- getFirst():获取集合头部的元素
- getLast():获取节尾部的元素
- pop():弹出头部的元素
- push(E e):在集合的头部增加元素
代码示例
import java.util.LinkedList;
public class Demo_LinkedList {
public static void main(String[] args) {
LinkedList list= new LinkedList();
list.add("春天");
list.add("夏天");
System.out.println(list); //[春天, 夏天]
list.addFirst("秋天");
System.out.println(list);//[秋天, 春天, 夏天]
list.addLast("冬天");
System.out.println(list); //[秋天, 春天, 夏天, 冬天]
list.removeFirst();
System.out.println(list); //[春天, 夏天, 冬天]
list.removeLast();
System.out.println(list); //[春天, 夏天]
System.out.println(list.getFirst()); //春天
System.out.println(list.getLast());//夏天
list.push("夏至");
list.push("冬至");
list.push("春节");
System.out.println(list); //[春节, 冬至, 夏至, 春天, 夏天]
Object object = list.pop();
System.out.println(object); // 春节
System.out.println(list); //[冬至, 夏至, 春天, 夏天]
}
}
- 练习题:比较ArrayList和LinkedList在头部增删元素的效率
代码
import java.util.ArrayList;
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
LinkedList list2 = new LinkedList<>();
long start = System.currentTimeMillis();
for (int i = 0; i < 20000000; i++) {
list.add(5);
}
long end = System.currentTimeMillis();
System.out.println("ArrayList 添加元素所消耗时间:"+(end-start));
long start1 = System.currentTimeMillis();
for (int i = 0; i < 20000000; i++) {
list2.addFirst(5);
}
long end1 = System.currentTimeMillis();
System.out.println("LinkedList 添加元素所消耗时间:"+(end1-start1));
System.out.println("================");
long start2 = System.currentTimeMillis();
for (int i = 0; i < 200; i++) {
list.remove(Integer.valueOf(5));
}
long end2 = System.currentTimeMillis();
System.out.println("ArrayList 删除元素所消耗时间:"+(end2-start2));
long start3 = System.currentTimeMillis();
for (int i = 0; i < 200; i++) {
list2.removeLast();
}
long end3 = System.currentTimeMillis();
System.out.println("LinkedList 删除元素所消耗时间:"+(end3-start3));
}
}
- 比较ArrayList和LinkedList查询元素的效率>【课间来练习】
ArrayList 添加元素所消耗时间:257
LinkedList 添加元素所消耗时间:13420
ArrayList 删除元素所消耗时间:2796
LinkedList 删除元素所消耗时间:0
总结:
当在添加千万级的数据的时候添加到头部的位置时ArrayList 比LinkedList 快
删除的时候LinkedList 比ArrayList 快。
三,泛型
- 概述:提前使用未知的数据类型,未来的数据类型是未知 所以泛型知道具体数据类型吗?不知道
- 泛型使用 <字母> 来表示泛型 一般情况下字母都使用大写字母,常见的:E T
- 说白一点:泛型可以理解为是占位置的符号。
- 泛型可以代表任意的引用数据类型
- 代码举例:Collection<E> List<E> 代表集合未来要存放的数据的数据类型
- 泛型的使用:指泛型的数据类型具体化 具体化之后泛型消失
- 举例:Collection<String> c = new ArrayList<String>()
- 说明:<E> 变成了<String> 变得具体化了,这就是泛型的使用
- 好处:
- 可以把一些异常提到编译的时候,这样提升编写代码的速度
- 避免了强转的工作
- 特点:
- 泛型只能是引用数据类型
- 泛型没有继承之说
- 泛型一旦确定保持一致
- 如果没有指定泛型,具体化的时候默认是 Object 类型
代码示例
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList();
ArrayList<Object> list2 = new ArrayList<>();
list.add(3);
//list.add("3");//报错,不是integer类型
//list.add("大朗"); //报错,不是integer类型
list.add(3);
list.add(5);
System.out.println(list); //[3,3,5]
for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(i);
int num = integer+5;
System.out.println(num);//8 8 10
}
}
}
-
泛型的使用范围
- 使用范围:使用在类 方法 接口上
- 使用方式:<大写的字母> E T M
-
使用在类上面 【泛型类】
- 格式:修饰符 class 类名 <E> { 类的内容 }
- 举例:ArrayList<E>{ 类的内容}
- 说明:类中使用的 一些不确定的数据类型就可以使用<E>
- 泛型实例化:让类中的泛型具体化
- 泛型实例化时机:创建该类的对象的时候实例化泛型【必须实例化】
- 举例:ArrayList<Integer> list = new ArrayList<Integer>();
- 注意:
- 泛型实例化的时候只能给定引用数据类型
- 创建对象的时候如果对象要赋值给变量,保证左右的泛型类型一致
-
练习
- 定义一个泛型类,只能在类的头部进行增删
代码示例:
//定义Head类
import java.util.LinkedList;
public class Head<E> {
//创建一个存放数据的容器 属性
LinkedList<E> list = new LinkedList<E>();
public void add(E e) {
list.addFirst(e);
}
public void remove() {
list.removeFirst();
}
}
//定义测试类
public class HeadTest {
public static void main(String[] args) {
//泛型类使用【创建对象】的时候一定指定泛型具体数据类型
Head<String> head = new Head<String>();
head.add("法师");
head.add("adf");
head.add("提莫");
head.add("龙 ");
//head.add(123);报错因为不是string类型
System.out.println(heand.list); //[龙,提莫,adf,法师]
head.remove();
System.out.println(head.list); //[提莫,adf,法师]
Head<Integer> head1 = new Head<Integer>();
}
}
- 泛型类定义的时候:
- 创建对象的时候,左右的泛型类型要一致。
- 左边写了,右边可以省略,自动匹配。
-
使用在方法上
- 概述:因为方法体中使用到不确定的数据类型,需要在方法上要使用泛型
- 格式:修饰符 <泛型> 返回值类型 方法名(泛型参数列表){方法体}
- 说明:返回值类型前面的泛型只是用来声明泛型的。
- 泛型实例化时机:该方法被调用的时候才会实例化
- 注意事项:
- 在方法体中泛型就是一个数据类型
- 泛型一旦确定整个方法中的泛型就是确定的数据类型
- 方法定义的时候声明了泛型,方法体和参数列表中的泛型符号要全程一致
-
练习
- 定义一个方法,可以交换任意引用数据类型的数组的两个指定索引值的元素位置
代码示例:
import java.util.Arrays;
public class Demo_Method {
public static void main(String[] args) {
Integer[] arr = {1,2,3,4,5};
change(arr, 2, 4);
System.out.println(Arrays.toString(arr)); //[1,2,5,4,3]
String[] arr1 = {"宝宝","乃亮","大朗","金莲","银莲","PGone"};
change(arr1, 2, 4);
System.out.println(Arrays.toString(arr1));
}
//泛型方法
public static <E> void change(E[] arr,int a,int b) {
E temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
-
使用在接口上
- 概述:接口中使用到不确定的数据类型,需要使用泛型。
- 格式:修饰符 interface 接口名<泛型>{接口的内容}
- 例如:List<E> 泛型接口
- 泛型实例化时机:
- 第一种:在定义接口的实现类的时候可以让接口的泛型实例化
- 创建实现类对象之前泛型就已经确定
例如:ArrlyList implents List<String>
//定义接口
public interface InterfaceA<M> {
void show(M m);
}
//定义类ClassA实现接口
public class ClassA implements InterfaceA<String>{//接口自己被实现前把泛型具体化
@Override
public void show(String m) {
System.out.println("输出传入的内容:"+m);
}
}
//定义测试类
public class ClassA_Test {
public static void main(String[] args) {
ClassA a = new ClassA();
a.show("我爱你中国");
}
}
第二种方式:定义实现类的时候用的也是相同的泛型,创建实现类对象的时候必须确
- 定数据类型
- 定义实现类的时候接口没有指定泛型的具体类型,要求类也得使用这个泛型
例如:ArrlyList <E> implents List<E>
//定义ClassB类实现接口
public class ClassB<M> implements InterfaceA<M>{//接口实现前没有给定泛型的具体数据类型
public void show(M m) { //重写方法
System.out.println("你传入的数据是"+m);
}
}
//定义测试类
public class ClassA_Test {
public static void main(String[] args) {
ClassA a = new ClassA();
a.show("我爱你中国");
//创建实现类对象的时候实例化泛型的数据类型
ClassB<Integer> b = new ClassB<Integer>(); //Integer类型
b.show(12948);
}
}
- 注意事项:接口实现的时候接口和实现类中使用的泛型要保持一致
- 泛型通配符
- 概述: 我们使用别人写好的泛型的类、接口进行传递数据的时候,里面的不确定的数据类型被动在接收,这个不确定的数据类型也得一个符号来暂时代替,我们使用 <?>来暂时代替被动就收的数据类型。 < ?> 就是泛型通配符。
- 基本使用: <?> 可以代表任意的引用数据类型
举例:ArrayList集合中的removeAll(Collection<?> c)
import java.util.ArrayList;
import java.util.List;
public class Demo01 {
public static void print(List<?> list) {//被动的接收传入的数据类型
System.out.println(list);
}
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
list.add("寒冰");
print(list);//list传到这个方法中了,list中的数据类型是Object 把Object带到方法中 ?就代表Object
}
}