搜索 K
Appearance
博客正在加载中...
Appearance
在集合类中,List 是最基础的一种集合,可以理解为一种有序的线性表。
在数据结构里我们学过线性表,如果我们要往数组里添加或删除某个元素,需要挪动其他位置的元素;如果空间不够了,还要扩容。List 已经自动帮我们实现了这些,我们直接用就可以了,不用关心其内部怎么实现。
List 只是一个接口,常见的实现类有 ArrayList(用的最多,也推荐用)和 LinkedList,LinkedList 就相当于链表,两者的区别跟 数组和链表的区别一样。
List 接口允许我们添加重复的元素,还运行添加 null。
boolean add(E e)boolean add(int index, E e)E remove(int index)boolean remove(Object e)E get(int index)int size()boolean contains(Object e)int indexOf(Object o)。如果元素不存在,就返回 -1。List<String> myWaves = new ArrayList<>();
myWaves.add("爱莉希亚");
myWaves.add("布洛尼娅");
myWaves.add(0,"梅比乌斯");
String ss = myWaves.get(0);
System.out.println(ss);
System.out.println("myWaves.size(): " + myWaves.size());运行结果:
梅比乌斯
myWaves.size(): 3of 方法 自 Java9 之后,可以通过 List 接口提供的 of() 方法,根据给定元素快速创建 List:
List<Integer> list = List.of(1, 2, 5);该方法最多支持 10 个元素。注意事项:
List,调用 add()、remove() 方法会抛出 UnsupportedOperationException。List.of() 方法不接受 null 值,如果传入 null,会抛出 NullPointerException 异常。如果使用 Java8,可以使用 Arrays.asList 方法来快速创建:
List<Integer> list = Arrays.asList(1, 2, 3);Arrays.asList() 方法是 Arrays 的静态方法。这种方式构造的 List 是固定长度的,如果调用 add 方法增加新的元素时会报异常 java.lang.UnsupportedOperationException。这种方式仅适用于构造静态不变的 List。
遍历可以说是集合类最常用的方法了。我们可以用之前学过的遍历数组的方法来遍历:
for (int i = 0; i < myWaves.size(); i++)
System.out.println(myWaves.get(i));但这种方式并不推荐,一是代码复杂,二是因为 get(int) 方法只有 ArrayList 的实现是高效的,换成 LinkedList 后,索引越大,访问速度越慢。
所以我们要始终坚持使用迭代器 Iterator 来访问 List。Iterator 本身也是一个对象,但它是由 List 的实例调用 iterator() 方法的时候创建的。Iterator 对象知道如何遍历一个 List,并且不同的 List 类型,返回的 Iterator 对象实现也是不同的,但总是具有最高的访问效率。
Iterator 对象有两个方法:boolean hasNext() 判断是否有下一个元素,E next() 返回下一个元素。因此,使用 Iterator 遍历 List 代码如下:
for(Iterator<String> it = myWaves.iterator(); it.hasNext(); ){
System.out.println(it.next());
}有童鞋可能觉得使用 Iterator 访问 List 的代码比使用索引更复杂。但是,要记住,通过 Iterator 遍历 List 永远是最高效的方式。并且,由于 Iterator 遍历是如此常用,所以,Java 的 for each 循环本身就可以帮我们使用 Iterator 遍历。把上面的代码再改写如下:
for (String s : myWaves) {
System.out.println(s);
}实际上,只要实现了 Iterable 接口的集合类都可以直接用 for each 循环来遍历,Java 编译器本身并不知道如何遍历集合对象,但它会自动把 for each 循环变成 Iterator 的调用,原因就在于 Iterable 接口定义了一个 Iterator<E> iterator() 方法,强迫集合类必须返回一个 Iterator 实例。
把 List 变为 Array 有三种方法,
Object[] toArray() 方法直接返回一个数组:Object[] array = list.toArray(); 。但这样会丢失类型信息,较少用。toArray() 传入一个类型相同的 Array:Integer[] array = list.toArray(new Integer[3]);如果我们传入的数组大小和 List 实际的元素个数不一致怎么办?根据 List 接口的文档,我们可以知道:
如果传入的数组不够大,那么 List 内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比 List 元素还要多,那么填充完元素后,剩下的数组元素一律填充 null。
实际上,最常用的是传入一个“恰好”大小的数组:
Integer[] array = list.toArray(new Integer[list.size()]);把 Array 变为 List 就简单多了,通过 List.of(T...) 方法最简单:
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);对于 JDK 11 之前的版本,可以使用 Arrays.asList(T...) 方法把数组转换成 List。
前面我们简单演示了下 contains() 方法,其内部是用 equals() 方法判断两个元素是否相等(indexOf() 也是用 equals() 来查找的)。如果我们存放的类型没有实现 equals() 方法,那么 contains() 方法是无效的,我们来演示下。
首先创建一个 Person 类,里面没有实现 equals 方法:
class Person {
public String name;
public int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
}然后测试 contains():
List<Person> myWaves = new ArrayList<>();
myWaves.add(new Person("雷姆", 18));
myWaves.add(new Person("拉姆", 18));
myWaves.add(new Person("艾米莉雅", 18));
boolean b = myWaves.contains(new Person("雷姆", 18));
System.out.println(b); //false可以看到,虽然我们创建了一样的 Person 类(name 和 age 相同),但是 contains() 返回的是 false
因此,要正确使用 List 的 contains()、indexOf() 这些方法,放入的实例必须正确覆写 equals() 方法,否则,放进去的实例,查找不到
至于实例是否 equals 是否相同,则是我们自己定义的逻辑。我们可以认为名字相同既是同一人,也可以认为名字和年龄都一样才是同一人。我们完善 Person 类如下:
class Person {
public String name;
public int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
public boolean equals(Object o){
if(o instanceof Person){
Person p = (Person) o;
return this.name.equals(p.name) && this.age == p.age;
}
return false;
}
}此时我们再使用 contains() 方法,就可以判断是否存在某个实例了。
不过,此时的方法还不完善:
Objects.equals() 静态方法。