Collection接口
1、集合
在集合中分为单例集合和双列集合
1、集合和数组的区别
-
集合
-
数组
相同点:都是容器,都能够用来放入数据;
不同点:
1、数组中既可以放基本类型数据,也可以放入引用类型数据;而集合中只能够放入引用类型数据;
2、数组长度固定;而集合的长度不是固定的;
3、数组中放入的数据都是相同数据类型的;而集合中放入的数据可以不是相同类型的(除去泛型约定之外)
既然接口中定义了所有子类必须要实现的方法,那么不妨来看一下Collection集合中的方法。
2、Collection中的方法
Collection接口继承了Iterable接口,自然而然的也继承了其中的方法。
* add(Object obj); 向集合中添加元素
* addAll(Collection coll1); 将coll1集合中的元素添加到当前的集合中
* size(); 获取添加元素的个数
* isEmpty(); 判断当前集合是否为空
* clear(); 清空集合元素
* contains(Object obj); 判断当前集合是否包含obj
* containsAll(Collection coll1); 判断形参coll1中的所有元素是否都存在于当前集合中
* remove(Object obj); 移除某个元素,会先进行equals()判断
* removeAll(Collection coll1); 从当前集合中移除与coll1中相同的元素
* retainAll(Collection coll1); 获取当前集合和coll1集合的交集,并修改当前集合
* equals(Object obj); 判断当前集合和形参集合的元素是否相同
* hashCode(); 返回当前对象的哈希值
* toArray(); 集合转换为数组
* Iterator<T> iterator(); 获取得到迭代器
* void forEach(Consumer<? super T> action):遍历
增(一个或多个)
删(一个或多个)
判断(判断是否为空、集合中是否包含一个或多个、一个集合和另外一个集合是否相同)
转换(转换成数组)
获取(哈希值、集合大小)
比较(比较两个集合是否存在交集)
画个图来记忆一下:
3、迭代器
1、什么叫迭代
即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
2、迭代器方法
迭代器实际上是一个对象。这个对象中有三个方法:
Iterator:
boolean hasNext() : 判断是否有下一个元素
E next() : 获取下一个元素
void remove()
当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:
在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
3、迭代器和迭代器
其实迭代器这种思想在很多其他的容器思想中也有具体的体现。
那么容器和迭代器的关系是什么样子的?
可以将迭代器看成是容器中的一个副本,在操作副本的同时,也在操作Collection集合。所以二者是同步操作,但是不能够在操作副本的同时,又在操作集合。
在获取得到迭代器的那一刻开始,迭代器就是容器集合的一个副本。所以我们可以在获取得到迭代器之前,来对容器中的元素来进行操作;而在获取得到迭代器之后,不允许再来对迭代器进行操作了。
下面通过一段代码来进行演示:
@Test
public void testCollection(){
Collection collection = new ArrayList();
collection.add("hello");
collection.add(false);
collection.add('A');
final Iterator iterator = collection.iterator();
while (iterator.hasNext()){
final Object next = iterator.next();
if (Objects.equals("hello",next)){
collection.remove("hello");
}
}
}
这里会在控制台上显示:
java.util.ConcurrentModificationException
并发修改异常
而直接使用迭代器来进行操作,则是没有问题的:
@Test
public void testCollection(){
Collection collection = new ArrayList();
collection.add("hello");
collection.add(false);
collection.add('A');
final Iterator iterator = collection.iterator();
while (iterator.hasNext()){
final Object next = iterator.next();
if (Objects.equals("hello",next)){
// collection.remove("hello");
iterator.remove();
}
}
System.out.println(collection);
}
此时看控制台:
[false, A]
则是没有任何影响的。但是在我们使用集合在遍历的时候来进行操作的时候,可能会因为删除操作而出现很多问题。
所以在做删除操作的时候,使用迭代器是最好的选择。
4、使用迭代器注意情况
(1) NoSuchElementException
- 迭代器遍历的过程中, 已经没有元素可以获取了, 仍然继续使用next()方法
(2) ConcurrentModificationException
- 并发修改异常
- 产生的原因: 使用迭代器遍历集合的同时, 使用集合的方法修改了集合
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("java");
list.add("c");
list.add("d");
// 迭代器遍历
Iterator<String> it = list.iterator();
// 判断是否有下一个元素
while (it.hasNext()) {
// 获取下一个元素
String s = it.next();
// 判断: 如果获取到的是java, 就删除
if ("java".equals(s)) {
// 使用集合的删除方法, 会出现并发修改异常
// list.remove(s);
// 使用迭代器的删除方法
it.remove();
}
}
System.out.println(list);
特殊情况, 不会出现并发修改异常:
要删除的元素, 在集合的倒数第二个位置
5、三种遍历选择
- 普通for循环 : 使用索引(针对list集合)
- 迭代器 : 删除集合中元素, 使用迭代器自己的remove()
- 增强for: 只做遍历(本质上也是迭代器,但是表面上没有提供出来而已)
4、泛型
提到集合不得不提的一点,泛型。
泛型在我们使用集合的时候是最常用用到的。
1、泛型的好处
主要来解决了两个问题:
- 将运行时期问题提前到编译期间
- 避免了强转的麻烦
比如下面这段代码:
@Test
public void testCollection(){
Collection collection = new ArrayList();
collection.add("hello");
collection.add(false);
collection.add('A');
final Iterator iterator = collection.iterator();
while (iterator.hasNext()){
final Object next = iterator.next();
System.out.println("获取得到字符串的长度:"+((String)next).length());
}
System.out.println(collection);
}
这里在编译的时候,添加到集合的数据都是Object类型的,但是在进行遍历的时候,就会遇到强制类型转换的问题。
那么就可能会存在着ClassCastException异常问题。
所以使用泛型来解决问题的时候,可以使用下面这种方式:
@Test
public void testCollection(){
Collection<String> collection = new ArrayList();
collection.add("hello");
collection.add(false);
collection.add('A');
final Iterator iterator = collection.iterator();
while (iterator.hasNext()){
final Object next = iterator.next();
System.out.println("获取得到字符串的长度:"+((String)next).length());
}
System.out.println(collection);
}
那么只需要声明泛型之后,再来进行操作的时候,在编译阶段,就不会出现添加进去其他类型的数据。所以这里是将运行阶段的做的事情提前到了编译期间,那么再次在迭代器中来进行操作的时候,只需要直接进行操作即可。
因为在泛型确定的时候,已经确定下来了集合中的容器的元素类型了。从而避免了强制类型转换可能带来的隐患。
2、泛型的声明
1、泛型类
参考ArrayList即可
泛型的确定时机:在创建对象的时候
2、泛型接口
参考Collection集合
泛型的确定时机,分为了两种情况
1、子类实现接口的时候确定;
2、子类实现接口的时候不确定,但是在确定对象的时候确定;
定义一个泛型接口:
public interface GenericInterface <E>{
E getE(E e);
}
第一种方式:
/**
* 第一种使用方式
* @param <String>
*/
public class GenericInterfaceImpl<String> implements GenericInterface<String> {
@Override
public String getE(String string) {
return null;
}
}
在第一种方式中,可以看到在确定了泛型之后,再次来重写其中的方法时,就已经在确定好了方法的类型
第二种方式:
/**
* 第二种使用方式
*/
public class GenericInterfaceImpl1<E> implements GenericInterface<E> {
@Override
public E getE(E e) {
return null;
}
}
然后再来创建对应的测试类来进行测试:
public class TestGeneric {
public static void main(String[] args) {
GenericInterfaceImpl1<Integer> t1 = new GenericInterfaceImpl1<>();
t1.getE()
GenericInterfaceImpl1<String> t2 = new GenericInterfaceImpl1<>();
t2.getE()
GenericInterfaceImpl1<Double> t3 = new GenericInterfaceImpl1<>();
t3.getE()
}
}
在创建对象的时候确定要使用的具体的数据类型,那么在进行添加的时候就需要来使用对应的泛型类型了。
3、泛型方法
参考Arrays.setAll方法
泛型的确定时机:在传递变量值的时候
泛型方法更多的是用来做一种通用性的操作。比如说Arrays.sort方法,在不知道对应的数据类型的情况下来进行遍历方法等等操作。
public static <T> T testOne(T t){
return t;
}
4、泛型的上下边界
在泛型上添加:
<? extends TMP>
这里使用的时候只能够是TMP本身类型或者是TMP的子类
<? super TMP>
这里使用的时候只能够是TMP本身类型或者是TMP的父类。但是一般见到这种方式的时候,使用的都是其本身,也就是TMP数据类型。
public class GenericMethod {
public static void main(String[] args) {
Collection<Number> c1 = new ArrayList<>();
Collection<Object> c2 = new ArrayList<>();
Collection<Integer> c3 = new ArrayList<>();
testOne(c3);
testOne(c2); // 编译报错
testOne(c1);
}
public static void testOne(Collection<? extends Number> t) {
}
}
public class GenericMethod {
public static void main(String[] args) {
Collection<Number> c1 = new ArrayList<>();
Collection<Object> c2 = new ArrayList<>();
Collection<Integer> c3 = new ArrayList<>();
testOne(c3); // 编译报错
testOne(c2);
testOne(c1);
}
public static void testOne(Collection<? super Number> t) {
}
}
5、工具类的使用
这些工具在平常使用的时候都没有怎么在意,但是现在想在这里来记录总结一下:
1、Objects
java中万物皆是对象,java在JDK7之后,提供了来对对象操作的工具类。方法比较少,我们一个一个的来进行总结即可。
-
boolean equals(Object a, Object b)
比较两个对象是否相等。可以看一下对应的源码
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
首先判断两个对象的地址值是否是相等或者是调用对象的equals方法判断是否是相等的(在此之前对a进行判空操作)。
其中这里利用了多态的特性,如果是两个类重写了equals方法,那么运行阶段调用的就是特定类型的equals方法。
那么这里来看一下Object对象中的equals方法:
public boolean equals(Object obj) {return (this == obj);}
- boolean deepEquals
通过字面意思来看的话,深入判断是否相等:
public static boolean deepEquals(Object a, Object b) { if (a == b) return true; else if (a == null || b == null) return false; else return Arrays.deepEquals0(a, b); }
也就是 用来比较两个数组中的元素是否都是相同的,如果有一个不同,那么返回FALSE。
-
int hashCode(Object o)
获取得到对象的哈希Code的值:
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
这里的判断也很简单,如果对象不为空,那么直接返回对象的hashCode的值;如果对象为空,那么对应的哈希值就是0;
- String toString(Object o)
public static String toString(Object o) {
return String.valueOf(o);
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
直接调用String.valueOf方法来输出对象的值。如果对象为空,那么输出Null;如果不为空,那么直接调用对象的toString方法来打印输出即可
- String toString(Object o, String nullDefault)
如果对象为空,那么给一个指定的字符串的值,这里还是比较直接的。
public static String toString(Object o, String nullDefault) {
return (o != null) ? o.toString() : nullDefault;
}
- int compare(T a, T b, Comparator<? super T> c)
对象的比较方法。这里可以看到传入的是一个函数式接口,那么对应的compare方法我们是可以自己来进行书写的。
还可以看到,这里传入的对象的类型是相同类型的。
public static <T> int compare(T a, T b, Comparator<? super T> c) {
return (a == b) ? 0 : c.compare(a, b);
}
- T requireNonNull
如果对象是空的,那么直接抛出空指针异常;如果不是空的,那么返回对象本身;
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
通过这种方式,我们一般放在参数校验的第一行即可。
- T requireNonNull(T obj, String message)
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
这里是判断是否为空对象,如果是空对象,还可以给出异常信息,这个方法比较方便使用;如果不是,返回对象本身;
- boolean isNull和boolean nonNull
public static boolean isNull(Object obj) { return obj == null; }
public static boolean nonNull(Object obj) { return obj != null; }
直接进行的判空操作,相当于是我们自己写的
if(obj == null){ doSomething;}else{ doOtherthing;}
- T requireNonNull(T obj, Supplier
messageSupplier)
相当于是给出是空对象的异常信息;如果不是,那么直接返回对象本身;
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) { if (obj == null) throw new NullPointerException(messageSupplier.get()); return obj; }
- int hash(Object... values)
直接计算出来对象的哈希值
public static int hash(Object... values) { return Arrays.hashCode(values); }
2、Arrays
可以看到Objects类中的方法,那么现在一探究竟。结果打开类中的方法一看,发现很多方法都是重载的,那么看一下这里的静态方法
- 1Arrays.sort(数组名);
对数组进行排序,默认是升序排序
int [] arr1={12,21,13,24,0}; Arrays.sort(arr1); for(int i=0;i<arr1.length;i++){ System.out.print(arr1[i]+" "); }
当然,在看了之后,发现还有另外一种排序方式。可以从指定的下标开始、到指定的下标结束:
int [] arr1={12,21,13,24,0}; Arrays.sort(arr1,2,arr1.length); for(int i=0;i<arr1.length;i++){ System.out.print(arr1[i]+" "); }
发现这种方式以后万一可以用到了呢。
- void fill()
把数组array所有元素都赋值为指定数
int [] arr3={12,21,13,24}; Arrays.fill(arr3,22); for(int i=0;i<arr3.length;i++){ System.out.print(arr3[i]+" "); }
当然除了这种方式,也有和上面的sort方法一样的方式,从指定下标开始:
int [] arr3={12,21,13,24};
Arrays.fill(arr3,0,2,22);
for(int i=0;i<arr3.length;i++){
System.out.print(arr3[i]+" ");
}
但是这里需要注意的是,fill的第三个参数的意思是到第几个索引,但是不包括。也就是左开右闭原则。
- copyOf()
把数组array复制成一个长度为length的新数组,返回类型与复制的数组一致
int[] arr3={12,21,13,24};
int[] arr=Arrays.copyOf(arr3, 6);
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
if (Objects.equals(arr3,arr)){
System.out.println("二者相同");
}
当然,不用想,肯定也有另外一个从指定下标开始拷贝的方法,但是发现不是这个方法名称了
- copyOfRange
从范围来进行copy
int[] arr3 = {12, 21, 13, 24}; int[] arr = Arrays.copyOfRange(arr3, 0, 3); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); // 12 21 13 } if (Objects.equals(arr3, arr)) { System.out.println("二者相同"); }
主要注意的是:同样遵守左开右闭原则
- int binarySearch()
查询元素值在数组中的下标(要求数组中元素已经按升序排列,所以在使用的时候记得排序)
int [] arr3={12,21,13,24}; Arrays.sort(arr3); int bin=Arrays.binarySearch(arr3, 21); System.out.println(bin); // 2
3、Collections
专门针对collection集合来进行的操作
- sort(List
list)
从这里可以看到尽管是Collections,但是这里做到的是对list集合的排序,而没有考虑到其他集合
List c = new ArrayList(); c.add("l"); c.add("o"); c.add("v"); c.add("e"); System.out.println(c); Collections.sort(c); System.out.println(c);
但是通过这里也可以看到,我们可以指定对应的排序规则。如果需要指定,那么我们需要重写对应的Comparator接口。
那么这个来写也很简单。
看一下对应的规则即可
int compare(T o1, T o2);
1-2-------------------->升序;
2-1-------------------->降序;
- shuffle(List<?> list)
对集合进行随机排序
List c = new ArrayList(); c.add("l"); c.add("o"); c.add("v"); c.add("e"); System.out.println(c); // [l, o, v, e] Collections.shuffle(c); System.out.println(c); // [e, v, o, l]
- int binarySearch(List<? extends Comparable<? super T>> list, T key)
查找指定集合中的元素,返回所查找元素的索引
List c = new ArrayList();
c.add("l");
c.add("o");
c.add("v");
c.add("e");
System.out.println(c); // [l, o, v, e]
final int o = Collections.binarySearch(c, "o");
System.out.println("对应的下标是:"+o); // 对应的下标是:1
- boolean replaceAll(List
list, T oldVal, T newVal)
替换批定元素为某元素,若要替换的值存在刚返回true,反之返回false
List c = new ArrayList();
c.add("l");
c.add("o");
c.add("o");
c.add("v");
c.add("e");
System.out.println(c); // [l, o, v, e]
Collections.replaceAll(c, "o", "my");
System.out.println(c);
- void reverse(List<?> list)
反转集合中元素的顺序
List c = new ArrayList(); c.add("l"); c.add("o"); c.add("o"); c.add("v"); c.add("e"); System.out.println(c); // [l, o, o, v, e] Collections.reverse(c); System.out.println(c); // [e, v, o, o, l]
- rotate(List<?> list, int distance)
集合中的元素向后移m个位置,在后面被遮盖的元素循环到前面来
List c = new ArrayList();
c.add("l");
c.add("o");
c.add("o");
c.add("v");
c.add("e");
System.out.println(c); // [l, o, o, v, e]
Collections.rotate(c,2);
System.out.println(c); // [v, e, l, o, o]
- copy(List<? super T> dest, List<? extends T> src)
将集合n中的元素全部复制到m中,并且覆盖相应索引的元素
List m = Arrays.asList("one two three four five six siven".split(" "));
System.out.println(m); // [one, two, three, four, five, six, siven]
List n = Arrays.asList("我 是 复制过来的哈".split(" "));
System.out.println(m); // [one, two, three, four, five, six, siven]
Collections.copy(m,n);
System.out.println(m); // [我, 是, 复制过来的哈, four, five, six, siven]
- swap(List<?> list, int i, int j)
交换集合中指定元素索引的位置
List m = Arrays.asList("one two three four five six siven".split(" "));
System.out.println(m); // [one, two, three, four, five, six, siven]
Collections.swap(m, 2, 3);
System.out.println(m); // [one, two, four, three, five, six, siven]
- fill(List<? super T> list, T obj)
用对象o替换集合list中的所有元素
List m = Arrays.asList("one two three four five six siven".split(" "));
System.out.println(m); // [one, two, three, four, five, six, siven]
Collections.fill(m, "2");
System.out.println(m); // [2, 2, 2, 2, 2, 2, 2]
- 求最大值、最小值
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(55);
list.add(512);
Integer max = Collections.max(list);
Integer min = Collections.min(list);
System.out.println("最大值是:"+max); // 最大值是:512
System.out.println("最小值是:"+min); // 最小值是:5
4、collection和array相互转换
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(55);
list.add(512);
Integer[] integers = list.toArray(new Integer[list.size()]);
for (Integer integer : integers) {
System.out.println(integer);
}
final List<Integer> integers1 = Arrays.asList(integers);
System.out.println(integers1);