集合
在编程时,可以使用数组来保存多个对象。使用数组的缺点是数组的长度不可变化,一旦初始化了数组的长度时,这个数组的长度就无法进行改变。如果想要保存数量在变化的数据时 ,使用数组的方法就不可取了。
为了保存数量不确定数据,以及保存具有映射关系的数据,Java提供了Collection和Map接口。
一、Collection集合
Collection接口中声明的方法:
public boolean add(E e):把给定的对象添加到当前集合中。
public void clear() :清空集合中所有的元素。
public boolean remove(E e): 把给定的对象在当前集合中删除。
public boolean contains(E e): 判断当前集合中是否包含给定的对象。
public boolean isEmpty(): 判断当前集合是否为空。
public int size(): 返回集合中元素的个数。
public Object[] toArray(): 把集合中的元素,存储到数组中。
1、Collection的子接口——List接口
是最常用的接口。是有序集合,允许有相同的元素。使用 List 能够精确地控制每个元素插入的位置,用户能够使用索引(元素在 List 中的位置,类似于数组下标)来访问 List 中的元素,与数组类似。
List接口的实现类:
集合名 | 底层实现原理 | 特点 |
---|---|---|
ArrayList | 数组 | 查询快,增删慢 |
LinkedList | 链表 | 查询慢,增删快 |
Vector(了解) | 数组 | 和ArrayList相似,但是它是线程安全的 |
List接口中特有的方法:
① public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
② public E get(int index):返回集合中指定位置的元素。
③ public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
④ public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值为更新前的元素。
(1)ArrayList
(1) 什么是ArrayList
java.util.ArrayList 是大小可变的数组的实现,存储在内的数据称为元素。此类提供一些方法来操作内部存储的元素。 ArrayList 中可不断添加元素,其大小也自动增。
(2)ArrayList的声明
格式: ArrayList 名称=new ArrayList<>();
E:表示泛型:表示该集合要存储哪种引用类型的数据。如果要存储字符串就写为
举例:
//1.声明一个存储整数类型的ArrayList
ArrayList<Integer> intList=new ArrayList<>();
//2.声明一个存储字符串类型的ArrayList
ArrayList<String> strList=new ArrayList<>();
//3.声明一个存储学生对象的ArrayList
ArrayList<Student> stuList=new ArrayList<>();
//4.使用List多态形式创建:List是ArrayList的父接口
List<Integer> list=new ArrayList<>();
//5.使用Collection多态的形式创建:Collection是ArrayList的爷接口
//缺点:无法使用子接口或者子类中的特有方法。
Collection<Integer> list2=new ArrayList<>();
**注意事项:**ArrayList对象不能存储基本类型,只能存储引用类型的数据。所以不能写ArrayList等,但是存储基本数据类型对应的包装类型是可以的。
(3)ArrayList类的基本方法
1.public boolean add(E e):往集合中添加元素,默认添加到尾部。
2.public int size():返回集合中的元素个数
3.public E remove(int index或者Object obj):删除指定下标的元素或者元素,返回删除的值
4.public E get(int index):返回指定下标的值
举例:
//1.public boolean add(E e):往集合中添加元素,默认添加到尾部。
ArrayList<Integer> intList=new ArrayList<>();
intList.add(1);
intList.add(-1);
intList.add(3);
System.out.println(intList);//[1, -1, 3]
//2.public int size():返回集合中的元素个数
System.out.println(intList.size());//3
//3.public E remove(int index或者Object obj):删除指定下标的元素或者元素,返回删除的值
int i=intList.remove(0);//1
//4.public E get(int index):返回指定下标的值
int j= intList.get(0);
(2) LinkedList
LinkedList集合数据存储的结构是链表结构。方便元素添加、删除。是一个双向链表。
LinkedList集合提供了大量首尾操作的方法(特有):
① public void addFirst(E e) :将指定元素插入此列表的开头。
② public void addLast(E e) :将指定元素添加到此列表的结尾。
③ public E getFirst() :返回此列表的第一个元素。
④ public E getLast() :返回此列表的最后一个元素。
⑤ public E removeFirst() :移除并返回此列表的第一个元素。
⑥ public E removeLast() :移除并返回此列表的最后一个元素。
⑦ public E pop() :从此列表所表示的堆栈处弹出一个元素。
⑧ public void push(E e) :将元素推入此列表所表示的堆栈。
⑨ public boolean isEmpty() :如果列表不包含元素,则返回true。
List集合的遍历
声明一个存储字符串类型的ArrayList,并添加元素
ArrayList<String> strList=new ArrayList<>();
strList.add("富强");
strList.add("民主");
strList.add("文明");
strList.add("和谐");
strList.add("自由");
1. for循环遍历:
//通过size()方法获取到集合的长度
for(int i=0;i<strList.size();i++) {
//使用get(int index)方法根据下标获取元素
System.out.println(strList.get(i));
}
2. 增强for循环遍历:
for(String str:strList) {
System.out.println(str);
}
3. 使用迭代器进行遍历:
使用步骤:
1.使用public Iterator iterator()来获取集合对应的迭代器
2.通过it引用使用public boolean hasNext()来判断是否还有元素进行迭代,如果有则返回true
3.使用next()方法获取元素
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
//1.使用public Iterator iterator()来获取集合对应的迭代器
Iterator<String> it= strList.iterator();
//2.通过it引用使用public boolean hasNext()来判断是否还有元素进行迭代,如果有则返回true
while(it.hasNext()) {
//3.使用next()方法获取元素。
System.out.println(it.next());
}
4.使用jdk1.8新特性foreach进行遍历
格式:
list名称.forEach(变量名->{
System.out.println(变量名);
})
//item表示每次获取到的元素
strList.forEach(item->{
System.out.println(item);
});
练习:用上述方式遍历linkedList
总结:
1.当需要对数据进行大量的查询时,使用ArrayList,底层是数组实现,查询快,增删慢。
2.当需要对数据进行大量的增删操作时,使用LinkedList,底层是链表实现,增删快,查询慢。
2、Collection的子接口——Set接口
同样继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加 严格了。与 List 接口不同的是, Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
名称 | 底层实现 | 特点 |
---|---|---|
HashSet | 哈希表 + 链表+红黑树 | 无索引,不可存储重复元素,存取无序 |
LinkedHashSet | 哈希表 + 链表 | 无索引,不可存储重复元素,保证存取顺序 |
TreeSet(了解) | 二叉树 | 一般就用于排序 |
1、HashSet
特点:
1.不允许存储重复的元素。
2.没有索引,没有带索引操作的方法,所以不能使用普通的for循环进行遍历。
3.是一个无序的集合,存储元素和取出元素的顺序有可能不一致。
4.底层是一个哈希表结构。(查询速度非常快)。
注:HashSet是根据对象的哈希值来缺点元素在集合中的存储位置,因此具有良好的存储和查找性能。保证元素唯一性的方式依赖于:hashcode和equals方法。
(1)HashSet的声明
//1.声明存储整型的HashSet
HashSet<Integer> intSet=new HashSet<>();
intSet.add(12);
intSet.add(11);
intSet.add(10);
System.out.println(intSet);//[10,11,12]
//2.使用多态的方式进行声明
Set<String> strSet=new HashSet<>();
strSet.add("徐悲鸿画马");
strSet.add("齐白石画虾");
strSet.add("周杰伦画饼");
(2)HashSet元素的唯一性
//声明一个能存储字符串类型的hashSet
HashSet<String> objSet=new HashSet<>();
//添加重复的元素
objSet.add("kaler");
objSet.add("kaler");
objSet.add("young");
objSet.add("young");
System.out.println(objSet);//[kaler, young] 打印出来的元素是不重复的
HashSet是如何实现元素不重复的?
存储的数值 | 100 | 125 | 150 | 200 | 225 |
---|---|---|---|---|---|
hashcode值(举例) | 12321 | 32112 | 45654 | 65445 | 78998 |
通过Hash算法计算存储位置 | 0 | 2 | 1 | 2 | 2 |
存储逻辑:
首先计算出存储元素元素的hash(比如123),去集合中查询是否已经存储了hash值为123的这个元素。如果没有则直接添加,如果已经存在,使用equals方法去比较存储的内容是否相同,相同则不添加,不相同则在相同hash值存储元素的下方使用链表进行存储。
hashCode
举例:
//一般来说,equals相同,hashcode相同
String str="abc";
String str2=new String("abc");
System.out.println(str.equals(str2));//true
System.out.println(str.hashCode());//96354
System.out.println(str2.hashCode());//96354
//equals不同,hashcode不一定不同
System.out.println("通话".hashCode());//1179395
System.out.println("重地".hashCode());//1179395
(2)HashSet存储自定义类型的元素
定义学生类:
public class Student {
//成员变量
private String name;
private int age;
//有参构造方法
public Student(String name,int age) {
this.name=name;
this.age=age;
}
//重写toString方法
@Override
public String toString() {
return "[姓名:"+this.name+",年龄"+this.age+"]";
}
}
HashSet<Student> stuSet=new HashSet<>();
stuSet.add(new Student("彭于晏", 18));
stuSet.add(new Student("陈冠希", 19));//100
stuSet.add(new Student("陈冠希", 19));//123
stuSet.add(new Student("吴彦祖", 20));
System.out.println(stuSet);
/*
打印结果:
[姓名:陈冠希,年龄19],
[姓名:陈冠希,年龄19],
[姓名:吴彦祖,年龄20],
[姓名:彭于晏,年龄18]
*/
注:发现上述存储的对象不唯一。由于每个对象的地址值不同,所以hashcode值不同。因此是直接可以存储的。
如何保证对象元素的唯一?
解决:重写hashcode方法
思路:因为在存储时只是计算new Student()对象的hash值,每个对象地址不一致,因此hashCode肯定也不一致,HashSet是直接存储的。是跟成员变量的值无关的。但是我们可以通过重写使用name和age两个成员变量来计算其hashcode,此时hashcode的值就和成员变量有关。
总结:对象的name和age一样时,让对象的hashcode一样,不让HashSet直接存储。
(1) 在Student类下重写hashcode方法
@Override
public int hashCode() {
//返回name值的hashcode+年龄
return this.name.hashCode()+this.age;
}
此时name和age相同的对象,hashCode一致了,此时两个对象的equals比较还是false,因此还是会存储进去。
(2) 再在Student类下重写equals方法
//逻辑:判断成员变量name和age是否都一致,如果一致返回true。
@Override
public boolean equals(Object obj) {
if(obj instanceof Student) {
Student stu=(Student)obj;
if(stu.name.equals(this.name) && stu.age==this.age) {
return true;
}
}
return false;
}
总结:如果要保证存储对象的唯一性。根据HashSet存储元素时的判断,就必须同时重写hashCode和equals方法。保证equlas不同时,hashCode不同。
//打印结果:此时就没有重复的对象了。
[姓名:彭于晏,年龄18], ,
[姓名:吴彦祖,年龄20],
[姓名:陈冠希,年龄19]
2、LinkedHashSet
操作和HashSet相似,底层是由哈希表和链表实现。
3、TreeSet
TreeSet会对存储的元素进行自动排序。
TreeSet<Integer> tree=new TreeSet<>();
tree.add(10);
tree.add(7);
tree.add(18);
tree.add(4);
System.out.println(tree);//[4, 7, 10, 18]
TreeSet<String> strTree=new TreeSet<>();
strTree.add("cba");
strTree.add("aba");
strTree.add("bba");
System.out.println(strTree);//[aba, bba, cba] 根据首字母进行排序
Set的遍历
由于Set集合没有索引,没有带索引操作的方法,所以不能使用普通的for循环进行遍历。
1.使用增强for循环遍历。
2.使用iterator迭代器进行遍历。
3.使用jdk1.8新特性进行遍历。
4、Collections工具类
常用方法
1.public static boolean addAll(Collection,T…element),往集合里面添加一些元素
2.public static void shuffle(List<?> list):打乱集合顺序
3.public static void sort(List list):将集合中元素按照默认规则排序
4.public static void sort(List list,Comparator<? super T> ) :将集合中元素按照
//1.public static <T> boolean addAll(Collection<T>,T....element),往集合里面添加一些元素
List<Integer> list=new ArrayList<>();
Collections.addAll(list, 89,100,20,46);
System.out.println(list);//[89, 100, 20, 46]
//2.public static void shuffle(List<?> list):打乱集合顺序
Collections.shuffle(list);
System.out.println(list);//[20, 46, 100, 89]
//3.public static <T> void sort(List<T> list):将集合中元素按照默认规则排序
Collections.sort(list);
System.out.println(list);//[20, 46, 89, 100]
//4.public static <T> void sort(List<T> list,Comparator<? super T> ) :将集合中元素按照
5、比较器–Comparator接口
根据上述代码,使用sort方法是对元素默认进行升序排列,如何按照降序进行排序。这就需要使用Comprator比较器自定义规则。
使用方法:public static void sort(List list,Comparator<? super T> )
List<Integer> list=new ArrayList<>();
Collections.addAll(list, 89,100,20,46);
System.out.println(list);//[89, 100, 20, 46]
//使用匿名内部类的方式,创建Comparator接口对象,并重写compara方法
Comparator<Integer> com= new Comparator<>() {
public int compare(Integer num1, Integer num2) {
return num2-num1;//表示相邻两个数进行比较,>0交换位置
}
};
//使用sort方法,并传入list,和Comparator对象
Collections.sort(list, com);
ystem.out.println(list);//[100, 89, 46, 20]
如果对自定义类型进行排序。比如,按照自定义学生类的名字或者年龄进行排序
示例:
定义一个学生类
public class Student {
String name;
int age;
public Student() {
}
public Student(String name,int age) {
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "姓名:"+this.name+",年龄:"+this.age;
}
}
测试:
public static void main(String[] args) {
ArrayList<Student> list=new ArrayList<>();
list.add(new Student("zhangsan",18));
list.add(new Student("lisi",20));
list.add(new Student("wangwu1",16));
list.add(new Student("zhaoliu",30));
//使用匿名内部类创建Comparator接口的对象,并重写compara方法。
Comparator<Student> com=new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//按照name的长度升序排序
int i=s1.name.length()-s2.name.length();
if(i==0) {//如果名字的长度一致
i=s1.age-s2.age;//按照年龄的大小升序排序
}
return i;
}
};
//调用sort方法传递Comparator对象进行排序
Collections.sort(list,com);
//遍历集合
for(Student stu:list) {
System.out.println(stu);
}
}
//打印结果:
姓名:lisi,年龄:20
姓名:wangwu1,年龄:16
姓名:zhaoliu,年龄:30
姓名:zhangsan,年龄:18
6.比较器–comparable接口
在Student类上实现Comparable接口,并重写CompareTo方法
//实现Comparable接口
public class Student implements Comparable<Student>{
String name;
int age;
public Student() {
}
public Student(String name,int age) {
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "姓名:"+this.name+",年龄:"+this.age;
}
//重写ComparaTo方法
@Override
public int compareTo(Student o) {
//按照name的长度升序排序
int i=this.name.length()-o.name.length();
if(i==0) {//如果名字的长度一致
i=this.age-o.age;//按照年龄的大小升序排序
}
return i;
}
}
测试:
public static void main(String[] args) {
ArrayList<Student> list=new ArrayList<>();
list.add(new Student("zhangsan",18));
list.add(new Student("lisi",20));
list.add(new Student("wangwu1",16));
list.add(new Student("zhaoliu",30));
//使用sort方法进行排序
Collections.sort(list);
for(Student stu:list) {
System.out.println(stu);
}
}
//打印结果:
姓名:lisi,年龄:20
姓名:wangwu1,年龄:16
姓名:zhaoliu,年龄:30
姓名:zhangsan,年龄:18
二、Map
现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象。
-
在Collection接口中,元素都是单个存在的。
-
Map 中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
-
Map 中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
Map中常用的实现类:
实现类 | 底层实现 | 特点 |
---|---|---|
HashMap<K,V> | 哈希表+链表+红黑树 | 元素的存取顺序可能不一致,允许null值null键 |
LinkedHashMap<K,V> | 哈希表+链表+红黑树 | 为HashMap的子类,存取顺序一致,允许null值null键 |
TreeMap<K,V> | 红黑树 | 根据key值排序,不允许null值null键 |
注:Map中的键-值实质上是存储在Entry对象中的。
1、HashMap
(1)HashMap<K,V>的声明;(K:表示键的类型。V:表示值的类型)
//声明一个键值都是字符串类型的HashMap
HashMap<String,String> ssmap=new HashMap<>();
//使用多态方式声明
Map<Integer,String> ismap=new HashMap<>();
(2)HashMap的常用操作
1 public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。
2.public V get(Object key) :根据指定的键,在Map集合中获取对应的值。
3.boolean containsKey(Object key) / containsValue(Object value) :判断集合中是否包含指定的键或指定的值。
4.public V remove(Object key) : 删除指定的键所对应的键值对元素,返回被删除元素的值。
5.public Set keySet() : 获取Map集合中所有的键,存储到Set集合中。
6.public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。
//声明一个HashMap
HashMap<String,String> cpMap =new HashMap<>();
//1 public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。
cpMap.put("霞", "洛");
cpMap.put("EZ", "拉克丝");
cpMap.put("泰达米尔", "艾希");
System.out.println(cpMap);//{EZ=拉克丝, 霞=洛, 泰达米尔=艾希}
//2.public V get(Object key) :根据指定的键,在Map集合中获取对应的值。
cpMap.get("EZ")//拉克丝
//3. boolean containsKey(Object key) / containsValue(Object value) :判断集合中是否包含指定的键或指定的值。
cpMap.containsKey("霞");//ture
cpMap.containsValue("洛");//ture
//4.public V remove(Object key) : 删除指定的键所对应的键值对元素,返回被删除元素的值。
cpMap.remove("霞");//返回"洛"
//5.public Set<K> keySet() : 获取Map集合中所有的键,存储到Set集合中。
Set<String> set=cpMap.keySet();//[EZ, 霞, 泰达米尔]
//6.public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。
Set<Map.Entry<String, String>> sets= cpMap.entrySet();//[EZ=拉克丝, 霞=洛, 泰达米尔=艾希]
(3)HashMap插入重复的key
代码示例:
HashMap<String,Integer> map =new HashMap<>();
map.put("盖伦", 18);
map.put("赵信", 19);
map.put("嘉文四世", 20);
System.out.println(map);//{嘉文四世=20, 赵信=19, 盖伦=18}
//添加一个重复的key
map.put("盖伦", 30);
System.out.println(map);//{嘉文四世=20, 赵信=19, 盖伦=30} 此时盖伦所对应的值被后来添加的value覆盖了
注:在hashMap中插入重复的key时,value会被后来插入的value覆盖掉。
**(4) ** HashMap的遍历
方式一:通过key去查找,使用keySet()方法获取到一个键集合,遍历该key,再使用get(K key)方法获取值。
HashMap<String,Integer> map =new HashMap<>();
map.put("盖伦", 18);
map.put("赵信", 19);
map.put("嘉文四世", 20);
map.put("艾希", 21);
map.put("李青", 22);
//使用keySet()方法获取到一个键Set集合
Set<String> keys=map.keySet();
for(String key:keys) {
System.out.println(key+":"+map.get(key));
}
方式二:HashMap中-=的每个key-value是存储在Entry这个对象中。因此我们要遍历出元素,首先要先获取到每个Entry对象。使用entryset()方法就可以获取到每个Entry,再使用Entry中的getKey()和getValue()方法来获取到键和值。
//获取到Entry对象的Set集合·
Set<Map.Entry<String, Integer>> entries=map.entrySet();
//遍历得到每个Entry对象
for(Map.Entry<String, Integer> entry:entries) {
//使用getKey()方法获取键 getValue()方法获取值
System.out.println(entry.getKey()+":"+entry.getValue());
}
(4)HashMap存储自定义类型的值
定义一个People类
public class People {
String name;
int age;
public People() {
}
public People(String name,int age) {
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "名称:"+this.name+",年龄:"+this.age;
}
}
测试:
HashMap<People,String> userMap=new HashMap<>();
userMap.put(new People("zhangsan",18),"成都");
userMap.put(new People("lisi",20),"北京");
userMap.put(new People("wangwu",19),"上海");
userMap.put(new People("zhaoliu",18),"重庆");
//遍历
Set<People>sets=userMap.keySet();
for(People people:sets) {
System.out.println(people+" 地址:"+userMap.get(people));
}
补充:
1. HashTable
HashTable是线程安全,HashMap线程不安全。