前言
Collection接口的层次结构图:
一、集合概述
所有的集合类和集合接口都在java.util包下。
集合实际上就是一个容器。可以来容纳其它类型的数据,可以一次容纳多个对象。(数组其实就是一个集合。)
集合不能直接存储基本数据类型,也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。(例如:数组、二叉树、链表、哈希表…以上这些都是常见的数据结构。)
在java中集合分为两大类:
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
一类是以键值对儿的方式存储元素
以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;
二、Collection详述
Collection 是 List 和 Set 的父接口
Collection接口中的常用方法:
boolean add(E e) 向集合中添加元素
int size();获取集合中的元素个数
void clear();清空集合
boolean contains(Object o) 判断当前集合是否含有o元素
boolean remove(Object o) 删除集合中的元素
boolean isEmpty() 判断集合是否为空
Object[] toArray() 把集合转换为数组
示例代码(1):
import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; public class CollectionText { public static void main(String[] args) { Collection collection = new ArrayList(); //向集合中添加元素 boolean b1 = collection.add(100); boolean b2 = collection.add("你好,java"); boolean b3 = collection.add("中国"); System.out.println(b1); System.out.println(b2); System.out.println(b3); //获取集合中的元素个数 int i1 = collection.size(); System.out.println(i1); //collection.clear(); //System.out.println(collection.size()); boolean b = collection.contains(100); System.out.println(b); //删除集合中的元素 collection.remove("中国"); System.out.println(collection.size()); //判断集合是否为空 System.out.println(collection.isEmpty()); collection.clear(); System.out.println(collection.isEmpty()); collection.add("我"); collection.add("是"); collection.add("中"); collection.add("国"); collection.add("人"); //转换为数组 Object[] objects=collection.toArray(); for(int i=0;i<objects.length;i++){ Object o=objects[i]; System.out.println(o); } System.out.println(Arrays.toString(objects)); } }
运行结果:
true true true 3 true 2 false true 我 是 中 国 人 [我, 是, 中, 国, 人] Process finished with exit code 0
注:Collection在使用泛型之前,可存放Object的所有子类,在使用泛型之后,只能存某个具体的类型。
集合的迭代(遍历)
迭代器是一个对象
所有Collection以及子类通用,Map集合不能使用。
使用迭代器的步骤:
第一步:获取集合对象的迭代器对象
第二部:通过获取的迭代器对象进行迭代(遍历)
迭代器(iterator)中的方法:
boolean hasNext(); true:有元素迭代 false:无元素迭代
E next() ;返回迭代的下一个元素
示例代码(2):
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class CollectionText01 { public static void main(String[] args) { //创建集合 Collection c1=new ArrayList(); c1.add(100); c1.add("abc"); c1.add("def"); c1.add(new Object()); //第一步:获取集合对象的迭代器对象iterator Iterator it=c1.iterator(); //第二步通过获取的迭代器对象进行集合遍历/迭代。 while (it.hasNext()){ Object obj=it.next(); System.out.println(obj); } } }
运行结果:
100 abc def java.lang.Object@723279cf
迭代器重要规律:
1.集合结果发生改变,迭代器需要重新获取
2.迭代过程中不能调用remove()方法删除元素,会出现异常(ConcurrentModificationException)
3.迭代元素的过程中使用迭代器的remove()方法删除元素
示例代码(3):
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class CollectionText05 { public static void main(String[] args) { Collection c=new ArrayList(); //ConcurrentModificationException异常!!! //Iterator it=c.iterator(); c.add(1); c.add(2); c.add(3); Iterator it=c.iterator(); while (it.hasNext()){ Object o=it.next(); // 没有更新迭代器 //c.remove();直接通过集合,没有通知迭代器(导致迭代器快照与原集合状态不同) //更新迭代器 。 it.remove(); //删除的是迭代器指向的当前对象。 System.out.println(o); } System.out.println(c.size()); } }
运行结果:
1 2 3 0
三、List详述
List集合存储元素特点:有序,可重复!
他们都是有顺序的,也就是放进去是什么顺序,取出来还是什么顺序,也就是基于线性存储,可以看作是一个可变数组
List 接口下面主要有三个实现 ArrayList 、LinkedList和Vector
List接口的常用方法:
void add(int index, E element) 在列表中指定的位置上插入指定的元素
E get(int index) 根据下标获取元素
int indexOf(Object o) 返回此列表中指定元素的第一个出现的索引
int lastIndexOf(Object o) 返回此列表中指定元素的最后一个发生的索引
E remove(int index) 移除此列表中指定下标的元素
E set(int index, E element) 用指定元素替换此列表中指定位置的元素
示例代码(4):
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ListText01 { public static void main(String[] args) { //创建list类型集合 List list=new ArrayList(); //添加元素,将指定的元素到这个列表的末尾 list.add("a"); list.add("b"); list.add("c"); //在列表中指定的位置上插入指定的元素(效率低!!!) list.add(1,"ooo"); //迭代器 Iterator it=list.iterator(); while(it.hasNext()){ Object o=it.next(); System.out.println(o); } System.out.println("================================="); //根据下标获取元素 Object o=list.get(0); System.out.println(o); //list集合特有的遍历方法!! for(int i=0;i<list.size();i++){ System.out.println( list.get(i)); } // 返回此列表中指定元素的第一个出现的索引 System.out.println(list.indexOf("b")); //返回此列表中指定元素的最后一个发生的索引 System.out.println(list.lastIndexOf("c")); System.out.println("============================="); //移除此列表中指定位置的元素 list.remove(1); System.out.println(list.size());//3 //用指定元素替换此列表中指定位置的元素 list.set(0,"n"); System.out.println(list.get(0)); } }
运行结果:
a ooo b c ============================== a a ooo b c 2 3 ============================= 3 n
ArrayList类
ArrayList底层是Object类型的数组。
ArrayList集合初始化容量10。添加第一个元素时,创建长度为10的空数组。
扩容机制:扩容为原容量1.5倍。
ArrayList集合优化:尽可能少的扩容,数组扩容效率低。
ArrayList集合优缺点:
优点:查询数据比较快,检索效率高
缺点:添加和删除数据比较慢,无法存大数据量(向数组末尾添加元素效率高)
ArrayList是非线程安全
示例代码(5):
import java.util.*; public class ArrayListText01 { public static void main(String[] args) { //初始化容量为10 List list1=new ArrayList(); //初始化容量为20 List list2=new ArrayList(20); Collection c=new ArrayList(); c.add("cccc"); c.add("bvncm"); c.add("mnkj"); Iterator it=c.iterator(); while (it.hasNext()){ Object o= it.next(); System.out.println(o); } } }
运行结果:
cccc bvncm mnkj
LinkedList类
LinkedList底层是基于链表数据结构。
LinkedList集合优缺点:
优点:添加和删除数据比较快,随机增删效率高
缺点:查询数据比较慢,检索效率低。
Vector类
Vector初始化容量是10.
扩容为原容量的2倍。
Vector底层是数组。
Vector底层是线程安全的,但是效率低
泛型机制(jdk1.5之后新特性)
只在编译时起作用,给编译器参考
泛型优缺点:
优点:
1.集合中存储元素类型统一
2.集合中去除掉元素是泛型指定的类型,无需进行向下转型。
缺点:导致集合中元素缺乏多样性
JDK8新特性:钻石表达式
List<String> list = new ArrayList<>(); 类型自动推断!
示例代码(6):
使用泛型之前
自定义类:
class Animal{ public void run(){ System.out.println("动物在移动!!"); } } class Cat extends Animal{ public void catchMouse(){ System.out.println("猫抓老鼠"); } } class Dog extends Animal{ public void fly(){ System.out.println("狗在飞"); } }
测试类:
import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * jdk5.0之后的特性:泛型 */ public class GenericText { //使用泛型之前 public static void main(String[] args) { List list = new ArrayList(); Cat cat = new Cat(); Dog dog = new Dog(); list.add(cat); list.add(dog); Iterator it = list.iterator(); while (it.hasNext()) { Object o = it.next(); if (o instanceof Animal) { ((Animal) o).run(); } } } }
运行结果:
动物在移动!! 动物在移动!!
使用泛型之后
测试类:
public class GenericText { public static void main(String[] args) { //使用泛型List<Animal>后,表示List集合只能存储Animal类型的元素 //泛型指定集合中存储的数据类型 List<Animal> list = new ArrayList<Animal>(); Cat cat = new Cat(); Dog dog = new Dog(); list.add(cat); list.add(dog); //表示迭代器迭代Animal类型 Iterator<Animal> it = list.iterator(); while (it.hasNext()) { //使用泛型后,迭代器每一次返回的数据是Animal类型 Animal a = it.next(); //无需强转,直接调用 a.run(); if (a instanceof Cat) { ((Cat) a).catchMouse(); } else if (a instanceof Dog) { ((Dog) a).fly(); } } } }
运行结果:
动物在移动!! 猫抓老鼠 动物在移动!! 狗在飞
一起找不同吧!!!!!
四、Set详述
Set集合存储元素特点:无序不可重复。
无序表示存进去是这个顺序,取出来就不一定是这个顺序了,另外Set集合中元素没有下标。Set集合中的元素还不能重复。
HashSet类
HashSet 中的数据是无序的不可重复的。HashSet 按照哈希算法存取数据的,具有非常好性能,它的工作原理是这样的,当向 HashSet 中插入数据的时候,他会调用对象的 hashCode 得到该对象的哈希码,然后根据哈希码计算出该对象插入到集合中的位置。
HashSet不是同步的;
集合元素值可以是null;
如果HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals方法比较true,但它们的hashCode方法返回的值不相等,HashSet将会把它们存储在不同位置,依然可以添加成功。
HashSet集合判断两个元素的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode方法返回值也相等。
特别是向 HashSet 或 HashMap 中加入数据时必须同时覆盖 equals 和 hashCode 方法,应该养成一种习惯覆盖 equals 的同时最好同时覆盖 hashCode
Java语法要求:
两个对象 equals 相等,那么它的 hashcode 相等
两个对象 equals 不相等,那么它的 hashcode 并不要求它不相等,但一般建议不相等
hashcode 相等不代表两个对象相等(采用 equals 比较)
哈希表
一个元素为链表的数组,综合了数组与链表的优点。
示例代码(7):
没有重写hashCode和equals之前
自定义类:
public class Student { private String name; public Student() { } public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
测试类:
import java.util.HashSet; import java.util.Set; public class HashMapText02 { public static void main(String[] args) { Student s1 = new Student("zhangsan"); Student s2 = new Student("zhangsan"); System.out.println(s1.equals(s2)); //重写hashCode之前 System.out.println("s1hashCode值:" + s1.hashCode()); System.out.println("s2hashCode值:" + s2.hashCode()); Set<Student> set=new HashSet<>(); set.add(s1); set.add(s2); System.out.println(set.size());//2 } }
运行结果:
false s1hashCode值:284720968 s2hashCode值:122883338 2
重写hashCode和equals之后
自定义类:
public class Student { private String name; public Student() { } public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } //equals方法 public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return Objects.equals(name, student.name); } //hashCode方法 public int hashCode() { return Objects.hash(name); } }
测试类:
import java.util.HashSet; import java.util.Set; public class HashMapText02 { public static void main(String[] args) { Student s1 = new Student("zhangsan"); Student s2 = new Student("zhangsan"); System.out.println(s1.equals(s2)); //重写hashCode之后 System.out.println("s1hashCode值:" + s1.hashCode()); System.out.println("s2hashCode值:" + s2.hashCode()); Set<Student> set=new HashSet<>(); set.add(s1); set.add(s2); System.out.println(set.size());//1 } }
运行结果:
true s1hashCode值:-1432604525 s2hashCode值:-1432604525 1
找呀找呀,找不同。
TreeSet类
TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet 可以对 Set 集合进行排序,默认自然排序(即升序)
TreeSet 是可排序的
TreeSet集合对自定义数据可以排序方法:
第一种:Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类必须实现该方法,实现接口的类就可以比较大小了。当调用一个一个对象调用该方法与另一个对象进行比较obj1.compareTo(obj2)如果返回0表示两个对象相等;如果返回正整数则表明obj1大于obj2,如果是负整数则相反。
第二种:在构造TreeSet或TreeMap集合时给他一个比较器对象。比较规则自己写!!!
Comparable 与Comparator区别?
当比较规则不会发生改变时或比较规则只用一个时建议实现Comparable接口
当比较规则有多个,并且需要多个比较规则之间进行切换,建议使用与Comparator
编写比较器可以改变规则!!!
Comparator区别符合OCT原则
红黑树(自平衡二叉树)
TreeSet内部实现的是红黑树,默认整形排序为从小到大。
三种遍历方式:前序遍历,中序遍历,后序遍历。(前中后指的是根的位置)
TreeSet采用中序遍历方式。
示例代码(8):
import java.util.TreeSet; public class TreeMapText01 { public static void main(String[] args) { // TreeSet<String> ts=new TreeSet(); ts.add("zhangsan"); ts.add("wangwu"); ts.add("make"); ts.add("langlang"); for (String s: ts){ System.out.println(s); } System.out.println("============================="); TreeSet<Integer> ts2=new TreeSet(); ts2.add(200); ts2.add(300); ts2.add(600); ts2.add(14); for (Integer i:ts2){ System.out.println(i); } } }
运行结果:
langlang make wangwu zhangsan ============================= 14 200 300 600 Process finished with exit code 0
从以上结果可以看出,String类和Integer类都实现了这个接口。
示例代码(9):
实现Comparable接口
Customer类:
class Customer implements Comparable<Customer>{ int age; public Customer(int age) { this.age = age; } //需要在此方法写比较的逻辑,或者说出比较规则,按照什么进行比较!! //比较规则自己定 public int compareTo(Customer o) {//c1.comperTo(c2); //this是c1,o是c2 //c1与c2比较,就是this与c比较 // return this.age-o.age; return o.age-this.age; } @Override public String toString() { return "Customer{" + "age=" + age + '}'; } }
测试类:
import java.util.TreeSet; public class TreeSetText03 { public static void main(String[] args) { Customer p = new Customer(23); Customer p2 = new Customer(100); Customer p3 = new Customer(30); Customer p4 = new Customer(65); Customer p5 = new Customer(46); TreeSet<Customer> treeSet = new TreeSet<>(); treeSet.add(p); treeSet.add(p2); treeSet.add(p3); treeSet.add(p4); treeSet.add(p5); for (Customer c : treeSet) { System.out.println(c); } } }
运行结果:
Customer{age=100} Customer{age=65} Customer{age=46} Customer{age=30} Customer{age=23}
示例代码(10):
比较器进行排序
WuGui类:
class WuGui { int age; public WuGui(int age) { this.age = age; } @Override public String toString() { return "WuGui{" + "age=" + age + '}'; } }
比较器:
//单独在这里编写一个比较器 //比较实现java.util.Comparator接口。(Comparable是java.lang包下的,Comparator接口是java.util包下) class WuguiComparator implements Comparator<WuGui> { public int compare(WuGui o1, WuGui o2) { return o1.age - o2.age; } }
测试类:
import java.util.Comparator; import java.util.TreeSet; public class TreeSetText05 { public static void main(String[] args) { //创建TreeSet时,需要使用比较器 //TreeSet<WuGui> wuGuis=new TreeSet<>();这样不行,没有通过构造方法构造一个比较器进去 //给构造方法添加一个比较器 TreeSet<WuGui> wuGuis = new TreeSet<>(new WuguiComparator()); wuGuis.add(new WuGui(200)); wuGuis.add(new WuGui(30)); wuGuis.add(new WuGui(50)); wuGuis.add(new WuGui(10)); wuGuis.add(new WuGui(100)); for (WuGui w : wuGuis) { System.out.println(w); } } }
运行结果:
WuGui{age=10} WuGui{age=30} WuGui{age=50} WuGui{age=100} WuGui{age=200}
改进上面测试类(使用匿名内部类)代码如下:
public class TreeSetText05 { public static void main(String[] args) { //第三种!! //使用匿名内部类的方式 这个类没有名字,直接new接口!! TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() { @Override public int compare(WuGui o1, WuGui o2) { return o1.age - o2.age; } }); wuGuis.add(new WuGui(200)); wuGuis.add(new WuGui(30)); wuGuis.add(new WuGui(50)); wuGuis.add(new WuGui(10)); wuGuis.add(new WuGui(100)); for (WuGui w : wuGuis) { System.out.println(w); } } }