java集合(Collection接口下的 List、Set 深入理解)
什么是java集合?1、java集合大致分为Set、List、Queue、Map四种体系。 Set表示无序、不可重复的集合; List代表有序重复的集合; Map代表具有映射关系的集合; Queue代表一种队列集合。2、java集合就像一个容器,可以把多个对象(实际上是对象的引用,习惯都称之为对象)“丢进”该容器中。3、java集合可以记住
什么是java集合?
1、java集合大致分为Set、List、Queue、Map四种体系。
List代表有序、可重复的集合;(有序指存储顺序和取出顺序一致)
Set表示无序、不可重复(元素唯一)的集合;(无序指存储顺序和取出顺序不一致)
Map代表具有映射关系的集合;
Queue代表一种队列集合。
2、java集合就像一个容器,可以把多个对象(实际上是对象的引用,习惯都称之为对象)“丢进”该容器中。
3、java集合可以记住容器中对象的数据类型,从而编写出更简洁、简装的代码。
为什么要使用java集合?
1、在编程时,常常需要集中存放多个数据。可以使用数组来存放多个对象,但是数组的长度是不可变化的。在一开始定义了数组的长度之后,这个长度就是不可变化的,当数据量超过数组的长度之后,数组就无能为力了。
2、数组不能存储具有映射关系的数据。
3、数组只能存储同一类型的元素,而集合可以存储不同类型元素
4、数组可以存储基本数据类型,也可以存储引用数据类型;但是集合只能存储引用类型
为什么会出现集合类?
面向对象语言对事务的体现都是以对象的形式,所以为了方便对多个对象的操作,java就提供了集合类。
集合类的特点?
集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象。
集合只能存储引用类型,那么它要存储基本类型数据该怎么办呢?
在JDK5以后有了自动装箱的功能,JVM会把基本类型的数据自动转化为对应的包装类型,然后进行存储。
自动装箱和自动拆箱详解请看我这篇文章:http://blog.csdn.net/qq_36748278/article/details/77484436
如何访问不同集合中的元素?
1、如果访问List集合中的元素,可以直接根据元素的索引来访问
2、如果访问Map集合中的元素,可以根据每项元素的key来访问其value
3、如果访问Set集合中的元素,则只能根据元素本身来访问(因为把一个对象加入到Set集合时,Set无法记住添加这个元素的顺序,所以Set里的元素不能重复)
集合的使用步骤:
1、创建集合对象
2、创建元素对象
3、把元素添加到集合
4、遍历集合
a、通过集合对象获取迭代器
b、通过迭代器hasNext()方法判断是否有元素
c、通过迭代器对象的next()方法获取元素并移动到下一个位置
Collection
Collection接口:集合的顶层接口
Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法即可用于操作Set集合,也可用于操作List和Queue集合。
Collection接口里定义了如下操作集合的方法:
1、boolean add(Object o):向集合里添加一个元素,成功添加则返回true。
2、boolean addAll(Collection c):把集合c里的所有元素添加到指定集合里,成功添加,返回true。
3、void clear():清除集合里的所有元素,将集合长度变为0。
5、boolean contains(Object o):判断集合中是否包含指定元素
6、boolean containsAll(Collection c):判断集合中是否包含集合c中的所有元素。
7、boolean isEmpty():判断集合是否为空。为空的时候返回true,否则返回false
8、boolean remove(Object o):删除集合中指定的o元素,当集合中含有多个o元素的时候,只删除第一个符合条件的元素,并将返回true
9、boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素(相当于调用该方法的集合 - 集合c)
10、boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(也就是把调用该方法的集合变成该集合和集合c的交集)(交集方法)。
11、int size():返回集合里元素的个数
12、Object[] toArray():该方法把一个集合转换为数组,所有的集合元素变成对应的数组元素
什么是集合的继承体系结构?
由于需求不同,java就提供了不同的集合类。这些集合类的数据结构不同,但是他们都要提供存储和遍历功能的,把他们的共性不断向上提取,最终就像成了集合的继承体系图。
Iterator接口
Iterator iterator():返回一个Iterator对象,用于遍历集合中的元素
测试一下retainAll
假设有集合A和B,A调用此方法。把A与B的交集,交集的结果保存在A中,B中保持不变。
返回值表示A中内容是否发生过改变。
public class TestRtainAll {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("第一个元素");
list.add("第二个元素");
list.add("第三个元素");
ArrayList<String> list1 = new ArrayList<String>();
list1.add("第二个元素");
list1.add("第四个元素");
list1.add("第三个元素");
boolean ret = list.retainAll(list1);
System.out.println(ret);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
System.out.println("------");
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
}
}
输出结果为:
true
第二个元素
第三个元素
------
第二个元素
第四个元素
第三个元素
boolean ret = list.retainAll(list1);
调用retainAll方法之后,list集合就变成了list集合与list1集合的交集。而list1集合保持不变
测试一下aaddAll(Collection c)方法
public class CollectionDemo {
public static void main(String[] args) {
// 创建集合1
Collection<String> c1 = new ArrayList<String>();
c1.add("abc1");
c1.add("abc2");
c1.add("abc3");
//创建集合2
Collection<String> c2 = new ArrayList<String>();
c2.add("def1");
c2.add("abc2");
c2.add("def3");
System.out.println(c1);
c1.addAll(c2);
System.out.println(c1);
c1.removeAll(c2);
System.out.println(c1);
}
}
输出:
[abc1, abc2, abc3]
[abc1, abc2, abc3, def1, abc2, def3] //可见ArrayList()里面的值可以重复
[abc1, abc3]
集合的三种遍历方式
集合的遍历—–Object[] toArray():把集合转换成数组,可以实现集合的遍历(不推荐)
public class CollectionDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("I");
c.add("love");
c.add("you");
Object[] objs = c.toArray(); //数组里存放的是Object类型
for(int i = 0;i < objs.length;i++){
String s = (String)objs[i]; //把Object类型转换为String类型
System.out.println(s);
}
}
}
输出:
I
love
you
集合的遍历—–增强for循环:
public class CollectionDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("I");
c.add("love");
c.add("you");
for(int i = 0;i < c.size();i++){
System.out.println(c.get(i));
}
}
}
输出:
I
love
you
集合的遍历—–Iterator迭代器。迭代器是依赖于集合而存在的。先有集合,再有迭代器。
Iterator是个接口。这个接口有3个方法:
boolean hasNext() :如果仍有元素可以迭代,则返回 true。
E next() : 返回迭代的下一个元素。 最初指向第一个元素的上面开始。
void remove() :从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
public class CollectionDemo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("you");
c.add("and");
c.add("me");
Iterator it = c.iterator(); //Iterator是个接口,所以实际返回的是子类对象。it是集合c的迭代器
while(it.hasNext()){
System.out.println(it.next());
}
}
}
一定要记住next()的强大作用。一定要慎用next()方法,不要多次使用这个方法。因为每次使用都是访问一个对象
比如Iterator遍历一个集合中存放的是Student对象(有name和age属性)的情况的时候;
public class Test {
public static void main(String[] args) {
Collection<Student> c = new ArrayList<Student>();
Student s1 = new Student("梨梨",21);
Student s2 = new Student("熊熊",24);
Student s3 = new Student("菜菜",10);
c.add(s1);
c.add(s2);
c.add(s3);
Iterator it = c.iterator(); //Iterator是个接口,所以实际返回的是子类对象。
while(it.hasNext()){
//System.out.println(((Student) it.next()).getName() + "-----" ((Student)it.next()).getAge());
//报错,因为遍历最后一个对象的时候,next()在getName()的时候已经是最后一个对象元素了,在后面的getAge()的时候又进行了一次next()方法,所以越界了
Student s = (Student)it.next();
System.out.println(s.getName() + "----" + s.getAge());
}
}
}
迭代器为什么不定义成一个类?而是要定义成一个接口?
因为如果是实现类,它就必须提供具体实现。但是集合分为很多种不同的结构,它的每一个的实现类当然也就不同。
Collections类
Collections:针对集合操作的工具类,都是静态方法。
public static <T> void sort(List<T> list):排序。默认情况下是自然排序。
public static <T> int binarySearch(List<?> list,T key):二分查找
public static <T> T max(Collection<?> coll):最大值
public static void reverse(List<?> list):反转
public static void shuffle(List<?> list):随机置换
当ArrayList存储基本包装类时,Collections操作方法使用:
public class CollectionsDemo {
public static void main(String[] args) {
//创建集合对象
List<Integer> list = new ArrayList<Integer>();
//添加元素
list.add(30);
list.add(50);
list.add(10);
list.add(40);
list.add(20);
System.out.println(list);
//public static <T> void sort(List<T> list):排序,默认情况下是自然排序。
Collections.sort(list);
System.out.println(list);
//public static <T> int binarySearch(List<?> list,T key):二分查找
System.out.println(Collections.binarySearch(list, 30));
System.out.println(Collections.binarySearch(list, 300));
//public static <T> T max(Collection<?> coll):最大值
System.out.println(Collections.max(list));
//public static void reverse(List<?> list):反转
Collections.reverse(list);
System.out.println(list);
//public static void shuffle(List<?> list):随机置换(没有固定的顺序)
Collections.shuffle(list);
System.out.println(list);
}
}
输出:
[30, 50, 10, 40, 20]
[10, 20, 30, 40, 50]
2
-6
50
[50, 40, 30, 20, 10]
[10, 30, 20, 50, 40]
当ArrayList存储自定义对象的时候,Collections的这些静态方法的使用如下:
Student类:
public class Student2{
private String name;
private int age;
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试类:
public class CollectionsDemo2 {
public static void main(String[] args) {
//创建集合对象
List<Student2> list = new ArrayList<Student2>();
//创建学生对象
Student2 stu1 = new Student2("caicai",21);
Student2 stu2 = new Student2("lili",12);
Student2 stu3 = new Student2("xiong",34);
Student2 stu4 = new Student2("xiong",34);
Student2 stu5 = new Student2("hehe",12);
//添加元素对象
list.add(stu1);
list.add(stu2);
list.add(stu3);
list.add(stu4);
list.add(stu5);
//排序
Collections.sort(list); //自然排序,报错
//遍历集合
for(Student2 s : list){
System.out.println(s.getName() + "----" + s.getAge());
}
}
}
我们会发现,程序sort()方法报错了。为什么呢?我们来看看sort方法:
public static <T extends Comparable<? super T>> void sort(List<T> list)
可发现列表中的所有元素都必须实现 Comparable 接口。而此时列表中元素是Student类型,所以必须在Student类中实现Comparable 才行。
修改后的Student类如下:
public class Student2 implements Comparable<Student2>{
private String name;
private int age;
public Student2() {
}
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student2 s) {
int num = this.age - s.age;
int num2 = (num == 0 ? this.name.compareTo(s.name) : num);
return num2;
}
}
输出:
hehe----12
lili----12
caicai----21
xiong----34
xiong----34
上面问题还有一种解决办法:通过构造器排序
public static <T> void sort(List<T> list,Comparator<? super T> c)
我们可以重写这个Comparator来达到同样的目的。
如果同时又自然排序和比较器排序,以比较器排序为主
//法二:通过比较器
Collections.sort(list,new Comparator<Student2>() {
@Override
public int compare(Student2 s1, Student2 s2) {
int num1 = s1.getAge() - s2.getAge();
int num2 = (num1 == 0 ? s1.getName().compareTo(s2.getName()):num1);
return num2;
}
});
输出:
hehe----12
lili----12
caicai----21
xiong----34
xiong----34
Collection集合的比较:
List:有序,可重复。(存入和取出顺序一致)
ArrayList:底层数据结构是数组, 查询快,增删慢。
线程不安全,效率高。
Vector:底层数据结构是数组, 查询快,增删慢。
线程安全,效率低。
LinkedList:底层数据结构是链表, 查询慢,增删快。
线程不安全,效率高。
Set:无序,唯一。
HashSet:底层数据结构是哈希表。
保证元素唯一性:hashCode()和equals()方法。
LinkedHashSet:底层数据结构是链表和哈希表
保证元素唯一性:哈希表
保证元素有序(存入和取出顺序一致,是特殊的Set):链表
TreeSet:底层数据结构是红黑树。
保证元素排序:自然排序和比较器排序
自然排序(元素具有比较性):让元素所属的类实现Comparable接口
比较器排序(集合具有比较性):让集合接收一个Comparator的实现类对象
保证元素唯一性:根据比较的返回值是否是0来决定
List接口
List集合的特点是什么?
1、有序(存储和取出的元素一致)
2、可重复的。
List集合的三个子类各有什么特点?
ArrayList
1、底层数据结构是数组,查询快,增删慢。
2、线程不安全,效率高
Vector
1、底层数据结构是数组,查询快,增删慢。
2、线程安全,效率低
LinkedList
1、底层数据结构是链表,查询慢,增删快。
2、线程不安全,效率高。
List的3个子类都在什么情况下使用?
看自己的需求:
1、要求安全性:Vector(不过现在大部分都不用Vector)
2、不要求安全性:ArraayList或者LinkedList
2.1、查询多:ArraayList
2.1、增删多:LinkedList
ArrayList()方法里面为什么允许有重复的值存在?
我们看一下add()方法的源码:
public boolean add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
return true;
}
我们可以看到他永远返回的都是true,也就是他每次添加都能成功,所以也就是可以添加重复的对象。
List集合特有的一些功能(父元素没有的功能)。 要注意索引的范围是否越界。
void add(int index, Object element) :在指定位置添加元素
Object get(int index):获取指定位置的元素
ListIterator listIterator():List集合特有的迭代器。
Object remove(int index):根据索引删除元素,返回被删除的元素
Object set(int index, Object element):根据索引修改元素,返回被修改的元素
public class ListDemo {
public static void main(String[] args) {
ArrayList list = new ArrayList<String>();
list.add("hello");
list.add("you");
list.add(1,"java");
//list.add(4,"no"); //出错,索引越界了
list.add(3,"last"); //可以添加此位置
System.out.println("get方法获取要获得的元素:" + list.get(1));
System.out.println(list);
System.out.println("set方法返回被修改的元素:" + list.set(2,"me"));
System.out.println(list);
}
}
输出:
get方法获取要获得的元素:java
[hello, java, you, last]
set方法返回被修改的元素:you
[hello, java, me, last]
List接口- - - - - ->List集合的特有的两种遍历方式
List集合特有的遍历:size()+get()方法结合(普通for循环)
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("today");
list.add("is");
list.add("yours");
for(int i = 0;i < list.size();i++){
String s = (String)list.get(i);
System.out.println(s);
}
}
}
输出:
today
is
yours
List集合特有的迭代器— >列表迭代器ListIterator(它的父亲是Iterator)
该迭代器继承了Iterator迭代器,所以就可以直接使用hasNext()和next()方法。
特特有的功能是它也可以向前访问元素。
Object previous():获取上一个元素。
boolean hasPrevious():判断是否有上一个元素。
此时就需要注意指针的位置。
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("today");
list.add("is");
list.add("yours");
ListIterator<String> lit = list.listIterator();
//此时指针在最前面,逆向遍历就没有元素。
while(lit.hasPrevious()){
String s = (String)lit.previous();
System.out.println(s);
}
System.out.println("------");
while(lit.hasNext()){
String s = (String)lit.next();
System.out.println(s);
}
System.out.println("------");
//ListIterator还可以往前找元素。先正向后逆向即可
while(lit.hasPrevious()){
String s = (String)lit.previous();
System.out.println(s);
}
}
}
输出:
------
today
is
yours
------
yours
is
today
List接口- - - - - ->Vector集合特有的功能(父类List集合没有的)
void addElement(Object obj):添加功能 ———————————– > 被add()替代
Object elementAt(int index):获取功能 ———————————— > 被get()替代
Enumeration elements():获取功能 ———————————– > 被Iterator iterator()替代
boolean hasMoreElements()— —————————————— > 被hasNext()替代
Object nextElement()—————————————————- > 被next()替代
只需要了解一下。不推荐使用了。
public class VectorDemo {
public static void main(String[] args) {
Vector<String> v = new Vector<String>();
v.addElement("I");
v.addElement("miss");
v.addElement("you");
//第一种方式
for(int i = 0;i < v.size();i++){
String s = (String)v.elementAt(i);
System.out.println(s);
}
System.out.println("--------");
//第二种方式
//Enumeration是接口
Enumeration<String> en = v.elements(); //返回的是实现类的对象
while(en.hasMoreElements()){
String s = (String)en.nextElement();
System.out.println(s);
}
}
}
输出:
I
miss
you
--------
I
miss
you
List接口- - - - - ->LinkedList集合特有的功能(父类List集合没有的)
addFirst(Object o):
addLast(Object o):
getFirst():
getLast():
removeFirst():
removeLast():
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
link.add("I");
link.add("love");
link.add("you");
link.addFirst("first");
link.addLast("last");
System.out.println("removeFirst方法:" + link.removeFirst());
System.out.println("removeLast方法:" + link.removeLast());
System.out.println(link.getFirst());
System.out.println(link.getLast());
System.out.println(link);
}
}
输出:
removeFirst方法:first
removeLast方法:last
I
you
[I, love, you]
并发修改异常
并发修改异常:ConcurrentModificationException
迭代器遍历集合,集合修改元素的时候会发生这个异常。
因为迭代器是依赖于集合而存在的,集合中新添加了元素,而迭代器却不知道,迭代器获取的还是修改之前的那个集合,所以会报错。这个错叫并发修改异常。
也就是说迭代器遍历元素的时候,集合是不可以修改元素的。
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("today");
list.add("is");
list.add("yours");
//迭代器遍历。
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = (String)it.next();
if("is".equals(s)){
//list.add("haha"); //报错。并发修改异常
}
System.out.println(s);
}
}
}
解决方法:
1、迭代器遍历元素,迭代器修改元素。Iterator没有添加方法,而它的子方法ListIterator有。
元素是跟在他刚才迭代的元素后面的。
2、集合遍历元素,集合修改元素。
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("today");
list.add("is");
list.add("yours");
//迭代器遍历。迭代器添加
ListIterator<String> lit = list.listIterator();
while(lit.hasNext()){
String s = (String)lit.next();
if("is".equals(s)){
lit.add("haha");
}
}
System.out.println(list);
}
}
输出:[today, is, yours, haha]
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("today");
list.add("is");
list.add("yours");
//集合遍历元素,集合修改元素
for(int i = 0;i < list.size();i++){
String s = (String)list.get(i);
if("is".equals(s)){
list.add("haha");
}
}
System.out.println(list);
}
}
输出:[today, is, yours, haha]
Set接口
Set集合:无序(存储顺序和取出顺序不一致),值不可以重复。但是虽然Set集合的元素无序,但是作为集合来说,他有自己的存储顺序。
Set接口- - - - - ->HashSet集合类
为什么HashSet存储字符串的时候,字符串相同的值只存储了一个呢?
可以去看我这篇文章:http://blog.csdn.net/qq_36748278/article/details/77842660
Set接口- - - - - ->LinkedHashSet集合类(继承HashSet集合类)
LinkedHashSet:特殊的set集合。底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序(存储和取出顺序是一致的)。
Set接口- - - - - ->TreeSet集合类
TreeSet:底层是二叉树。并且是红黑树。红黑树是一种自平衡二叉树。
TreeSet:能够对元素按照某种规则进行排序。
1、一种叫做自然排序。根据元素的自然顺序对元素进行排序
2、根据创建set时提供的Comparator进行排序。具体取决于使用的构造方法。
public TreeSet():构造一个新的空 set,该 set 根据其元素的自然顺序进行排序
public TreeSet(Comparator<? super E> comparator):构造一个新的空 TreeSet,它根据指定比较器进行排序。
TreeSet是如何保证元素的唯一性和排序的呢?
请看我这篇文章,有源码,解析的很透彻:http://blog.csdn.net/qq_36748278/article/details/77915801#t1
扩展
集合的toString方法的作用原理是什么呢?
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("I");
c.add("am");
c.add("here");
System.out.println(c);
}
输出:
[I, am, here]
现在我们就有个疑惑了,为什么打印输出c输出的不是地址而是值呢?
出现这种情况,我们应该就会猜想集合c应该是调用了toString()方法,所以才没有输出地址值。我们假设是调用了toString()方法。Collection c = new ArrayList();这是多态的用法,所以调用的也可定时ArrayList的toString方法才对。为了解决我们的疑惑,我觉得看源码是直接的方式。
于是我去ArrayList类中找toString方法,但是没有找到,那怎么办呢?只能去ArrayList类的父类中找啦,于是去它的父类AbstractList类中找,还是没有找到,于是我们就去AbstractList类的父类中找,终于找到了toString()方法。
代码如下:
public String toString() {
Iterator<E> it = iterator(); //集合本身调用迭代器方法,得到集合迭代器
if (! it.hasNext()) //如果没有元素,就返回空
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next(); //如果有元素,就进行拼接
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
杂记
1、ArrayList()方法默认的是构造一个初始容量为10的空列表。数据增长:当需要增长时,Vector 默认增长为原来一培,而ArrayList却是原来的一半 。
2、数组求长度用length属性;字符串String求长度用length()方法;集合求长度用size()方法
3、对象数组:数组即可以存储基本数据类型,也可以存储引用数据类型,它存储引用数据类型的时候的数组叫做对象数组。
更多推荐
所有评论(0)