List
List
是一种最基础的集合,它是一个有序列表,它的行为基本上与数组相同:从0
号索引开始依次放入元素,通过索引确定元素等,区别在于List
允许传入null
值而且它的长度是可变的
List
的一些基本操作:
1、新建
List<Integer> list = new ArrayList<>();
2、添加
list.add(1);
也可以添加一个集合
list.addAll(list1);
这里需求的是一个Collection
,可以是List
、Set
等,只需要实现了Collection
接口即可
3、插入
list.add(0,2);
第一个参数指定插入位置,第二个参数指定插入元素
同样也可以插入集合
list.addAll(0,list1);
参数要求等同于add()
4、删除
remove()
可以指定索引删除,如果索引超出长度或者小于0将会报错
list.remove(1);
也可以指定元素值删除,将会删除第一个匹配的值,如果没有指定元素则不会做任何操作
list.remove(Integer.valueOf(9)); // 由于直接传入int会被判断为索引删除,需要使用Integer包装类
5、修改
将指定索引的值修改为指定值
list.set(1,23);
6、查找
元素是否存在
list.contains(2)
是否存在指定集合的所有值
list.containsAll(list2)
同样,这里的集合也要求为Collection
从左向右查找第一个符合的元素索引
list.indexOf(5)
从右向左查找第一个符合的元素索引
list.lastIndexOf(5)
7、获得List长度
list.size()
8、判断List是否为空
list.isEmpty()
9、清空List
list.clear();
10、遍历List
尽管可以像数组一样循环根据索引获得List
中的元素,但是并不推荐,因为List
除了ArrayList
以外还可以通过LinkedList
实现,而根据索引获得元素的get()
只在ArrayList
中高效,对于LinkedList
来说,长度越长,索引的效率就越低
为了高效地遍历List
,应当使用迭代器Iterator
来访问List
:
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
int n = iterator.next();
System.out.print(n+"\t");
}
如上,List
提供了iterator()
方法获得一个Iterator
,Iterator
也是一个类,它提供了判断是否有下一个元素的方法hasNext()
和获得下一个元素的方法next()
不仅是List
,所有实现了Iterable
接口的类都可以通过iterator
获得迭代器Iterator
,这个Iterator
由相应的集合类提供,不同的集合类具有不同的Iterator
,但总是有最高的访问效率
由于Collection
继承自Iterable
接口,因此除Map
外,Java提供的集合类都拥有iterator
方法
为了便于迭代,Java提供了foreach
循环:
for(int n:list){
System.out.print(n+"\t");
}
11、与数组相互转换
通过List
的toArray()
方法可以将List
转换为Array
,方法定义如下:
<T> T[] toArray(T[] a);
如果传入的数组大小不大于List
,会返回一个大小等于List
的数组:
List<Integer> list3 = new ArrayList<>();
list3.add(1);
list3.add(2);
Integer[] integers = list3.toArray(new Integer[1]); // 数组大小小于List,返回1 2
Integer[] integers2 = list3.toArray(new Integer[list3.size()]); // 数组大小等于List,返回1 2
如果数组大小大于List
,多余的位置将被填为null
Integer[] integers1 = list3.toArray(new Integer[4]); // 1 2 null null
由于使用了泛型,这里的数组元素类型只能是引用类型而不能是基本类型,例如上面的Integer[]
就不能是int[]
Integer[] integers1 = list3.toArray(new int[4]); //错误
由于该泛型方法的泛型<T>
和List
的泛型<E>
并不相同,所以也可以传入别的类型的数组,例如传入Number[]
Number[] numbers = list3.toArray(new Integer[0]);
但是传入的数组其泛型只能是List
泛型可以转换的,也就是List
泛型本身以及它的父类,如果不符合要求将会报错
Double[] numbers = list3.toArray(new Double[0]); //ArrayStoreException
也可以不传入参数,这样将会返回一个Object[]
数组
通过Arrays
提供的asList()
方法可以将数组转换为List
List<Integer> list4 = Arrays.asList(integers);
查看源码可以得知其返回的是一个List
接口,而实际类型是一个ArrayList
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
12、排序
List
提供了sort()
方法实现对元素排序,如下:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
可以看到它实际上是使用了Arrays
的sort
方法,而Arrays
的sort
方法使用了一种被称为TimSort
的排序算法,拥有极高的排序效率
TimSort
算法比较复杂,但我们在使用时无需关心,只需要知道如何调用即可,sort
方法要求传入一个函数式接口,这个接口定义了这样一个比较方法:
int compare(T o1, T o2);
最终sort
方法将会把List
重新排序,排序后的List
将会满足以下条件:
- 对于
List
中任意两个元素o1
,o2
,如果它们的索引x
,y
满足x<y
,则必然有compare(o1,o2)<=0
例如将一个List<Integer>
升序排序:
list.sort((a,b)->a-b);
降序排列
list.sort((a,b)->b-a);
对于一些比较复杂的对象,也可以实现比较复杂的比较逻辑,例如一个有两个字段的对象Pig
class Pig{
String name;
int age;
public Pig(String name, int age) {
this.name = name;
this.age = age;
}
}
将它先按照name
的字典序升序,如果name
相同则按照age
降序
pigList.sort((a,b)->{
if(a.name != b.name){
return a.name.compareTo(b.name);
}
else{
return b.age - a.age;
}
});
使用Comparator
构造比较器
如果元素具有很多的字段,自己编写匿名函数就会出现大量的嵌套if
,可读性就会很差,此时可以选择使用Comparator
接口中的一些方法构造比较器,大致上分为三种:
-
comparing()
:选择比较的字段,需要传入获得该字段的对应方法:Comparator.comparing(a->a.age)
虽然可以使用
lambda
表达式,但是官方更推荐的用法是传入方法引用,例如:Comparator.comparing(Pig::getName)
-
thenComparing()
:接下来选择的字段,参数等同于comparing()
,可以链式调用:Comparator.comparing(Pig::getName).thenComparing(Pig::getName)
-
reversed()
:逆序,不需要参数Comparator.comparing(Pig::getName).thenComparing(Pig::getName).reversed()
Comparator
借助递归的方式构造比较器,先来看comparing()
:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
它返回一个Comparator
对象,其中的方法就是一个lambda
表达式
再来看thenComparing()
:
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
它使用comparing
创建了一个比较器并传入一个同名方法thenComparing
中:
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
我们可以看到这里先是调用了先前构造的compare
方法进行比较,相同时再使用新创建的compare
方法进行比较,并将这个表达式作为新的Comparator
返回
最后是reversed()
:
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
它调用了Collections
的reverseOrder
方法并将自己传入,而reverseOrder
方法将会返回一个ReverseComparator2
对象,它将compare
修改为:
public int compare(T t1, T t2) {
return cmp.compare(t2, t1);
}
可以看到它就是简单地将传入的参数调转了,因此,使用reversed()
会影响链式调用前面的所有比较器,例如对于Pig
,如果想要先按照name
升序排列,再按照age
逆序排列,就要先写一个逆序比较age
的比较器:
Comparator<Pig> comparator = Comparator.comparing(Pig::getAge).reversed();
再将其传入比较器链中:
pigList.sort(Comparator.comparing(Pig::getName).thenComparing(comparator));
而不能直接写完整的比较链:
pigList.sort(Comparator.comparing(Pig::getName).thenComparing(Pig::getAge).reversed());
这样的比较器会使List
先按照name
降序再按照age
降序排列