一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用数组存储对象具有一些弊端,而Java集合就像一种容器,可以动态地把多个对象的引用放入容器中。
数组在内存存储方面的特点:
数组初始化以后,其长度就确定了;
数组声明的类型,就决定了进行元素初始化时的类型,存储的数据类型单一
数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展;
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高;同时对于获取数组中存储元素个数的需求,没有现成的属性和方法可用;
数组存储的数据是有序的、可以重复的,对于无序、不可重复的需求,不能满足。存储数据的特点单一
Java集合类可以用于存储数量不多的多个对象,还可以用于保存具有映射关系的关联数组。
Note:
集合框架:
Collection接口继承树:
图中:实线:继承关系,虚线:实现
Map接口继承数:
/----Collection接口:单列集合,用来存储一个一个的对象
/----List子接口:存储有序的、可重复的数据--->习惯上称作“动态”数组
/--实现类--ArrayList、LinkedList、Vector
/----Set子接口:存储无序的、不可重复的数据--->类比高中数学中的“集合”(无序性、确定性、互异性),通常用set来过滤数据
/--实现类--HashSet、LinkedHashSet、TreeSet
/-----Map接口:双列集合,用来存储一对(Key-Value)一对的数据 --->类比高中函数:y=f(x),多对一
/--实现类--HashMap、LinkedHashMap、Treemap、HashTable、Properties
1. 添加
add(Object obj)
addAll(Collection coll)
2.获取有效元素的个数
int size();
package com.weixinyu;
import org.junit.Test;
import sun.plugin.liveconnect.OriginNotAllowedException;
import java.util.*;
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();//不能new collection,只能用它的实现类,此处用ArrayList举例
//1.add(Object e);将元素e添加到集合coll中
coll.add("aa");
coll.add("bb");
coll.add(123);//自动装箱
coll.add(new Date());
//2.size();获取添加的元素的个数
System.out.println(coll.size());//4
//3.addAll(Collection coll1);将coll1中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add("cc");
coll1.add(456);
coll.addAll(coll1);
System.out.println(coll.size());//6
System.out.println(coll);//[aa, bb, 123, Tue Mar 08 22:12:30 CST 2022, cc, 456]
//4.isEmpty(Collection coll);判断当前集合是否为空
System.out.println(coll.isEmpty());//false
//5.clear();清空集合元素
coll1.clear();
System.out.println(coll1.isEmpty());//true
}
@Test
public void test2(){
Collection coll2 = new ArrayList();
//6.contains(Object obj);判断当前集合中是否包含obj
//注意:判断时会调用obj对象所在类的equals()方法,所以需要考虑是否重写,
例如,string()类重写了,如果自己定义的类也要考虑使用==还是判断变量从而考虑是否需要重写
//要求:向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
coll2.add("bb");
coll2.add(123);//自动装箱
coll2.add(456);//自动装箱
coll2.add(new String("tom"));
coll2.add(false);//Boolean类型,包装类
System.out.println(coll2.contains(new String("tom")));//true
//7.containsAll(Collection coll1);//判断coll1中的所有元素是否都存在于当前集合中
Collection coll3 = Arrays.asList(123,456);//多态,Arrays.asList()返回的是List,List又是Collection的一个子接口
System.out.println(coll2.containsAll(coll3));//true
//8.remove(Object obj);移除该集合中的obj元素
coll2.remove("bb");
System.out.println(coll2);//[123, 456, tom, false]
//9.removeAll(Collection coll1)//从当前集合中移除coll1中所有的元素(移除交集)
Collection coll4 = Arrays.asList(123,4567);
coll2.removeAll(coll4);
System.out.println(coll2);//[456, tom, false]
}
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);//自动装箱
coll.add(456);//自动装箱
Collection coll1 = Arrays.asList(123,4567,89);
//10. retainAll(Collection coll1)//获取当前集合和coll1的交集,结果返回给当前集合
System.out.println(coll.retainAll(coll1));//true
System.out.println(coll);//[123]
//11. equals(Object obj);//判断两个集合是否相等,每个元素作比较,ArrayList()有序
Collection coll2 = Arrays.asList(123);
System.out.println(coll.equals(coll2));//true
}
@Test
public void test4(){
Collection coll = new ArrayList();
coll.add(123);//自动装箱
coll.add(456);//自动装箱
Collection coll1 = Arrays.asList(456,123);
//11. equals(Object obj);//判断两个集合是否相等,每个元素作比较
// ArrayList()有序不仅要考虑元素是否一样,也要考虑顺序;HashSet()无序,所以只需要考虑元素是否一样,不用考虑顺序
System.out.println(coll.equals(coll1));//false
//12. HashCode();//返回当前对象的hash值
System.out.println(coll.hashCode());//5230,一个hashcode值,不用在意
//13.toArray();将集合转换成数组
Object[] arry = coll.toArray();
for(int i = 0;i < arry.length; i++){
System.out.println(arry[i]);//123 456
}
//拓展: 数组--->集合,调用Arrays类的静态方法asList();
List list = Arrays.asList(new String[]{"AA","BB","CC"});//返回值是List,当然也是collection
System.out.println(list);//[AA, BB, CC]
List list1 = Arrays.asList(new int[]{123,456});
System.out.println(list1.size());//1
List list2 = Arrays.asList(new Integer[]{123,456});
System.out.println(list2.size());//2
}
}
//14.iterator(); 集合元素的遍历操作,使用迭代器Iterator接口,Iterator内部的方法:hasNext()和next();
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import org.junit.Test;
public class Main {
@Test
public void collectionTest1(){
Collection coll = new ArrayList();
coll.add("aa");
coll.size();
}
@Test
public void iteratorTest() {
Collection coll = new ArrayList();
coll.add("aa");
coll.add(123);//自动装箱
coll.add(new Date());
//遍历集合中的元素方法一:
//Iterator仅用于遍历集合,本身并不提供承装对象的能力,如果需要创建Iterator对象,则必须有一个被迭代的集合
Iterator ite = coll.iterator();//调用iterator()方法,用以返回一个实现了Iterator接口的对象
// System.out.println(ite.next());//输出结果为aa,因为集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
// System.out.println(ite.next());//123
// System.out.println(ite.next());//输出当前日期
//遍历集合中的元素方法二:
// for (int i = 0;i < coll.size();i++){
// System.out.println(ite.next());
// }
//上面两种方法一般不使用
//遍历集合中的元素方法三:
while(ite.hasNext()){
System.out.println(ite.next());
}
}
}
Note:
迭代器的执行原理:
Iterator ite = coll.iterator();
//hasNext();判断是否还有下一个元素
while(ite.hasNext()){
next();//①指针下移;②将下移后集合位置上的元素返回。
System.out.println(ite.next());
}
迭代器中提供了remove();方法,可以在遍历集合的时候删除集合中的元素(不同于Collection中的remove方法,是两个不同的方法),如下:
Note:如果还未调用next()或在上一次调用next方法后已经调用了remove方法,再调用remove都会报illegalStateException。
@Test
public void iteratorTest2(){
Collection coll = new ArrayList();
coll.add("aa");
coll.add(123);//自动装箱
Iterator iter = coll.iterator();
while(iter.hasNext()){
Object obj = iter.next();
if ("aa".equals(obj)){
iter.remove();//删除aa
}
}
Iterator iter1 = coll.iterator();//回到起点,重新遍历
while (iter1.hasNext()){
System.out.println(iter1.next());//123
}
}
JDK5.0增加了foreach循环,又叫增强for循环,用来遍历集合和数组
@Test
public void forEachTest(){
Collection coll = new ArrayList();
coll.add("aa");
coll.add(123);
//for(集合中元素的类型 局部变量名 : 集合对象),foreach内部仍然调用的是迭代器。
for (Object obj : coll){
System.out.println(obj);
}
//foreach遍历数组
int[] num = new int[]{1, 2, 3};
for(int i : num)
System.out.println(i);
}
面试题:ArrayList、LinkedList、Vector三者的异同?
同:三者都是List接口的实现类,存储数据的特点都是有序可重复
异:
ArrayList:是List接口的主要实现类;是线程不安全的,效率高,底层使用Object[] elementData存储数据
LinkedList:对于频繁的插入、删除操作,使用此类比ArrayList效率高;因为它底层使用双向链表
Vector:是List接口的古老实现类;线程安全的,效率低;底层也使用底层Object[] 数组存储数据
JDK7
ArrayList list = new ArrayList(); //底层创建了长度是10的Object[]数组elementData
list.add(123);// elementData[0] = new Integer(123);
...
list.add(11); //如果此次的添加导致底层elementData数组容量不够,则扩容
//默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中
//建议开发中使用带参构造器:ArrayList list = new ArrayList(int capacity)
JDK8中的变化:
ArrayList list = new ArrayList(); //底层Object[] elementData初始化为{}, 并没有创建长度为10的数组
list.add(123);//第一次调用add()时底层才创建了长度为10的数组,并将123添加到elementData中
//后续的操作和JDK7一样
结:JDK7中的ArrayList的对象的床架类似于单例模式的饿汉式,而JDK8中类似于懒汉式,延迟了数组的创建,节省内存。
JDK7和JDK8无异
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象,体现了LinkedList双向链表
JDK7和JDK8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来数组的两倍
除了从Collection集合中继承的15个方法之外,List集合里添加了一些根据索引来操作集合元素的方法
void add(int index, Object ele)//在index位置插入元素
boolean addAll(int index,Collection eles)//从index位置开始将eles中的所有元素添加进来
Object get(int index)//获取指定index位置的元素,
int indexOf(Object obj)//返回obj在集合中首次出现的位置
int lastIndexOf(Object obj)//返回obj在当前集合中最后一次出现的位置
Object remove(int index)// 移除指定index位置的元素,并返回此元素
Object set(int index, Object ele)//设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex)//返回从fromIndex到ToIndex位置的子集合
常用的方法为增(add)、删(remove)、改(set)、查(get)、长度(size)、遍历(Iterator, for循环,增强for循环)
循环的三种方法:
public class Test {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(123);
list.add("wei");
//方式一:Iterator迭代器
Iterator ite = list.iterator();
while(ite.hasNext()) {
System.out.println(ite.next());
}
System.out.println("***********");
//方式2:foreach循环
for (Object obj : list) {
System.out.println(obj);
}
System.out.println("***********");
//方式3:普通for循环
for ( int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
}
}
/----Collection接口:单列集合,用来存储一个一个的对象
/----Set子接口:存储无序的、不可重复的数据--->类比高中数学中的“集合”(无序性、确定性、互异性),通常用set来过滤数据
/--HashSet:作为Set接口的主要实现类,是线程不安全的;可以存储null值
/--LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,因此对于频繁的遍历操作,LinkedHashSet效率高于HashSet
/--TreeSet:底层结构是红黑树,可以按照添加对象的指定属性,进行排序,所以要求存入的值必须是同一类型,不能string、123、Person等乱存
1.HashSet中元素的添加过程(以HashSet为例)
向HashSet中添加元素a,首先调用元素a所在类的hasCode()方法,计算元素的哈希值,此哈希值接着通过某种算法算出在HasSet底层数组中的存放位置(即:索引位置),判断数组此位置上是否已经有元素:
如果此位置没有其它元素,则元素a添加成功。--->情况1
如果此位置上有其它元素b(或者已经以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不同,则元素a添加成功。--->情况2
如果hash值相同,此时需要调用元素a所在类的equals()方法:
equals()返回true,则表示a与b相同,元素a添加失败
equals()返回false,则元素a添加成功.--->情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk7:元素a放到数组中,指向原来的元素;
jdk8:原来的元素在数组中,指向元素a
public static void main(String[] args) {
Set set = new HashSet();
set.add(456);
set.add(123);
set.add("AA");
set.add("CC");
set.add(129);
Iterator ite = set.iterator();
while (ite.hasNext()){
System.out.println(ite.next());
}
}
//遍历输出的顺序和添加的顺序不一样,但是每次遍历输出的顺序相同。注意:无序性指的是添加元素的时候不是一个挨一个的顺序放的
LinkedHashSet在作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此处数据的前一个数据和后一个数据(双向链表),对应频繁的遍历操作,LinkedHashSet效率高于HashSet
import java.util.*;
public class Test06 {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add("AA");
set.add("CC");
set.add(129);
Iterator ite = set.iterator();
while (ite.hasNext()){
System.out.println(ite.next());
}
}
}
//遍历输出的顺序和添加的顺序一样
import java.util.*;
public class Test06 {
/*
1.向TreeSet中添加的数据,要求是相同类的对象
2.两种排序方式:自然排序(用Comparable接口)和定制排序(Comparator接口)
3. 自然排序中,比较两个对象是否相同的标准为:CompareTo(Object obj)返回0,不再是equals()
4. 定制排序中,比较两个对象是否相同的标准为:compare(Object o1, Object o2)返回0,不再是equals()
*/
public static void main(String[] args) {
TreeSet set = new TreeSet();
//不能添加不同类的对象,异常ClassCastException: java.lang.Integer cannot be cast to java.lang.String
// set.add(456);
// set.add(123);
// set.add("AA");
set.add(123);
set.add(129);
set.add(-3);
set.add(8);
Iterator ite = set.iterator();
while (ite.hasNext()){
System.out.println(ite.next());
}
}
}