Java集合-List
List接口(java.util.List)代表着有序的对象集合, List中包含的元素可以根据它们在List中的内部顺序进行插入、访问、迭代和删除,元素的顺序就是这个数据结构被称为列表的原因。List中的每个元素都有一个索引,第一个元素的索引是0,第二个元素的索引是1。索引的意思是“离List的第一个元素间隔多少个元素”。因为第一个元素在List的开头,所有间隔为0。如果List不是类型化的,使用Java泛型,那么甚至可以在同一个列表中混合不同类型(类)的对象
然而,在时间开发中很少在List中混合不同类型的对象。
List是一个标准的接口,是Collection接口的子类,意味着继承了Collection的特性。
List和Set非常相似,都代表了一组元素的集合,但是也有一些明显的不一样, 这些差异反映在List和Set接口提供的方法中。
第一个不同是List中可以重复添加相同的元素,而Set中只能添加一次。第二个不同是,List中的元素是有顺序的,可以按顺序迭代,Set不会对内部保存的元素的顺序做出任何承诺。
作为 Collection 的子类,Collection中的所有方法在List中同样实用。既然List是个接口,所有初始化时需要具体的实现,可以选择下面的List的实现:
这些实现中, ArrayList应用最广泛。在java.util.concurrent包中也有List的并发类的实现,更多细节后面的文章会讲述。
通过List的实现创建List实例,下面是代码:
List listA = new ArrayList();
List listB = new LinkedList();
List listC = new Vector();
List listD = new Stack();
记住,最常用的是ArrayList,其他的实现我们可以根据具体场景使用。
List中默认的是添加Object,但从JAVA5以后增加了泛型,可以让List中添加的元素类型受到限制,下面是代码:
List<MyObject> list = new ArrayList<MyObject>();
List中只能添加 MyObject的实例对象,同时迭代元素时不许强制类型转换,看看下面的代码:
List<MyObject> list = new ArrayList<MyObject>();
list.add(new MyObject("First MyObject"));
MyObject myObject = list.get(0);
for(MyObject anObject : list){
//do someting to anObject...
}
如果不使用泛型将是下面的样子:
List list = new ArrayList(); //no generic type specified
list.add(new MyObject("First MyObject"));
MyObject myObject = (MyObject) list.get(0); //cast needed
for(Object anObject : list){
//cast needed
MyObject theMyObject = (MyObject) anObject;
//do someting to anObject...
}
注意如何将从列表中检索到的MyObject实例强制转换为MyObject,如果没有设置泛型,编译的时候java只识别Object实例对象,需要强制转换它们的类型。List变量指定泛型是很好得实践,避免了向List中插入错误得类型,能够从List中检索对象,而不必将它们转换为其真实类型, 而且-它帮助代码的读者了解List应该包含什么类型的对象。只有在有充分理由的情况下才应该省略泛型类型。
可以通过List得add()方法插入元素,下面是代码:
List<String> listA = new ArrayList<>();
listA.add("element 1");
listA.add("element 2");
listA.add("element 3");
调用了三次add()方法,增加String到List中。
实际是有可能向List中插入null值的,下面是代码:
Object element = null;
List<Object> list = new ArrayList<>();
list.add(element);
可以将元素插入指定索引位置,List有一个add()方法,第一个参数是索引的位置,第二个参数是元素,下面是代码:
list.add(0, "element 4");
如果List中已经包含元素,那么这些元素现在将在列表的内部序列往后退一个序列,比如在插入新元素前索引是0,然后在0的位置在插入一个元素,则原来的元素索引为1。w element was inserted at index 0, will get pushed to index 1 etc.
可以将一List的所有元素加到另外一个List中,可以使用List的addAll()方法,下面是代码示例:
List<String> listSource = new ArrayList<>();
listSource.add("123");
listSource.add("456");
List<String> listDest = new ArrayList<>();
listDest.addAll(listSource);
上面例子把listSource 中所有的元素添加到listDest ,
addAll()方法的参数是Collection ,所以可以传入 List 或者Set作为参数,意思就是可以把通过addAll()方法把List或者Set元素加到List中。
可以通过 List的索引获取元素,可以用get(int index),下面的代码是通过索引访问List的元素:
List<String> listA = new ArrayList<>();
listA.add("element 0");
listA.add("element 1");
listA.add("element 2");
//access via index
String element0 = listA.get(0);
String element1 = listA.get(1);
String element3 = listA.get(2);
也可以通过迭代按内部顺序访问List的元素,这个后面会讲述。
可以通过List的下面两个方法查找是否包含元素:
List<String> list = new ArrayList<>();
String element1 = "element 1";
String element2 = "element 2";
list.add(element1);
list.add(element2);
int index1 = list.indexOf(element1);
int index2 = list.indexOf(element2);
System.out.println("index1 = " + index1);
System.out.println("index2 = " + index2);
输出结果:
index1 = 0
index2 = 1
lastIndexOf()方法可以查找元素在List中出现的最后一个索引值,下面是代码:
List<String> list = new ArrayList<>();
String element1 = "element 1";
String element2 = "element 2";
list.add(element1);
list.add(element2);
list.add(element1);
int lastIndex = list.lastIndexOf(element1);
System.out.println("lastIndex = " + lastIndex);
输出结果:
lastIndex = 2
元素“element 1”在List中出现了两次,最后一个位置索引是2(第三个元素)。
可以通过 List的contains()方法检查List中是否包含给定的元素:
List<String> list = new ArrayList<>();
String element1 = "element 1";
list.add(element1);
boolean containsElement =
list.contains("element 1");
System.out.println(containsElement);
输出结果:
true
因为List中包含"element 1",为了决定List中是否包含给定的元素,List内部户将要迭代,并且调用equal()方法检查是否与给定的元素相等。既然可以添加null值到List中,那么同样可以检查List中是否包含null值,下面是代码:
list.add(null);
containsElement = list.contains(null);
System.out.println(containsElement);
显然,如果contains()中传入的是null值,那么contains()内部调用的不是equals()方法而是==。
可以通过下面两个方法从List中移除元素:
List<String> list = new ArrayList<>();
String element = "first element";
list.add(element);
list.remove(element);
这个例子首先增加了一个元素然后移除,List的 remove(int index)方法是移除给定索引值对应的元素,所有的后面元素往前移动一位,索引值也相应的减1,下面是代码:
List<String> list = new ArrayList<>();
list.add("element 0");
list.add("element 1");
list.add("element 2");
list.remove(0);
执行完后,List包含 element 1 和 element 2 ,索引值分别是 0 和1。第一个元素 (element 0) 已经被从List中移除。
List接口包含了clear()方法,这个方法可以移除List中的所有元素。从List中删除所有元素也被称为清除List,下面是代码:
List<String> list = new ArrayList<>();
list.add("object 1");
list.add("object 2");
//etc.
list.clear();
首先创建List,其次向List中添加两个元素,第三调用clear()方法,调用后List为空。
List接口中有个retainAll(),它能够保留一个列表中的所有元素,这些元素也存在于另一个列表中。意思就是,retain()方法移除目标List中在给定的List,中不存在的元素, 就是两个List的交集,下面是代码:
List<String> list = new ArrayList<>();
List<String> otherList = new ArrayList<>();
String element1 = "element 1";
String element2 = "element 2";
String element3 = "element 3";
String element4 = "element 4";
list.add(element1);
list.add(element2);
list.add(element3);
otherList.add(element1);
otherList.add(element3);
otherList.add(element4);
list.retainAll(otherList);
首先创建两个List,然后每个List都新加三个元素,第三步调用list的retainAll()方法参数是otherList , list.retainAll(otherList)执行完后, list中只有 list 和 otherList都有的元素也就是, element1 和element3 。
可以通过size()获取List的大小,也就是List中元素的个数:
List<String> list = new ArrayList<>();
list.add("object 1");
list.add("object 2");
int size = list.size();
List接口包含一个subList()方法,这个方法可以创建一个原来List的子集。subList()有两个参数:开始索引和结束索引,第一个索引是原List中对应的元素索引,第二个是结束索引,子集中包含起始索引不包括结束索引,和String的substring()非常相似。下面是代码:
List<String> list = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 4");
List<String> sublist = list.subList(1, 3);
执行完list.subList(1,3)后, sublist will contain the 包含索引为1(第二个)和索引为 2(第三个)元素。记住原来List中半酣四个元素,索引是0到3,调用list.subList(1,3)后将包含索引1,但不包括索引3,因此将元素保留在索引1和索引2处。
可以通过创建啊一个新的Set,然后调用add方法List作为参数,Set会删除List中的重复元素,只保留一个,下面是代码:
List<String> list = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 3");
Set<String> set = new HashSet<>();
set.addAll(list);
注意 List中element 3添加了两次,Set中包含一次,执行后Set中包含元素 element 1, element 2 和 element 3 。
可以通过List的 toArray()方法,将List转换成数组:
List<String> list = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 3");
Object[] objects = list.toArray();
也可以将List转换成指定类型得数组,下面是代码:
List<String> list = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 3");
String[] objects1 = list.toArray(new String[0]);
注意,即使我们将大小为0的字符串数组传递给toArray(),返回的数组中也会包含List中的所有元素,它将具有与List相同数量的元素。
同样可以将数组转换成List,下面是代码:
String[] values = new String[]{ "one", "two", "three" };
List<String> list = (List<String>) Arrays.asList(values);
Arrays.asList()方法可以将数组转成List。
可以调用 Collections 的sort()方法给List排序,之前在前面Collections文章中讲到 ,但是下面我会讲述几种方法:
如果List中得元素都是实现了Comparable (java.lang.Comparable)接口,那么他们可以自行比较,看下面得代码:
List<String> list = new ArrayList<>();
list.add("c");
list.add("b");
list.add("a");
Collections.sort(list);
String类实现了 Comparable接口,可以使用Collections 的sort()方法对他们进行自然排序。
如果List中的对象元素没有实现Comparable接口,或者想通过其他方式对它们排序而不是用compare()的实现,那么可以实现Comparator (java.util.Comparator)接口。下面是使用Comparator类对Car类型的List排序:
public class Car{
public String brand;
public String numberPlate;
public int noOfDoors;
public Car(String brand, String numberPlate, int noOfDoors) {
this.brand = brand;
this.numberPlate = numberPlate;
this.noOfDoors = noOfDoors;
}
}
下面代码是对List中的Car对象排序:
List<Car> list = new ArrayList<>();
list.add(new Car("Volvo V40" , "XYZ 201845", 5));
list.add(new Car("Citroen C1", "ABC 164521", 4));
list.add(new Car("Dodge Ram" , "KLM 845990", 2));
Comparator<Car> carBrandComparator = new Comparator<Car>() {
@Override
public int compare(Car car1, Car car2) {
return car1.brand.compareTo(car2.brand);
}
};
Collections.sort(list, carBrandComparator);
上面是实现 Comparator的例子,实现只是简单的比较了Car的brand属性,也可以再实现Comparator 比较number plates或者门的数量noOfDoors属性, 同样可以使用Lambda表达式实现Comparator,下面是代码示例:
List<Car> list = new ArrayList<>();
list.add(new Car("Volvo V40" , "XYZ 201845", 5));
list.add(new Car("Citroen C1", "ABC 164521", 4));
list.add(new Car("Dodge Ram" , "KLM 845990", 2));
Comparator<Car> carBrandComparatorLambda =
(car1, car2) -> car1.brand.compareTo(car2.brand);
Comparator<Car> carNumberPlatComparatorLambda =
(car1, car2) -> car1.numberPlate.compareTo(car2.numberPlate);
Comparator<Car> carNoOfDoorsComparatorLambda =
(car1, car2) -> car1.noOfDoors - car2.noOfDoors;
Collections.sort(list, carBrandComparatorLambda);
Collections.sort(list, carNumberPlatComparatorLambda);
Collections.sort(list, carNoOfDoorsComparatorLambda);
可以通过几种不同的方法迭代List:
第一种方法使用Iterator 迭代List,下面是代码:
List<String> list = new ArrayList<>();
list.add("first");
list.add("second");
list.add("third");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String next = iterator.next();
}
可以调用List的iterator()Iterator,一旦获取了Iterator ,就可以一直调用 hasNext()方法循环直到返回fasle, 调用hasNext()是在while循环中完成的。While内部循环,可以调用Iterator的 next() 方法获取下一个元素。如果List使用了泛型,那么可以在while循环中保存一些对象转换。下面是代码:
List<String> list = new ArrayList<>();
list.add("first");
list.add("second");
list.add("third");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String obj = iterator.next();
}
第二种方法For-Each循环是在Java5中引入的,下面是代码:
List list = new ArrayList();
list.add("first");
list.add("second");
list.add("third");
for(Object element : list) {
System.out.println(element);
}
for循环对列表中的每个元素执行一次,在for循环中,每个元素依次绑定到obj变量,下面是使用泛型的List迭代:
List<String> list = new ArrayList<String>();
//add elements to list
for(String element : list) {
System.out.println(element);
}
注意这人的泛型是String, 因此,可以将for循环中的变量类型设置为String。
第三种方法是使用标准的for循环迭代:
List list = new ArrayList();
list.add("first");
list.add("second");
list.add("third");
for(int i=0; i < list.size(); i++) {
Object element = list.get(i);
}
For循环创建一个int变量,初始值是0,然后循环,直到i的值等于List的大下停止,也就是小于List的大小时一直循环,i的值每次加1,for循环内部可以使用List的get()方法获取元素,下标索引为i。
下面是使用了泛型String:
List<String> list = new ArrayList<String>();
list.add("first");
list.add("second");
list.add("third");
for(int i=0; i < list.size(); i++) {
String element = list.get(i);
}
注意此时For循环内部的变量是String,因为List的泛型是String,List中只能包含String对象,因此编译后get()方法返回的是String类型,不需要强制转换。
第四种方式是是由Java Stream API迭代List,为了迭代List,需要从List中获取Stream ,可以通过List的 stream()方法获取,下面是代码:
List<String> stringList = new ArrayList<String>();
stringList.add("abc");
stringList.add("def");
Stream<String> stream = stringList.stream();
最后一行代码调用了List的 stream() 方法,从List中获取了Stream,一旦获取到了Stream,就可以调用Stream的forEach()方法迭代,下面是例子:
List<String> stringList = new ArrayList<String>();
stringList.add("one");
stringList.add("two");
stringList.add("three");
Stream<String> stream = stringList.stream();
stream
.forEach( element -> { System.out.println(element); });
调用forEach()方法,将迭代Stream 内部的所有元素,Consumer 为流中的每个元素调用作为参数传递给forEach()方法的使用者,更多的Stream内容后续文章会讲解,或者参考Java Stream API Tutorial.