Java中的集合类就像一个容器,专门用来存储Java类的对象。
1:为了在程序中可以保存这些数目不确定的对象,JDK中提供了一系列特殊的类,这些类可以存储任意类型的对象,并且长度可变,在Java中这些被统称为集合。
虚线框里填写的都是接口类型,而实线框里填写的都是具体的实现类。
- Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set。其中,List的特点是元素有序、可重复。Set的特点是元素无序,而且不可重复。
- Map:双列集合类的根接口,用于存储具有键(key)、值(Value)映射关系的元素,每个元素都包含一对键值,在使用Map集合时可以通过指定的key找到对应的Value,例如根据一个学生的学号就可以找到对应的学生。
2:Collection接口
表1:Collection接口的方法
方法声明 | 功能描述 |
---|---|
boolean add(Object o) | 向集合中添加一个元素 |
boolean addAll(Collection c) | 将指定Collection中的所有元素添加到该集合中 |
void clear() | 删除该集合中的所有元素 |
boolean remove(Object o) | 删除该集合中指定的元素 |
boolean removeAll(Collection c) | 删除指定集合中的所有元素 |
boolean isEmpty() | 判断该集合是否为空 |
boolean contains(Object o) | 判断该集合中是否包含某个元素 |
boolean containsAll(collection c) | 判断该集合中是否包含指定集合中的所有元素 |
Iterator iterator() | 返回在该集合的所有元素上进行迭代的迭代器(Iterator),用于遍历该集合所有元素 |
int size() | 获取该集合元素个数 |
3:List接口
在List集合中允许出现重复的元素,所以的元素是以一种线性方法进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
表2:List集合常用方法表
方法声明 | 功能描述 |
---|---|
void add(int index,Object element) | 将元素element插入在List集合的index处 |
boolean addAll(int index,Collection c) | 将集合c所包含的所有元素插入到List集合的index处 |
Object get(int index) | 返回集合索引处的元素 |
Object remove(int index) | 删除index索引处的元素 |
Object set(int index,Object element) | 将索引index处元素替换成element对象,并将替换后的元素返回 |
int indexOf(Object o) | 返回对象o在List集合中出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在List集合中最后一次出现的位置索引 |
List subList(int fromIndex,int toIndex) | 返回从索引fromIndex(包括)到toIndex(不包括)出所有元素集合组成的子集合 |
(1):ArrayList集合
在ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。
System.out.println("集合的长度 "+list.size());
System.out.println("第2个元素是 "+list.get(1));
由于ArrayList集合的底层是使用一个数组来保存元素的,在增加或删除指定位置的元素时,会导致创建新的数组,效率比较低,因此不适合做大量的增删操作。但这种数组的结构允许程序通过索引的方式来访问元素,因此使用ArrayList集合查找元素很便捷。
(2):LinkedList集合
该集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。
由此可见。LinkedList集合具有增删元素效率高的特点。
方法声明 | 功能描述 |
---|---|
void add(int index,E element) | 在此列表中指定的位置插入指定的元素 |
void addFirst(Object o) | 将指定元素插入此列表的开头 |
void addlast(Object o) | 将指定元素添加到此列表的结尾 |
Object getFirst() | 返回此列表的第一个元素 |
Object getList() | 返回此列表的最后一个元素 |
Object removeFirst() | 移除并返回此列表的第一个元素 |
Object removeLast() | 移除并返回此列表的最后一个元素 |
4:Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
import java.util.*;
public class EX3 {
public static void main(String[] args){
ArrayList list = new ArrayList(); //创建ArrayList集合
list.add("data_1"); //向该集合中添加字符串
list.add("data_2");
list.add("data_3");
list.add("data_4");
Iterator it = list.iterator(); //获取Iterator对象
while(it.hasNext()){ //判断ArrayList中是否存在下一个元素
Object obj = it.next(); //取出ArrayList集合中的元素
System.out.println(obj);
}
}
}
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。
当通过迭代器获取ArrayList集合中的元素时,都会将这些元素当作Object类型类看待,如果想得到特定类型的元素,则需要进行强制类型转换。
5:JDK5.0新特性——foreach循环
foreach循环是一种更加简洁的for循环,也称增强for循环。foreach循环用于遍历数组或集合中的元素,其语法格式如下:
for(容器中元素类型 临时变量 : 容器变量){
执行语句
}
foreach循环不需要获得容器的长度,也不需要根据索引访问容器中的元素,但它会自动遍历容器中的每个元素。
foreach循环的次数是由容器中的元素的个数决定的,每次循环时,foreach中都通过变量将当前循环的元素记住,从而将集合中的元素分别打印出来。
- 注:当使用foreach循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改。
原因:只是将临时变量 指向了一个新的字符串,这和数组中的元素没有一点关系,而在普通for循环中,是可以通过索引的方式来引用数组中的元素并将其值进行修改的。
6:Set接口
Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set接口主要有两个实现类,分别是HashSet和TreeSet。其中,HashSet根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。Treeset则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。
(1):HashSet集合
HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。当向HashSet集合中添加一个对象时,首先会调用该对象的hashCode()方法来计算对象的哈希值,从而确定元素的存储位置。如果此时哈希值相同,再调用对象的equals()方法来确保该位置没有重复元素。
HashSet集合之所以能确保不出现重复的元素,是因为它在存入元素时做了很多工作。当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值。然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入,如果该位置上有元素存在,则会调用equals()方法让当前存入的元素依次和给位置上的元素进行比较。如果返回的结果是false就将该元素存入集合,返回的结果为true则说明有重复元素,就将该元素舍弃。
定义Student类时要重写hashCode()和equals()方法。
import java.util.*;
class Student{
private String id;
private String name;
public Student(String id,String name){
this.id=id;
this.name=name;
}
//重写toString()方法
public String toString(){
return id+":"+name;
}
//重写hashCode方法
public int hashCode(){
return id.hashCode();
}
//重写equals方法
public boolean equals(Object obj){
if(this==obj){ //判断是否是同一个对象
return true; //如果是,直接返回true
}
if(!(obj instanceof Student)){ //判断对象是否为Student类型
return false; //如果对象不是Student类型,返回false
}
Student stu = (Student)obj; //将对象强转为Student类型
boolean b=this.id.equals(stu.id); //判断id值是否相同
return b; //返回判断结果
}
}
public class Ex9 {
public static void main(String[] args){
HashSet hs = new HashSet(); //创建HashSet对象
Student stu1 = new Student("1","Jack"); //创建Student对象
Student stu2 = new Student("2","Rose");
Student stu3 = new Student("2","Rose");
hs.add(stu1); //向集合存入对象
hs.add(stu2);
hs.add(stu3);
System.out.println(hs); //打印集合中的元素
}
}
7:Map接口
(1):Map接口是一种双列集合,它的每个元素都包含一个键对象key和值对象Value,键和值对象之间存在一种对应关系,成为映射。从Map集合中访问元素时,只要指定了Key,就能找到对应的Value。
(2):HashMap集合
HashMap集合是Map接口的一个实现类,它用于存储键值映射关系,但必须保证不出现重复的键。
注:Map的键必须是唯一的,不能重复,如果存储了相同的键。后存储的值则会覆盖原有的值,简而言之就是键相同,值覆盖。
遍历Map中所有的键值对的两种方法:
https://blog.csdn.net/m0_47305552/article/details/107898853
在Map中还提供了一个values()方法,通过这个方法可以直接获取Map中存储所有值的Collection集合,
import java.util.*;
public class Ex11 {
public static void main(String[] args){
Map map = new HashMap();
map.put("3", "Lucy");
map.put("2", "Rose");
map.put("1", "Jack");
Collection values = map.values();
Iterator it = values.iterator();
while(it.hasNext()){
Object value = it.next();
System.out.println(value);
}
}
}
运行结果如下
Jack
Rose
Lucy
通过调用Map的values()方法获取Map中所有值的Collection集合,然后迭代出集合中的每一个值。
HashMap集合迭代出来元素的顺序和存入的顺序是不一致的。如果想让这两个顺序一致,可以使用Java中提供的LinkedHashMap类,它是HashMap的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系,使Map元素迭代的顺序与存入的顺序一致。
import java.util.*;
public class Ex11 {
public static void main(String[] args){
Map map = new LinkedHashMap();
map.put("3", "Lucy");
map.put("2", "Rose");
map.put("1", "Jack");
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()){
Object key = it.next();
Object value = map.get(key);
System.out.println(key+":"+value);
}
}
}
运行结果如下:
3:Lucy
2:Rose
1:Jack
8:Properties集合
Map接口中还有一个实现类Hashtable,它和HashMap十分相似,区别在于Hashtable是线程安全的。Hashtable存取元素时速度很慢,目前基本上被HashMap类所取代,但Hashtable类有一个子类Properties,在实际应用中非常重要。
HashMap、Hashtable、ConcurrentHashMap线程安全性分析:
https://www.cnblogs.com/gaopengpy/p/11916932.html
Properties主要用来存储字符串类型的键和值,在实际开发中,经常使用Properties集合来存取应用的配置项。假设有一个文本编辑工具,要求默认背景色是红色,字体大小为14px,语言是中文,其配置项如下:
Backgroup-color = red;
Font-size = 14px;
Language = chinese;
在程序中可以使用Properties集合对这些配置项进行存取。
import java.util.*;
public class Ex15 {
public static void main(String[] args){
Properties p = new Properties(); //创建properties对象
p.setProperty("Backgroup-color","red");
p.setProperty("Font-size","14px");
p.setProperty("Language","chinese");
Enumeration names = p.propertyNames(); //得到一个包含所有键的Enumertion对象
while(names.hasMoreElements()){ //循环遍历所有的键
String key = (String)names.nextElement();
String value = p.getProperty(key); //获取对应的值
System.out.println(key+" = "+value);
}
}
}
运行结果:
Language = chinese
Backgroup-color = red
Font-size = 14px
9:JDK5.0新特性——泛型
在Java中引入了“参数化类型(parameterized type)”这个概念,即泛型。它可以限定方法操作的数据类型,在定义集合类时,使用“<参数化类型>”的方式指定该类中方法操作的数据类型,具体格式如下:
ArrayList <参数化类型> list = new ArrayList <参数化类型>();
import java.util.*;
public class Ex16 {
public static void main(String[] args){
ArrayList<String> list = new ArrayList<String>();
list.add("String");
list.add("Collection");
for(String str : list){
System.out.println(str);
}
}
}
运行结果如下:
String
Collection
需要注意的是,在使用泛型后,每次遍历集合元素时,可以指定元素类型为string ,而不是Object,这样就避免了在程序中进行强制类型转换。
思考题
1:请简述List、Set、Map 3个接口存取元素时各有什么特点。
List与Set都是单列元素的集合,它们有一个共同的父接口Collection。
Set 的特点是元素无序,而且不可重复
存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true;当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。
取元素:没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。
List 的特点是元素有序,可重复
存元素:多次调用add(Object)方法时,每次加入的对象按先来后到的顺序排序,也可以插队,即调用add(int index,Object)方法,就可以指定当前对象在集合中的存放位置。
取元素:方法1:Iterator接口取得所有,逐一遍历各个元素
方法2:调用get(index i)来明确说明取第几个。
Map是双列的集合,
存放用put方法:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。
取元素:用get(Object key)方法根据key获得相应的value。
也可以获得所有的key的集合,还可以获得所有的value的集合,
还可以获得key和value组合成的Map.Entry对象的集合。
2:请简述使用泛型的优点。
(1):在使用范型时.就规定了只有某种类型的数据才能放进集合里.你在集合中取数据时就不用进行强制转换.这样就提高了程序的性能
(2):泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。