Java集合详解(15,16--List集合,17--Set集合,18--Map集合)
一.集合的由来
我们学习的Java是面向对象语言,而面向对象语言对事物的描述是通过对象体现的。
为了方便对多个对象进行操作,我们就必须把这多个对象进行存储。
而要存储多个对象,就不能是一个基本的变量,而应该是一个容器类型的变量。
首先我们想到的是数组,但数组的长度是固定的且只能放统一类型的数据。
另外,能想到的就是StringBuffer(StringBuilder),但其结果是一个字符串,不一定满足要求。
所以,为了适应长度变化和多类型元素的双重要求,产生了集合。
二.集合与数组的区别
主要有3点区别,具体如下图:
三.集合的分类
Java集合主要由两大体系构成,分别是Collection体系和Map体系,Collection和Map分别是两大体系的顶层接口。
注意:Collection和Map都是接口,包括继承自他们的List和Set也都是接口,接口是不能实例化的。
下面首先介绍Collection接口的子接口:
1.List和Set的区别
Collection接口分为List接口和Set接口两大类,具体区别如下图:
2.List的特点和分类
List集合像一个数组,是有序的,可以对每个元素的插入位置进行精确地控制,并可根据索引访问和搜索元素。
与Set最大的不同,就是List中的元素是可重复的。
ArrayList举例:
1 public static void main(String[] args) { 2 ArrayList array = new ArrayList(); 3 4 Student s1=new Student("benon",28); 5 Student s2=new Student("yy",22); 6 Student s3=new Student("shaobao",6); 7 8 array.add(s1); 9 array.add(s2); 10 array.add(s3); 11 12 //方式一:迭代器 13 Iterator it=array.iterator(); 14 while(it.hasNext()) { 15 Student s=(Student)it.next(); 16 System.out.println(s.getName()+"---"+s.getAge()); 17 } 18 19 //方式二:for循环 20 for(int x=0;x<array.size();x++) { 21 Student s=(Student)array.get(x); 22 System.out.println(s.getName()+"---"+s.getAge()); 23 } 24 }
Vector举例:
1 public static void main(String[] args) { 2 Vector v = new Vector(); 3 //添加元素 4 v.addElement("hello"); 5 v.addElement("world"); 6 v.addElement("java"); 7 //遍历一 8 for(int x=0;x<v.size();x++) { 9 String s=(String)v.elementAt(x); 10 System.out.println(s); 11 } 12 System.out.println("----------------"); 13 //遍历二 14 Enumeration en=v.elements(); 15 while(en.hasMoreElements()) { 16 String s=(String)en.nextElement(); 17 System.out.println(s); 18 } 19 }
LinkedList举例:
1 public static void main(String[] args) { 2 LinkedList link=new LinkedList(); 3 //添加元素 4 link.add("hello"); 5 link.add("world"); 6 link.add("java"); 7 //分别在表头和表尾追加元素 8 link.addFirst("javaee"); 9 link.addLast("android"); 10 //获取表头元素和表尾元素 11 System.out.println(link.getFirst()); 12 System.out.println(link.getLast()); 13 //删除表头元素和表尾元素并返回表头元素和表尾元素 14 System.out.println(link.removeFirst()); 15 System.out.println(link.removeLast()); 16 }
3.Collections接口的常用功能和方法
1.添加功能
boolean add(Object obj):添加一个元素
boolean add(Collection c):添加一个集合的所有元素
2.删除功能
void clear():移除所有元素
boolean remove(Object obj):移除一个元素
boolean remove(Collection c):移除一个集合的所有元素
3.判断功能
boolean contains(Object obj):判断集合中是否包含指定的元素
boolean containsAll(Collection):判断集合中是否包含指定的集合元素
boolean isEmpty():判断集合是否为空
4.获取功能
Iterator<E> iterator()
5.长度功能
int size():元素的个数
6.交集功能
boolean retainAll(Collection c):两个集合都有的元素
7.集合转换为数组
Object[] toArray():集合直接调用toArray()方法,将其转换为数组,再使用遍历即可得到每一个元素。
3.Set的特点和分类
Set集合是无序的,元元素不允许重复。
Set接口下主要包含两个类:HashSet类和TreeSet类。
(1)HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素。
元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。
(注:哈希表是一个元素为链表的数组,综合了数组和链表的好处。)
步骤:首先比较哈希值
如果哈希值相同,则调用equals()方法,返回true则元素重复不添加,返回false则不重复,元素添加。
如果哈希值不同,则直接添加到集合中。
举例:自定义对象Student类中的hashCode()和equals()方法必须重写,否则无法保证元素的唯一性。
当然,除了重写,我们还可以使用自动生成,这种方法更加快速,是我们推荐的。
1 //HashSet集合存储自定义对象,如何保证其唯一性 2 public class HashSetDemo2 { 3 public static void main(String[] args) { 4 HashSet<Student> hs=new HashSet<Student>(); 5 6 Student s1=new Student("benon",28); 7 Student s2=new Student("yy",22); 8 Student s3=new Student("shaobao",6); 9 Student s4=new Student("tianbao",7); 10 Student s5=new Student("benon",28); 11 Student s6=new Student("benon",27); 12 13 hs.add(s1); 14 hs.add(s2); 15 hs.add(s3); 16 hs.add(s4); 17 hs.add(s5); 18 hs.add(s6); 19 20 for(Student s:hs) { 21 System.out.println(s.getName()+"---"+s.getAge()); 22 } 23 } 24 }
自定义对象类:(并重写equals方法和hashCode方法)
1 public class Student { 2 @Override 3 public boolean equals(Object obj) { 4 if(this==obj) { 5 return true; 6 } 7 if(!(obj instanceof Student)) { 8 return false; 9 } 10 Student s=(Student)obj; 11 return this.name.equals(s.name)&&this.age==age; 12 } 13 14 @Override 15 public int hashCode() { 16 return 0; 17 } 18 19 private String name; 20 private int age; 21 22 public Student() { 23 super(); 24 } 25 26 public String getName() { 27 return name; 28 } 29 public void setName(String name) { 30 this.name = name; 31 } 32 public int getAge() { 33 return age; 34 } 35 public void setAge(int age) { 36 this.age = age; 37 } 38 39 public Student(String name, int age) { 40 super(); 41 this.name = name; 42 this.age = age; 43 } 44 45 }
(2)LinkedHashSet底层数据结构由哈希表和链表组成:
哈希表保证元素的唯一性
链表保证元素有序(存储和取出是一致的)
(3)TreeSet底层数据结构采用二叉树来实现,元素唯一且有序。
根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造)。
底层是二叉树结构(红黑树是一种自平衡的二叉树)
元素的存储步骤:
第一个元素存储的时候直接作为根节点
第二个元素从根节点开始比较,比其大则为其右节点,比其小则为其左节点,相等则不管它
元素的遍历步骤:
按照左中右的原则依次取出即可。
因为TreeSet是有序的,所以自定义集合时不能像HashSet那样直接使用增强for进行遍历排序,因此要用到以下两种情况:
自然排序:(使用无参构造方法时)
如果一个自定义类的元素要想能够进行自然排序,就必须实现自然排序接口:implements Comparable<T>
其次要在自定义类中重写compareTo(T t)方法,而且要知道排序条件(主要排序和次要排序),这样才能确定返回值。
举例:需求要按年龄排序
1 public static void main(String[] args) { 2 TreeSet<Student> ts=new TreeSet<Student>(); 3 4 Student s1=new Student("benon",28); 5 Student s2=new Student("yy",22); 6 Student s3=new Student("shaobao",6); 7 Student s4=new Student("tianbao",7); 8 Student s5=new Student("jiabao",5); 9 Student s6=new Student("yanyan",22); 10 11 ts.add(s1); 12 ts.add(s2); 13 ts.add(s3); 14 ts.add(s4); 15 ts.add(s5); 16 ts.add(s6); 17 18 for(Student s:ts) { 19 System.out.println(s.getName()+"---"+s.getAge()); 20 } 21 }
自定义类:实现接口,再重写compareTo()方法
1 public class Student implements Comparable<Student>{ 2 @Override 3 public int compareTo(Student s) { 4 //这里返回什么应该根据排序规则来做 5 int num=this.age-s.age; 6 int num2=num==0?this.name.compareTo(s.name):num; 7 return num2; 8 } 9 10 private String name; 11 private int age; 12 13 public Student() { 14 super(); 15 } 16 17 public String getName() { 18 return name; 19 } 20 public void setName(String name) { 21 this.name = name; 22 } 23 public int getAge() { 24 return age; 25 } 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 public Student(String name, int age) { 31 super(); 32 this.name = name; 33 this.age = age; 34 } 35 36 }
比较器排序:(使用有参构造方法时)
我们首先想到的是新建一个外部类,但有点过于繁琐。
前面有学过,如果一个方法的参数是接口时,那么真正要的是接口实现类的对象,而匿名内部类就可以实现这个东西。
因为与自然排序相比,免去了自定义类中的实现接口和重写方法,只需在主类中定义一个匿名内部类即可。
还是上面的代码示例,换成比较器排序:
注意:
1.自定义类中进行比较可以直接使用this,而不在自定义类中进行比较,只能有两个参数s1和s2;
2.自定义类Student中的字段name和age是私有的,故无法直接调用,只能使用getName()和getAge()方法。
1 public class TreeSetDemo { 2 public static void main(String[] args) { 3 TreeSet<Student> ts=new TreeSet<Student>(new Comparator<Student>() { 4 5 @Override 6 public int compare(Student s1, Student s2) { 7 int num=s1.getName().length()-s2.getName().length(); 8 int num2=num==0?s1.getAge()-s2.getAge():num; 9 return num2; 10 } 11 12 }); 13 14 Student s1=new Student("benon",28); 15 Student s2=new Student("yy",22); 16 Student s3=new Student("shaobao",6); 17 Student s4=new Student("tianbao",7); 18 Student s5=new Student("jiabao",5); 19 Student s6=new Student("yy",23); 20 21 ts.add(s1); 22 ts.add(s2); 23 ts.add(s3); 24 ts.add(s4); 25 ts.add(s5); 26 ts.add(s6); 27 28 for(Student s:ts) { 29 System.out.println(s.getName()+"---"+s.getAge()); 30 } 31 } 32 }
4.Collection中的类如何选择?
5.Map体系的特点和使用
Map是java集合的另一个根接口,map也像一个罐子,不过在map中存储的时候都是以键值对的方式存储的(key-value方式)。
存储的时候,key键是不能重复的,相当于索引,而value值是可以重复的。查询value值时通过key进行查询。
下图中是Map体系中一些常用的实现类:
Map集合的功能:
1.添加(替换)
V put (K key,V value):
如果key不存在,则存储该键值对,返回null;
如果key已存在,则替换该键对应的值,返回被替换的值。
2.删除
void clear():移除所有键值对
V remove(Object key):根据键删除键值对,返回值
3.判断
boolean containKey(Object key):是否包含指定键
boolean containsValue(Objet value):是否包含指定值
boolean isEmpty():集合是否为空
4.获取
Set<Map.Entry<K,V>> entrySet():获取键值对对象集合
V get(Object key):根据键获取值
Set<K> keySet():获取所有键的集合(键是唯一的,所以是Set类型)
Collection<V> values():获取所有值的集合(值是可重复的,所以是Collection类型)
5.长度
int size():返回集合中的键值对的对数
- hashMap示例:
1 public class HashMapDemo3 { 2 public static void main(String[] args) { 3 //新建集合 4 HashMap<String, Student> hm=new HashMap<String, Student>(); 5 //创建自定义对象 6 Student s1=new Student("benon",28); 7 Student s2=new Student("yy",22); 8 Student s3=new Student("shaobao",6); 9 //添加元素 10 hm.put("706532", s1); 11 hm.put("504284", s2); 12 hm.put("704", s3); 13 //遍历 14 Set<String> set=hm.keySet(); 15 for(String key:set) { 16 Student value=hm.get(key);//根据键获取值 17 System.out.println(key+"---"+value.getName()+"---"+value.getAge()); 18 } 19 } 20 }
- LinkedHashMap:是Map接口的哈希表和链接列表的表现形式,具有可预知的迭代顺序
由哈希表保证键的唯一性;
由链表保证键的有序性;
- TreeMap:是基于红黑树的Map接口的实现,会保证键的排序和唯一性。
(注意:是重新排序,不是按之前的默认顺序)
同前面的TreeSet一样,根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造)。
- Hashtable:此类实现一个哈希表,该哈希表将键映射到相应的值,且是同步的。(任何非null对象都可以作为键和值)
与HashMap的区别:HashMap是非同步的,且允许使用null值和null键,其它两者无区别。
四.Collections类
这是一个针对集合操作的工具类,都是静态方法。
Collection和Collections的区别:
Collection:是单列集合的顶层接口,有子接口List和Set。
Collections:是针对集合操作的工具类,有针对集合进行排序和二分查找的方法。