Java集合之List

List

List是一种最基础的集合,它是一个有序列表,它的行为基本上与数组相同:从0号索引开始依次放入元素,通过索引确定元素等,区别在于List允许传入null值而且它的长度是可变的

List的一些基本操作:

1、新建

List<Integer> list = new ArrayList<>();

2、添加

list.add(1);

也可以添加一个集合

list.addAll(list1);

这里需求的是一个Collection,可以是ListSet等,只需要实现了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()方法获得一个IteratorIterator也是一个类,它提供了判断是否有下一个元素的方法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、与数组相互转换

通过ListtoArray()方法可以将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);
    }
}

可以看到它实际上是使用了Arrayssort方法,而Arrayssort方法使用了一种被称为TimSort的排序算法,拥有极高的排序效率

TimSort算法比较复杂,但我们在使用时无需关心,只需要知道如何调用即可,sort方法要求传入一个函数式接口,这个接口定义了这样一个比较方法:

int compare(T o1, T o2);

最终sort方法将会把List重新排序,排序后的List将会满足以下条件:

  • 对于List中任意两个元素o1o2,如果它们的索引xy满足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);
}

它调用了CollectionsreverseOrder方法并将自己传入,而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降序排列

参考

使用List - 廖雪峰的官方网站 (liaoxuefeng.com)

java8List.sort()排序功能_gmlic的博客-CSDN博客_java list sort

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值