集合(上)
1. 基本概念
- 集合就是一个容器 ,是一个载体,可以存放对象的引用。即集合不能直接存储基本数据类型,也不能直接存储java对象,存储的是java对象的内存地址。
- 所有集合类和接口都在java.util包下
- java集合分为两大类
- 一类是单个方式存储元素,这一类集合中的超级父接口:java.util.Collection
- 一类是以键值对儿的方式存储元素,这一类集合中的超级父接口:java.util.Map
2. 集合的UML
UML是一种统一建模语言,适用于所有面向对象的编程语言,它描述类和类之间的关系、程序执行流程、对象的状态等
下图为集合中常用的类和接口的UML图:
- Collection集合
- Map集合
-
实现类总结
- ArrayList:底层是数组
- LinkedList:底层是双向链表
- Vector:底层是数组,线程安全,效率较低,使用较少
- HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分
- TreeSet:底层是TreeMap,放到TreeMap集合中的元素等同于放大TreeMap集合key部分
- HashMap:底层是哈希表
- Hashtable:底层是哈希表,线程安全,效率较低,使用较少
- Properties:线程安全的,并且key和value只能存储字符串String
- TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序
-
各集合特点
-
List集合存储元素特点:有序可重复
有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
可重复:存进去1,可以再存储1
-
Set集合存储元素的特点:无序不可重复
无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
不可重复:存进去1,不能再存储1
-
SortedSet(SortedMap)集合存储元素特点:无序不可重复,集合中的元素可按大小排序
无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
不可重复:存进去1,不能再存储1
可排序:按照大小进行排序
-
Map集合的key,就是一个Set集合。
在Set集合中存放数据,实际上放到了Map集合的key部分
-
3. 数组与集合的区别
- 数组和集合都是Java中的容器
- 数组的长度是固定的,集合的长度是可变的
- 数组只能存储相同数据类型的数据,这里的数据类型可以是基本数据类型,也可以是引用类型
- 集合可以存储不同数据类型的对象的引用(但一般情况下,我们会使用泛型来约定只使用1种数据类型),但不能存储基本数据类型
4. Collection集合接口常用方法
-
Collection中能存放什么元素?
没有使用“泛型”之前,Collection中可以存储Object的所有子类型
使用了“泛型”之后,Colection中只能存储某个具体的类型
==注:==集合中不能直接存储基本数据类型,也不能存储java对象,只是存储java对象的内存地址
//Collection c = new Collection; 接口是抽象的,无法实例化 //多态 Collection c = new ArrayList();
(1) add
//boolean add(Object e) 向集合中添加元素
c.add(1200); //自动装箱,实际上放进去了一个对象的内存地址 Integer x = new Integer(1200);
c.add(3.14);
c.add(new Object());
(2) size
//int size() 获取集合中元素的个数,不是获取集合的容量
c.size() //3
(3) clear
// void clear() 清空集合
c.clear();
c.size(); //0
(4) contains
//boolean contains(Object o) 判断当前集合中是否包含元素o
c.add("hello");
c.add("hi");
c.add("li");
c.add(100);
c.add(new Object());
boolean flag = c.contains("hi"); //true
boolean flag1 = c.contains("???"); //false
(5) remove
//boolean remove(Object o) 删除集合中的某个元素(此方法只会删除一个)
c.remove("hello");
(6) isEmpty
//boolean isEmpty() 判断集合中元素个数是否为0
c.isEmpty(); //false
(7) toArray
//Object[] toArray() 调用方法可以把集合转换成数组
Object[] obj = c.toArray();
for(int i=0;i<c.size;i++){
Object o = obj[i];
System.out.println(o)// hi li 100 Object@XXXX
//String 和 Integer 的 toString方法重写
}
(8) 重点:contains和remove的特殊性
分析以下代码:为什么结果为true?
public class Main {
public static void main(String[] args){
Collection c = new ArrayList();
String s1 = new String("abc");
c.add(s1);
String s2 = new String("abc");
System.out.println(c.contains(s2)); //true
}
}
分析以下代码:为什么成功删除了?
public class Main {
public static void main(String[] args){
Collection c = new ArrayList();
String s1 = new String("abc");
c.add(s1);
String s2 = new String("abc");
c.remove(s2);
System.out.println(c.size()); //0
}
}
原因:实际上无论是contains方法还是remove方法,它们在底层代码中调用的都是equals方法。
contains方法调用equals方法来判断是否相等,相等即包含。remove方法底层调用equals方法来判断是否为要删除的元素。
而String类中又重写了equals方法,即equals方法判断的是对象内的内容,而不是判断地址。所以可以实现上述代码的操作。
如果没有重写equals方法,依旧比较对象的内存地址,那么上述代码结果为 false和1。
==注:不重写equals方法,调用的是Object类中的equals方法,而Object类中equals方法是用“”来比较,而“ == ”比较的是内存地址
所以可以得出结论:存放在一个集合中的类型,一定要重写equals方法。
5. 迭代器
以下方法是Collection通用的一种方式。在Map集合中不能使用,在所有的Collection以及子类中使用
(1) 使用方法
-
预备工作
创建集合对象,并添加元素
Collection c = new ArrayList(); c.add("abc"); c.add("def"); c.add(100); c.add(new Object());
-
获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
-
通过以上获取的迭代器对象开始迭代集合
迭代器对象Iterator中的两个方法:
- boolean hasNext() 如果有元素可以迭代,返回true
- Object next() 返回迭代的下一个元素
注:不管存进去的是什么,取出来的都是Object,但底层不变
while(it.hasNext()){ Object obj = it.next(); if(obj instanceof Integer) //底层不变 System.out.println("Integer类型"); System.out.println(obj); //输出的时候转换成字符串,因为println会调用toString()方法 }
(2) 迭代器的特殊性
集合结构只要发生改变,迭代器必须重新获取
-
分析以下代码为什么报错?
public class Demo{ public static void main(String[] args){ Collection c = new ArrayList(); //此时获取的迭代器,指向的是集合中没有元素状态下的迭代器 Iterator it = c.iterator(); c.add("abc"); c.add("def"); c.add(100); while(it.hasNext()){ Object obj = it.next(); System.out.println(obj); } } }
-
分析以下代码为什么报错?
public class Demo{ public static void main(String[] args){ Collection c = new ArrayList(); c.add("abc"); c.add("def"); c.add(100); Iterator it = c.iterator(); while(it.hasNext()){ Object obj = it.next(); //删除元素后,集合的结构发生了变化,没有通知迭代器,导致迭代器的快照和原集合状态不同,需要重新获取迭代器,否则会报错 c.remove(obj); System.out.println(obj); } } }
上述代码解决办法:
public class Demo{ public static void main(String[] args){ Collection c = new ArrayList(); c.add("abc"); c.add("def"); c.add(100); Iterator it = c.iterator(); while(it.hasNext()){ Object obj = it.next(); //调用迭代器的remove()方法 it.remove(); //删除的是当前迭代器指向的元素 System.out.println(obj); } System.out.println(c.size()); //0 } }
原因:Iterator it = c.iterator();获取的迭代器对象,迭代器用来遍历集合,此时相当于对当前集合的状态拍了一个快照,迭代器迭代时会参照这个快照进行迭代。而调用集合对象的remove()方法时,集合中的元素改变了,但迭代器的快照并没有改变,还会继续遍历,出现错误。采用迭代器的remove()方法时,迭代器会先将快照内的集合元素删除,再修改集合本身中的元素。
-
总结
- 集合结构只要发生变化,就需要重新获取迭代器
- 在迭代集合元素的过程中,不能调用集合对象的remove()方法,一定要使用迭代器Iterator的remove()方法
6. List集合特有方法
(1) add
在列表的指定位置插入指定元素(第一个参数是下标)
//void add(int index, Object element)
List myList = new ArrayList();
myList.add("a");
mylist.add("b");
mylist.add(1,"KING") //在下标为1的位置插入“KING”
(2) get
根据下标获取元素
//Object get(int index)
Object firstObj = myList.get(0);
(3) indexOf
获取指定对象第一次出现处的索引
//int indexOf(Object o)
int index=myList.indexof("a");
(4) lastIndexOf
获取指定对象最后一次出现处的索引
//int lastIndexOf(Object o)
int indexLast = myList.lastIndexOf("b");
(5) remove
删除指定下标位置的元素
//Object remove(int index)
myList.remove(0);
(6) set
修改指定位置的元素
//Object set(int index,Object element)
myList.set(1,"niihao");
7. ArrayList集合
-
ArrayList集合底层是Object[]数组,非线程安全的
-
ArrayList集合有三种构造方法
-
默认初始化容量为10
List list = new ArrayList();
-
指定初始化容量
List list = new ArrayList(20);
-
将其他集合转换成List集合
Collection c = new HashSet(); List list = new ArrayList(c);
-
-
ArrayList集合底层是数组,怎么优化?
尽可能少的去扩容,因为数组扩容效率比较低。ArrayList集合的检索效率比较高,向数组尾部添加元素的效率比较高,但增删元素效率比较低。
-
ArrayList集合在开发中使用较多,因为向尾部添加元素效率不受影响,其次做检索/查找某个元素的操作比较多
-
如何将ArrayList集合变为线程安全的
使用集合工具类:java.util.Collections
List list = new ArrayList(); //非线程安全 Collections.synchroizedList(list); //安全
注:java.util.Collection 是集合接口
java.util.Collections 是集合工具类
8. LinkedList集合
-
LinkedList集合底层是双向链表,底层也有下标
注:ArrayList集合检索效率高,不单纯是因为有下标,因为LinkedList也有,而是因为底层数组发挥的作用
-
LinkedList集合检索/查找某个元素效率较低,但是随机删除和插入效率较高,但在末尾加元素时,还是使用ArrayList
-
LinkedList集合没有初始化容量,最初这个链表没有任何元素,first和last引用都是null
-
链表在空间存储上,内存地址不连续
List list = new LinkedList();
9. Vector集合
- Vector集合底层是一个数组
- 初始化容量为10,每次扩容为原容量的2倍
- Vector集合中所有方法都是线程同步的,线程安全的,都带有synchronized关键字。但是效率较低,很少使用
List list = new Vector();
10.泛型机制
(1) 基本概念
-
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,**也就是说所操作的数据类型被指定为一个参数(**type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
-
泛型只是在程序编译阶段起作用,只是给编译器参考的
-
List<String> mylist = new ArrayList<String>(); List<String> mylist = new ArrayList<>(); //自动类型推断 两种均可 Iterator<String> it = mylist.iterator();//迭代器使用泛型
其中尖括号内的类可以替换。使用泛型List之后,表示集合mylist只能存储String类型的数据
(2) 为什么使用泛型
对比下面两段代码
-
不使用泛型
public class Demo{ public static void main(String[] args) { List mylist = new ArrayList(); Animal a1 = new Animal(); Animal a2 = new Animal(); mylist.add(a1); mylist.add(a2); Iterator it = mylist.iterator(); while(it.hasNext()){ //Animal a = it.next(); 不可以!! Object obj = it.next(); //取出来的都是Object对象 if(obj instanceof Animal){ //需要进行判断 再进行强制转换 因为Object类中没有doSome方法 Animal a = (Animal)obj; a.doSome(); } } } } class Animal{ public void doSome(){ System.out.println("Animal"); } }
-
使用泛型
public class Demo{ public static void main(String[] args) { List<Animal> mylist = new ArrayList(); Animal a1 = new Animal(); Animal a2 = new Animal(); mylist.add(a1); mylist.add(a2); Iterator<Animal> it = mylist.iterator(); while(it.hasNext()){ Animal a = it.next(); a.doSome(); } } } class Animal{ public void doSome(){ System.out.println("Animal"); } }
-
使用泛型优点
- 集合中存储的元素统一
- 从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
(3) 自定义泛型
自定义泛型的时候,< >尖括号中的是一个标识符,随便写
- 示例1:
public class GenericTest<标识符随便写> {
public void doSome(标识符随便写 o){
System.out.println(o);
}
public static void main(String[] args){
GenericTest<String> gt = new GenericTest<>();
gt.doSome("nihao"); //nihao 传入的必须是泛型内的类
GenericTest gt1 = new GenericTest();
gt1.doSome(new Object()); //不使用泛型 默认为Object类
}
}
- 示例2:
public class Demo{
public static void main(String[] args) {
MyIterator<String> mi = new MyIterator<>();
String s = mi.get();
}
}
class MyIterator<T> {
public T get(){
return null;
}
}