Java Collection接口下的“ List 集合” 与 “ Set 集合 ”
每博一文案
一个人最好的底牌,就这两个字:
靠谱,是最高级的聪明。
师父说:人生一回,道义一场,你对人对事的态度,藏着你一生的福报。
千金难买好人缘,人活的就是好口碑,别人怎么对你,是你的因果。
你怎么对别人,是你的修行。和谁在一起,决定了你是谁,而你是是谁,就拥有怎样的人生。
一个人为人处世最大的资本,不是才华,也不是钱,而是看他靠不靠谱。
良心,是做事的底线,人品,是做人的底牌。这世上从不缺优秀的人,难得的是靠谱的人。
聪明的人,只适合聊聊天,但靠谱的人,值得常伴心间。聪明只能让你走得快,但靠谱却能决定你走多远。
所以,一个人最好的底牌就是 “靠谱”二字。
人靠不靠谱,就看这几点
生活,不会一帆风顺,活着,很难事事如意。困难关头最需要的就是有人能为你挺身而出。
敢于担当,是人格最高的勋章。
只要你开口,就伸出援手,看到你有难,绝不袖手旁观。
不轻易向人许诺,但答应你的一定会做到,不随意评价别人,但心中自有一杆天平衡量。
你交代的事,件件有着落,你拜托的忙,事事有回音。
对感情,忠诚经得起诱惑,对事情,认真经得起推敲。
纵使人生路上再多的风雨,他给的安全感都像一座大山,随时随地让你踏实和心安。
越靠谱,越福气。
人怕走错路,心怕给错人。
信人别信嘴,交人要交心。
人生有尺,做人有度,什么都可以舍弃,但不可以舍弃内心的底线,什么都可以输掉,但不可以输掉自己的人品。
日子再苦,不能丢了良心,生活再累,不能忘了本分。
只想堂堂正正做人,明明白白做事和人踏踏实实相处,干干净净往来。
—————— 一禅心灵庙语
目录- Java Collection接口下的“ List 集合” 与 “ Set 集合 ”
1. Collection 接口
Collection 接口是 List,Set 和 Queue 接口的父接口,该接口里定义的方法。既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合 。
JDK 不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set 和 List) 实现。
在 Java5 之前,java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK5.0 增加了 泛型 以后,Java 集合可以记住容器中对象的数据类型。
接口是不可 new 的,接口用于多态——> 接口的实现类 。
1.1 Collection 接口方法
- add() 添加元素数据到集合中。
boolean add(E e); // 添加元素数据到该集合当中,添加成功返回true,如果此集合不允许重复,并且已包含指定的元素,则返回false。
- size() 返回此集合中实际存放的元素个数
int size(); // 返回此集合中实际存放的元素个数。 如果此收藏包含超过Integer.MAX_VALUE个元素,则返回Integer.MAX_VALUE 。
- 清空此集合中的元素数据,注意 并不是将 集合置为 null,而是清除集合中存放的元素数据。
void clear(); // 从此集合中删除所有元素(可选操作)。 此方法返回后,集合将为空。
- isEmpty() 判断此集合是否为空,为空返回 true ,不为空返回 false 。
boolean isEmpty(); // 如果此集合不包含元素,则返回 true 。
举例: 未使用泛型的。
import java.util.ArrayList;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
Collection collection = new ArrayList();
// 添加元素
// 注意集合存储的是引用类型的地址值,不可以存储基本数据类型
collection.add(123); // 自动装箱为 Integer包装类
collection.add(456);
collection.add("A");
collection.add(3.14);
collection.add(true);
collection.add(new Person("Tom",19));
int size = collection.size(); // 返回该集合实际存储的元素的个数,不是集合的容量
System.out.println(size);
boolean b1 = collection.isEmpty(); // 判断该集合是否为空,为空返回 true,不为空返回 fasle
System.out.println(b1);
collection.clear(); // 清空该集合中的元素数据
boolean b2 = collection.isEmpty();
System.out.println(b2);
}
}
class Person {
String name;
int age;
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
举例:使用泛型,泛型限定了,集合可以存储的数据类型,同样前提集合只能存储引用类型,不可以存储基本数据类型
import java.util.ArrayList;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
// 多态: Collection 接口,ArrayList 该接口的实现类
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(123);
collection.add(456);
collection.add(789);
System.out.println(collection.size()); // 返回集合实际存储的元素个数
System.out.println(collection.isEmpty()); // 判断该集合是否为空,是返回 true,不是返回 false
}
}
- remove : 通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素。所以对于自定义类型/没有重写 equlas()方法的必须重写 equals() 方法,不然默认调用的是 Object中的 equla()方法(比较的是地址值),具体的说明该文章的后面会说明。
boolean remove(Object o); // 通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
- hashCode() 返回此集合的哈希码值。虽然
Collection
接口不会增加规定为Object.hashCode
方法的常规协定,程序员应该注意的是,它覆盖Object.equals
方法也必须重写Object.hashCode
方法,以满足为Object.hashCode
方法一般合同中的任何类。 特别是c1.equals(c2)
意味着c1.hashCode()==c2.hashCode()
int hashCode(); 返回此集合的哈希码值
- toArrray() 转成对象数组。
Object[] toArray(); // 返回包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。 如果集合适合指定的数组,则返回其中。 否则,将为指定数组的运行时类型和此集合的大小分配一个新数组。
- contains() : 是通过元素的equals方法来判断是否是同一个对象,所以对于自定义类型/没有重写 equlas()方法的必须重写 equals() 方法,不然默认调用的是 Object中的 equla()方法(比较的是地址值),具体的说明该文章的后面会说明。
boolean contains(Object o); //判断该集合中是否存在该 O参数,存在返回 true,不存在返回 false.
举例:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
// 多态: Collection 接口,ArrayList 该接口的实现类
// <Integer> 泛型限定了集合可以存储的数据类型
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(123);
collection.add(456);
collection.add(789);
System.out.println(collection);
collection.remove(456); // 移除该集合中的 456 的内容,integer包装类已经重写了 equals()方法
System.out.println(collection);
int hashCode = collection.hashCode(); // 返回该集合的哈希码值。哈希码值集合存储的类型也是要重写的
System.out.println(hashCode);
Object[] array = collection.toArray(); // 将该集合转换成Object 数组。
System.out.println(Arrays.toString(array));
boolean b1 = collection.contains(123); // 判断该集合是否存在 123该参数内容,注意需要重写 equals()方法的
System.out.println(b1); // 存在返回 true,不存在返回 false;
boolean b2 = collection.contains(456);
System.out.println(b2);
}
}
- addAll() 将指定集合中的所有元素添加到此集合。注意两个集合之间的存储的类型要一致 不然报异常。
boolean addAll(Collection<? extends E> c); // 将指定集合中的所有元素添加到此集合
举例:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
// 多态: Collection 接口,ArrayList 该接口的实现类
// <Integer> 泛型限定了集合可以存储的数据类型
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(123);
collection.add(456);
System.out.println(collection);
Collection<Integer> collection2 = new ArrayList<Integer>();
collection2.add(789);
collection2.add(101112);
collection.addAll(collection2); // 将集合 collection2 中的元素数据添加到 collection集合当中
System.out.println(collection);
}
}
- removeAll(Collection<?> c) 删除该集合中包含此 c 集合参数的元素。删除成功返回 true,删除失败返回 false,需要判断比较所以集合存储的类型必须重写 equals()方法。
boolean removeAll(Collection<?> c); // 删除指定集合中包含的所有此集合的元素(可选操作)。 此调用返回后,此集合将不包含与指定集合相同的元素。
举例:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
// 多态: Collection 接口,ArrayList 该接口的实现类
// <Integer> 泛型限定了集合可以存储的数据类型
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(123);
collection.add(456);
collection.add(789);
System.out.println(collection);
Collection<Integer> collection2 = new ArrayList<Integer>();
collection2.add(456);
boolean b = collection.removeAll(collection2); // 移除该集合包含 collection2元素数据。成功返回 true,失败返回false
System.out.println(collection);
}
}
- retainAll(Collection<?> c) 把交集的结果存在当前集合中,不影响c。说简单一点就是,保留两个集合都相同的数据。成功返回 true。想要成功必须两个集合存储的数据类型都是一样的,并且集合存储的元素数据类型都重写 了 equals()方法
boolean retainAll(Collection<?> c); // 仅保留此集合中包含在指定集合中的元素(可选操作)。 换句话说,从该集合中删除所有不包含在指定集合中的元素。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
// 多态: Collection 接口,ArrayList 该接口的实现类
// <Integer> 泛型限定了集合可以存储的数据类型
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(123);
collection.add(456);
collection.add(789);
System.out.println(collection);
Collection<Integer> collection2 = new ArrayList<Integer>();
collection2.add(789);
boolean b = collection.retainAll(collection2);
System.out.println(b);
System.out.println(collection);
}
}
- 也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。想要返回 true 的话,必须两个集合存储的数据类型都是一样以及存储的元素数据都是一样的,并且集合存储的元素数据类型都重写 了 equals()方法,才可以
boolean containsAll(Collection<?> c); // 如果此集合包含指定 集合中的所有元素,则返回true。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class CollectionTest {
public static void main(String[] args) {
// 多态: Collection 接口,ArrayList 该接口的实现类
// <Integer> 泛型限定了集合可以存储的数据类型
Collection<Integer> collection = new ArrayList<Integer>();
collection.add(123);
collection.add(456);
collection.add(789);
Collection<Integer> collection2 = new ArrayList<Integer>();
collection2.add(123);
collection2.add(456);
collection2.add(789);
boolean b = collection.containsAll(collection2); // 判断两个集合的存储的内容是否完全相同
System.out.println(b);
}
}
2. Iterator迭代器接口
Collection 接口集合继承了该 Iterator 接口,所以只要是在 Collection 接口下的集合实现类都可以使用该 Iterator 接口下的方法如: List,Set。
- Iterator 对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
- GOF 给迭代器模式的定义为:提供一种方法访问一个容器(cotainer)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为集合(容器)而生 。类似于 “公交车上的售票员”,“火车上的乘务员”,“空姐”。
- Collection 接口继承了
java.lang.Iterable
接口,该接口有一个 iterator() 方法,那么所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象。 - Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。
- 集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
- 注意:获取到的迭代器必须和集合结构保持同步,一致性 。
2.1 Iterator 迭代器的执行原理
Iterator 迭代器常用的方法
- hasNext() : 判断当前位置上的迭起器后面是否还有元素数据存储,如果有返回 true,如果没有则返回 false.
boolean hasNext(); // 如果迭代具有更多的元素,则返回true 。
- next() : 获取当前迭代器所在位置上的存储的元素的数据,并且向下移动迭代器的指针。
E next(); // 返回迭代中的下一个元素。
- remove() : 删除此迭代器所指向的集合中的位置上的元素的数据。
default void remove(); // 从底层集合中删除此迭代器返回的最后一个元素
举例:迭代器遍历集合:
- Collection 接口中的方法 iterator() 获取到该集合的迭代器对象。
在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常
Iterator<E> iterator(); // 返回此集合中的元素的迭代器。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// Collection 接口,ArrayList 实现类
Collection collection = new ArrayList();
// 添加元素
collection.add("abc");
collection.add("def");
collection.add(100);
collection.add(new Object());
// 获取到该接口集合的 迭代器对象:
Iterator iterator = collection.iterator();
while(iterator.hasNext()) { // hasNext()判断该迭代器后面是否含有元素数据存储,有返回 true,没有返回fasle
Object o = iterator.next(); // next()获取到给迭代器所指向的位置上存储的元素数据,并向下移动该迭代器。
System.out.println(o);
}
}
}
集合迭代器遍历原理:
2.2 Iterator 迭代器的错误使用
错误方式一:
注意:集合获取到的迭代器必须和对于集合的结构保持一致,同步的,不然,使用迭代器获取集合中的元素数据会报异常 java.util.ConcurrentModificationException
举例如下:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// Collection 接口,ArrayList 实现类
Collection collection = new ArrayList();
// 获取到该接口集合的 迭代器对象:
// 注意了,这时候我们获取到的是当前集合没有添加元素的迭代器。
Iterator iterator = collection.iterator();
// 添加元素
collection.add("abc");
collection.add("def");
collection.add(100);
collection.add(new Object());
// 注意这里我们使用的是,集合没有添加元素时的迭代器,存在问题:
// 集合在生成的迭代器后面,添加了元素数据,集合发生了改变,而迭代器没有改变
// 迭代器和集合没有保持一致性,会出问题。
while(iterator.hasNext()) {
Object o = iterator.next();
System.out.println(o);
}
}
}
报错分析:
集合对象每次调用iterator()方法都得到一个全新的迭代器对象。
上述代码:获取到的 iterator 迭代器对象是,该 Collection 集合没有添加元素数据后的迭代器。
而后 collection 集合在生成的迭代器后面,又添加了元素数据,而迭代器并没有改变(还是之前集合没有添加元素数据的迭代器)。
导致集合的结构(有元素数据的) 于 迭代器(集合没有添加元素数据)不一致。导致通过该不一致的迭代器获取集合中的元素报异常。
具体图示如下:
修正: 让迭代器和集合结构保持一致,让集合先操作完后,再获取到该集合的迭代器。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// Collection 接口,ArrayList 实现类
Collection collection = new ArrayList();
// 添加元素
collection.add("abc");
collection.add("def");
collection.add(100);
collection.add(new Object());
// 获取到该集合操作完之后的迭代器
Iterator iterator = collection.iterator();
while(iterator.hasNext()) { // 判断该集合后面是否有数据有,返回true,没有返回 false
Object o = iterator.next();
System.out.println(o);
}
}
}
错误方式二:
同样是集合结构发生了改变,而迭代器没有同步发生改变,导致迭代器与集合结构没有保持一致性,同步上。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// Collection 接口,ArrayList 实现类
Collection collection = new ArrayList();
// 添加元素
collection.add("abc");
collection.add("def");
collection.add(100);
collection.add(new Object());
// 获取到该集合操作完之后的迭代器
Iterator iterator = collection.iterator();
while(iterator.hasNext()) { // 判断该集合后面是否有数据有,返回true,没有返回 false
Object o = iterator.next();
if("abc".equals(o)) {
collection.remove("abc"); // 删除集合中指定的元素数据,集合结构发生了改变,
// 而迭代器没有发生改变,同步上,继续使用该迭代器会报异常
}
System.out.println(o);
}
}
}
解析:
报异常的问题还是和错误方式一是一样的: 同样是集合结构发生了改变,而迭代器没有同步发生改变,导致迭代器与集合结构没有保持一致性,同步上。
collection.remove("abc");
该集合中删除了一个元素数据,集合结构发生了改变,但是遍历集合所使用的迭代器却并没有发生改变,还是原来集合没有删除数据时的迭代器,导致集合与迭代器不一致,没有同步上报异常。
具体图示如下:
修正:
为了让迭代器与集合结构保持一致性,我们不要调用 Collection 集合对象中的 remove()方法,而是调用 iteraton 迭代器中的 remove()方法,该方法可以删除当前迭代器所指向的元素数据,同时删除对于集合上的数据。
default void remove(); // 从底层集合中删除此迭代器返回的最后一个元素
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// Collection 接口,ArrayList 实现类
Collection collection = new ArrayList();
// 添加元素
collection.add("abc");
collection.add("def");
collection.add(100);
collection.add(new Object());
// 获取到该集合操作完之后的迭代器
Iterator iterator = collection.iterator();
while(iterator.hasNext()) { // 判断该集合后面是否有数据有,返回true,没有返回 false
Object o = iterator.next();
if("abc".equals(o)) {
iterator.remove(); // 删除当前迭代器所指向的元素数据。
}
System.out.println(o);
}
}
}
错误方式三:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
public static void main(String[] args) {
// Collection 接口,ArrayList 实现类
Collection collection = new ArrayList();
// 添加元素
collection.add("abc");
collection.add("def");
collection.add(100);
collection.add(new Object());
Iterator iterator = collection.iterator();
while (iterator.next() != null) { // 注意: 这里 iterator.next()已经向下移动了
System.out.println(iterator.next()); // 这里又向下移动了,可能后面就没有元素了。报异常
}
}
}
解析:
出问题的代码如下:
while (iterator.next() != null) { // 注意: 这里 iterator.next()已经向下移动了
System.out.println(iterator.next()); // 这里又向下移动了,可能后面就没有元素了。报异常
}
这里没有使用 iterator.hasNext() 判断该集合后面是否还含有元素数据,而是使用了 iterator.next() != null 来判断集合,作为循环终止条件。
但是需要注意的是: iterator.next()方法本身就会向下移动迭代器,可能当 while (iterator.next() != null) 判断的时候,就是集合中最后一个元素数据了,这时候 不等于 null,返回的是 true ,进入到 while()循环当中,再执行 iterator.next() 语句报异常,因为这时候的集合中已经没有元素数据可以获取的了,相当于是数组越界了。
3. Collection子接口之二:List接口
- 鉴于 Java 中数组用来存储数据的局限性,我们通常使用 List 代替数组。
- List 集合类中 元素有序,且可重复,可以通过下标访问 。集合中每个元素都有其对应的顺序索引。
- List 容器中的元素都对应一个整数型的序号,记载其在容器中的位置,可以根据下标存取容器中的元素数据。
- JDKAPl 中 List 接口的实现类常用的有 :ArrayList,LinkedList,Vector 。
- ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素,底层使用Object[] elementDate 存储
- LinkedList: 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素,对于频繁的插入,删除操作,使用此类效率比ArrayList 高,底层使用的是双向链表存储
- Vector : 底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素,作为List 接口的古老实现类;线程安全的,效率低,底层使用Object[] elementDate存储。
3.1 List接口常用的方法
- List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
- add(int index,E element): 在 index位置插入 element 元素 数据
void add(int index, E element); // 将指定的元素插入此列表中的指定位置
- get(int index) :获取到集合中指定 index 下标位置上的元素。
E get(int index); // 返回此列表中指定位置的元素。
- indexOf(Object o) : 返回o在集合中首次出现的位置。
int indexOf(Object o); // 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
- lastIndexOf(Object o) : 返回obj在当前集合中末次出现的位置。
int lastIndexOf(Object o); // 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
- remove(int index) : 移除指定index位置的元素,并返回此元素。
E remove(int index); // 删除该列表中指定位置的元素(可选操作)。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 返回从列表中删除的元素。
- set(int index,E element) : 设置指定 index 位置的元素为 element。
E set(int index, E element); // 用指定的元素(可选操作)替换此列表中指定位置的元素。
举例:
import java.util.ArrayList;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
// List 接口,ArrayList 实现类,<Integer> 泛型限定了该集合存储类型
List<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(456);
list.add(123);
list.add(0,999); // 在0号下标添加 999
System.out.println(list.get(0));
System.out.println(list.indexOf(999)); // 获取该 999 元素数据在集合中首次出现的下标位置。
System.out.println(list.lastIndexOf(123)); // 获取该 123元素数据在集合中首次出现的下标位置。
System.out.println(list.remove(1)); // 移除该下标上的元素数据,并返回该元素数据内容
System.out.println(list);
System.out.println(list.set(0, 999999)); // 为指向下标位置上修改元素数据内容,并返回修改前的数值内容
System.out.println(list);
}
}
- subList(int fromIndex,int toIndex) : 返回从 fromIndex 到 toIndex 位置的子集合。注意:是左闭右开的 [fromIndex toIndex)
List<E> subList(int fromIndex,int toIndex); // 返回从fromIndex到toIndex位置的子集合
举例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
// List 接口,ArrayList 实现类,<Integer> 泛型限定了该集合存储类型
List<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(456);
list.add(789);
list.add(999);
List<Integer> list2 = list.subList(1, 3); // 实际获取到的数值是 [1,2] 没有取到3下标上的位置
Iterator<Integer> iterator = list2.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
- addAll((int index,Collection<? extends E> c) : 从index位置开始将c中的所有元素添加进来。注意: 集合添加的数据类型要保持一致才能,添加成功 。
boolean addAll(int index,Collection<? extends E> c); // 将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。 将当前位于该位置(如果有的话)的元素和随后的任何元素移动到右边(增加其索引)。
举例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
// List 接口,ArrayList 实现类,<Integer> 泛型限定了该集合存储类型
List<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(456);
List<Integer> list2 = new ArrayList<Integer>();
list2.add(789);
list2.add(999);
boolean b = list.addAll(0, list2);
System.out.println(b);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
注意在 List 中 remove()方法,重载了 一个参数是 int 类型的下标,另一个是 Object c 引用类型的内容 。需要注意的是,什么时候该 remove() 参数判断的是 int 类型的下标,还是 Object o 引用类型的内容(需要重写 equal()方法,不然默认调用的就是Object 中的 equal()方法比较的是地址值)。
当传的是一个 int 的类型的值表示的就是下标,当传的是 new Integer()的对象类型的就是对象内容上的值。
E remove(int index); // 参数是 int 下标
int indexOf(Object o); // 参数是 Object 引用类型的地址值,需要重写 equals()方法进行判断。
举例: 下面移除的是 int 类型的下标位置上的元素数据内容。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list); // 判断的是 int 类型的下标
for (Object o : list) {
System.out.println(o);
}
}
public static void updateList(List list) {
list.remove(2); // 注意这里移除的对于下标索引位置上的元素的数据
}
}
举例: 下面移除的是 Object 引用类型的地址值上的内容
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList2(list); // 判断的是 int 类型的下标
for (Object o : list) {
System.out.println(o);
}
}
public static void updateList2(List list) {
list.remove(new Integer(2)); // 这里移除的是比较同以内容的元素数据。
// 需要重写 equals()方法
}
}
方法总结:
- 增:add(Object obj)
- 删 : remove(int index),remove(Object obj)
- 改 :set(int index,Object ele)
- 查 :get(int index)
- 插 :add(int index ,Object ele)
- 长度 :size()
- 遍历 : Iterator 迭代器,foreach()增强for循环,普通的循环
3.2 List实现类之一:ArrayList
- ArrayList是 List 接口的典型实现类、主要实现类
- 本质上,ArrayList是对象引用的一个”变长”数组
对于ArrayList 类中的方法基本上就是 Collection 与 List 接口中所继承的方法,所以这里就没有太多必要说明的了 。
ArrayList 有三个构造器
public ArrayList(int initialCapacity) // 构造一个具有指定初始容量的空列表。
public ArrayList() // 默认构造一个初始容量为10的空列表。
public ArrayList(Collection<? extends E> c) // 构造一个包含指定 collection 的元素的列表
这里我们来探讨一下 ArrayList 的扩容问题。
关于 ArrayList的初始化扩容问题,分为两种情况一种是 JDK 7 以及之前的版本,和另外一种 JDK 8 以及之后的版本。
3.2.1 ArrayList JDK7 / JDK 8 的初始化扩容问题
ArrayList 源码分析:JDK7 情况下:
初始化
ArrayList arrayList = new ArrayList(); // 底层创建了长度是 10 的Object[]数组 elementDate
一开始创建 ArrayList 集合对象,底层就创建了一个 长度是 10 的 Object[] elementDate 数组。
JDK7源码:
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData; // 创建一个 Object[] 数组/**
* Constructs an empty list with an initial capacity of ten.
构造一个初始容量为10的空列表。
*/
public ArrayList() {
this(10);
}/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new ObjectinitialCapacity; // 创建一个数组对象。
}
arrayList.add(123);
arrayList.add(456);
arrayList.add(789); // 向ArrayList 集合中添加元素数据
添加元素数据,不足扩容。
arrayList.add(789); // 向ArrayList 集合中添加元素数据。如果此次添加导致底层的 Object[] elementDate 数组容量不够,则会自动扩容。默认情况下,扩容为原来的容量(集合)的 1.5 倍,同时需要将原有的数组中的数据复制到新的数组当中去。
结论: 建议开发中使用尽可能少的扩容,因为数组扩容效率低,建议在使用 ArrayList集合的时候预估计元素的个数,给定一个初始化容量。减少扩容的次数,提高程序的执行效率。
ArrayList list = new ArrayList(int capacity); // 建议创建 ArrayList 对象给定初始值,减少扩容次数,,提高程序的执行效率。
JDK7 扩容源码分析:
arrayList.add(123);
ArrayList 源码分析:JDK8 情况下:
初始化
ArrayList arrayList = new ArrayList(); // 底层object[] elementData初始化为{},并没有创建长度为10的数组。
一开始创建 ArrayList 集合对象,底层object[] elementData初始化为{},并没有创建长度为10的数组,也就是 JDK8 与 JDK7 不同的地方。JDK8 当 arrayList.add(123) 第一次调用 add()时,底层才创建了长度为 10 object[] elementDate 数组,并将数据 123 添加到 elementDate 数组当中去。 后续的容量不足扩容,和 JDK 7 是一样的,扩容原来容量的 1.5 倍
JDK8 源码分析:
总结:
JDK7 会先创建 10 个容量的数组,与单例模式中的懒汉式一样(先创建对象)
JDK8 不会先创建 10 个容量的数组,而是在添加 add() 元素数据的时候,创建 10 个容量的数组,与单例模式中的饿汉式一样,延时了数组的创建,需要的用的时候再创建,节省了内存。
总的来说: 建议开发中使用尽可能少的扩容,因为数组扩容效率低,建议在使用 ArrayList集合的时候预估计元素的个数,给定一个初始化容量。减少扩容的次数,提高程序的执行效率。
ArrayList list = new ArrayList(int capacity); // 建议创建 ArrayList 对象给定初始值,减少扩容次数,,提高程序的执行效率。
3.3 List实现类之二:LinkedList
LinkedList是基于双向链表数据结构实现的Java集合(jdk1.8以前基于双向链表),在阅读源码之前,有必要简单了解一下链表。
先了解一下链表的概念:链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表。
单向链表:单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。
单向循环链表:单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点,形成一个“环”。
双向链表 : 向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
图一:常见链表
双向循环链表:向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。
图二:双向循环链表
通过类图可以看到,LinkedList不仅实现了List接口,而且实现了现了Queue和Deque接口,所以它既能作为List使用,也能作为双端队列使用,也可以作为栈使用。
链表的优点:
- 随机增删元素数据效率高(因为增删元素不涉及到大量元素的位移)
- 由于链表上元素在空间存储上内存地址不连续,所以随机增删元素的时候,不会有大量元素位移,因此随机增删效率较高。
- 在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用 LinkedList集合存储。
链表的缺点:
- 查询效率低,每一次查找某个元素都需要从头节点开始,遍历查找。
- 不能通过数学表达式,计算被查找元素的内存地址,每一次查找都是从头节点开始遍历。直到找到为止,所以 LinkedList 集合检索/查找的效率较低。
- 对于查找集合元素数据的业务比较多时,不建议使用 LInkedList 集合存储。
LinkedList 体系结构
LinkedList 也是实现了 Collection 接口以及 List 接口,所以 LinkedList 常用的方法基本上都是 对于实现接口的方法,这里就不多受说明了。
如下是 LinkedList特有的方法:
- addFirst(E e) : 在该列表开头插入指定的元素。
public void addFirst(E e); // 在该列表开头插入指定的元素。
- addLast(E e) : 将指定的元素追加到此列表的末尾。
public void addLast(E e); // 将指定的元素追加到此列表的末尾。
- getFirst() : 返回此列表中的第一个元素
public E getFirst(); // 返回此列表中的第一个元素
- getLast() : 返回此列表中的最后一个元素
public E getLast(); // 返回此列表中的最后一个元素
- removeFirst() : 从此列表中删除并返回第一个元素。
public E removeFirst(); // 从此列表中删除并返回第一个元素。
- removeLast() : 从此列表中删除并返回最后一个元素。
public E removeLast(); // 从此列表中删除并返回最后一个元素。
举例:
import java.util.LinkedList;
public class LinkedListTest {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<Integer>();
linkedList.add(123);
linkedList.add(456);
linkedList.add(789);
for (Integer integer : linkedList) {
System.out.println(integer);
}
System.out.println("******************");
linkedList.addFirst(000); // 在该 LinkedList 集合的首节点添加元素数据
linkedList.addLast(999); // 在该 LinkedList 集合的尾节点添加元素数据
System.out.println(linkedList.getFirst()); // 获取到该 LinkedList 集合的首节点的元素数据
System.out.println(linkedList.getLast()); // 获取到该 LinkedList 集合的尾节点的元素数据
System.out.println("******************");
for (Integer integer : linkedList) {
System.out.println(integer);
}
System.out.println("******************");
linkedList.removeFirst(); // 删除该 LinkedList 集合首节点的元素数据
linkedList.removeLast(); // 删除该 LinkedList 集合尾节点的元素数据
for (Integer integer : linkedList) {
System.out.println(integer);
}
}
}
3.3.1 LinkedList 类的源码分析
LinkedList List = new LinkedList(); // 内部声明了 Node 类型和 first 和 last 属性默认值都是 null
Node 定义为:内部类,体现了 LinkedList 双向链表的说法。
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element; // 双向链表存储的数据
this.next = next; // 变量记录前一个元素的位置
this.prev = prev; // 变量记录下一个元素的位置
}
}
3.4 List 实现类之三:Vector
Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
Vector 中所有的方法都是线程同步的,都带有 synchronized 关键字,是线程安全的。多线程变成了单线程,效率比较低,使用较少了。关于线程问题,大家可以移步至:Java多线程:多线程同步安全问题的 “三“ 种处理方式 ||多线程 ”死锁“ 的避免 || 单例模式”懒汉式“的线程同步安全问题_ChinaRainbowSea的博客-CSDN博客
因为处理线程安全有更好的方法,所以该 Vector 集合使用的很少。基本上就是没有人用了。
怎么将一个线程不安全的 ArrayList 集合转换成线程安全的呢 ?
使用集合工具类。
java.util.Collections 是集合工具类。
java.util.Collection 是集合的接口。
具体的大家可以移步至:
3.4.1 Vector 的源码分析:
Vector vector = new Vector();
Vecotr( ) 构造器创建对象时,底层都创建了长度为 10 的数组,在扩容方面,默认扩容为原来的数组长度的 2倍。
3.5 ArrayList 和 LinkedList 的异同
- ArrayList 和 LinkedList 两者都是线程不安全,相对线程安全的 Vector ,执行效率高。
- 此外,ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。对于随机访问 get 和 set 。ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。对于新增和删除操作 add(特指插入) 和 remove ,LinkedList 比较占优势,因为 ArrayList 要移动数据。
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
3.6 ArrayList 和 Vector 的区别
- Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步类 (
synchronized
) ,属于强同步类。因此开销就比 ArrayList 要大,访问要慢。正常情况下,大多数的 Java程序员使用 ArrayList 而不是 Vector ,因为同步安全可以由程序员自己来控制。 - Vector 每次扩容请求其大小的 2倍空间,而 ArrayList 是 1.5 倍。Vector 还有一个子类 Stack。
4. Collection子接口之二:Set接口
- Set接口是Collection的子接口,set接口没有提供额外的方法
- Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
- Set 判断两个对象是否相同不是使用 == 运算符,而是根据equals() 方法
- Set 接口下的集合的特点是:存储的是无序的,不可重复的数据。
- 无序性: 不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加的,而是根据数据的哈希值 决定的。
- 不可重复的数据: 保证添加的元素按照equal()判断时,不能返回 true,即:相同的元素只能添加一个。
- 不可通过下标访问,可以使用迭代器访问。
- 对于存放在 Set 容器中的对象,对应的类一定要重写
equals()
方法和hashCode(Object obj)
******方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”** 。 -
4.1 Set实现类之一:HashSet
- HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
- HashSet 是按 Hash 算法来存储集合中的元素,因此具有很好的存取,查找,删除性能。
- HashSet 具有以下特点:
- 无序的
- HashSet 不是线程安全的。
- HashSet 集合中可以存放 null值,但是仅仅只能存储一个 null,因为不可重复性。
- HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也是相等的。
- 对于存放在 Set 容器中的对象,对应的类一定要重写
equals()
方法和hashCode(Object obj)
******方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”** 。 - HashSet 集合中的方法基本上是都是来自 Collection 接口 和 Set 接口中的方法的。
举例:
import java.util.HashSet;
import java.util.Iterator;
public class HashSetTest {
public static void main(String[] args) {
// 创建 HashSet 对象
HashSet<Integer> hashSet = new HashSet<Integer>();
// 添加元素数据
hashSet.add(123);
hashSet.add(123);
hashSet.add(456);
hashSet.add(789);
// 遍历
Iterator<Integer> iterator = hashSet.iterator(); // 获取该集合的迭代器,需要和集合结构同步
while(iterator.hasNext()) {
Integer integer = iterator.next();
System.out.println(integer);
}
}
}
注意所谓的无序指的是:存储元素数据是无序的,不是连续的一块内存地址。
4.1.0 HashSet 添加元素数据的分析
HashSet<Integer> hashSet = new HashSet<Integer>();
// 添加元素数据
hashSet.add(123);
hashSet.add(123);
因为涉及到 HashSet 集合中无法添加重复的数据这以特点的处理。
我们向 HashSet 中添加元素 123 ,首先调用 元素 123 所在类的 hashCode() 方法,来得到该 123 对象的 hashCode(哈希) 值。
然后再根据得到的 hashCode (哈希)值,通过某种散列函数 计算除该对象在 HashSet 集合中底层数组的存储位置(即为:索引下标位置),(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)。具体原因可以移步至:
标签:Set,Java,ArrayList,元素,add,集合,java,public From: https://www.cnblogs.com/TheMagicalRainbowSea/p/17090185.html