- 容器:Java API 所提供的一系列类的实例,用于在程序中存放对象,也称集合。JDK所提供的容器API位于java.util包内。
- Java 集合框架主要结构图:
- 如上图所示,Java容器有两种基本类型Collection 和Map。
- 其中Map的结构比较简单,而Collection的结构就相对复杂一些。Collection有三个继承接口:List、Queue和Set。
Collection
Collection接口
Collection接口是最基本的集合接口,一个Collection代表一组Object,即Collection的元素。一些Collection允许相同的元素而另一些不行,一些能排序而另一些不行。所以Collection所代表的是一种规则,它所包含的元素都必须遵循一条或者多条规则。而JDK并不提供对Collection的直接实现,而是对其进行了细分,提供更加细化的子接口List、Set、Queue。
Collection接口继承了Iterable接口,Collection的几个子接口,如List、Set、Queue也都间接继承了Iterable接口。
Collection接口中定义的方法:
A:添加功能
boolean add(Object obj) 向集合中添加一个元素
boolean addAll(Collection c) 向集合中添加一个集合的元素
B:删除功能
void clear() 删除集合中的所有元素
boolean remove(Object obj) 从集合中删除指定的元素
boolean removeAll(Collection c) 从集合中删除一个指定的集合元素
C:判断功能
boolean isEmpty() 判断集合是否为空
boolean contains(Object obj) 判断集合中是否存在指定的元素
boolean containsAll(Collection c) 判断集合中是否存在指定的一个集合中的元素
boolean retainAll(Collection c) 判断两个集合中是否有相同的元素。
D:遍历功能
Iterator iterator() 就是用来获取集合中每一个元素。
E:长度功能
int size() 获取集合中的元素个数
F:把集合转换成数组
Object[] toArray() 把集合变成数组
Collection方法举例:
import java.util.*;
public class Test {
public static void main(String[] args) {
Collection c = new ArrayList(); //可以放入不同类型的对象
c.add("hello");
c.add(new Name("f1","l1"));
c.add(new Integer(100));
c.remove("hello");
c.remove(new Integer(100));
System.out.println(c.remove(new Name("f1","l1")));
System.out.println(c);
}
}
class Name {
private String firstname,lastname;
public Name(String firstname,lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public String getFirstName() {
return firstname;
}
public String getLastName() {
return lastname;
}
public String toString() {
return firstname + " " + lastname;
}
}
- 问题1:Collection c = new ArrayList(); 父类引用指向子类对象=====继承。为什么这么写?
如果写成ArrayList c = new ArrayList(); 并且使用了ArrayList的方法,但之后想换成LinkedList c = new LinkedList();后,那些关于ArrayList中一些特定的方法就不能再使用。因为我可能暂时认为c用ArrayList比较合适,但之后想要换成LinkedList,直接换掉ArrayList为LinkedList就可以了,其他的一概不动。这就提供了最大的灵活性。 - 问题2:c.add(100)这样写可以吗?
不可以,boolean add(Object obj)方法是向集合中添加一个元素,只能添加对象,Object!一个int型的100是存在栈上的,而栈上的东西随时有可能被清空,所以不能添加基本类型。 - 问题3:c.remove(new Name(“f1”,”l1”))中new Name(“f1”,”l1”)会被remove掉吗?不会,所以返回false。为什么?
因为这里的new Name(“f1”,”l1”)和add时的new Name(“f1”,”l1”)是不相等的,因为并没有重写equals方法,所以这里的不相等依然是指他俩指向的不是同一对象。当然返回false了。
容器类对象在调用remove、contains等方法时需要比较对象是否相等,这会涉及到对象类型的equals方法和hashCode方法;对于自定义的类型,需要重写equals和hashCode方法以实现自定义的对象相等规则。(重写equals方法必须重写hashCode方法,在equals效率较低时)
注意:相等的对象应该具有相等的hashCode
//增加Name类的equals方法和hashCode方法:
public boolean equals(Object obj) {
if (obj instanceof Name) {
Name name = (Name) obj;
return (firstName.equals(name.firstname)) && (lastName.equals(name.lastName));
}
return super.equals(obj);
}
public int hashCode() {
return firstName.hashCode();
}
Iterator与Interable
Collection接口、Iterable接口、Iterator接口、Iterator()方法之间的关系如下图所示:
为什么Collection接口要继承于Iterable接口,而不是Iterator接口?原因大致有三点:
- 在jdk 1.5以后,引入了Iterable,使用foreach语句(增强型for循环)必须使用Iterable类。
- Java设计者让Collection继承于Iterable而不是Iterator接口。首先要明确的是,Iterable的子类Collection,Collection的子类List,Set等,这些是数据结构或者说放数据的地方。Iterator是定义了迭代逻辑的对象,让迭代逻辑和数据结构分离开来,这样的好处是可以在一种数据结构上实现多种迭代逻辑。
- 更重要的一点是:每一次调用Iterable的Iterator()方法,都会返回Interator接口的操作对象,这个对象包括了Collection收集的所有对象,各个Iterator对象之间不会相互干扰,这样保证了可以同时对一个数据结构进行多个遍历。这是因为每个循环都是用了独立的迭代器Iterator对象。
- 所有实现了Collection接口的容器类都有一个iterator()方法用以返回Iterator接口操作的对象。
- Iterator对象称作迭代器,用以方便的实现对容器内元素的遍历操作。
boolean hasNext(); 判断游标右边是否有元素
Object next(); 返回游标右边的元素并将游标移动到下一个位置
void remove(); 删除游标左面的元素,在执行完next之后该操作只能执行一次
因此,无论List、Set、Queue还是任何Collection,都可以使用以下的forEach()来显示所收集的对象:
...
static void forEach(Iterable iterable) {
Iterator iterator = iterable.iterator();
while(iterator.hasNext()) {
out.println(iterator.next());
}
}
...
实际上,增强式for循环不仅用在数组上,还可运用在操作Iterable接口的对象上。因此,forEach()方法用增强式for循环更加简化:
import java.util.*;
public class ForEach {
public static void main(String[] args) {
List names = Arrays.asList("Justin","Monica","Irene");
forEach(names);
forEach(new HashSet(names));
forEach(new ArrayDeque(names));
}
static void forEach(Iterable i) {
for(Object o : i) {
System.out.println(o);
}
}
}
具有索引的List
- List接口是Collection的子接口,实现List接口的容器类中的元素是有顺序的,而且可以重复。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
List接口中定义的方法:
A:添加功能
void add(int index, Object obj) 在指定位置添加元素
B:删除功能
Object remove(int index) 根据指定索引删除元素,并把删除的元素返回
C:修改功能
Object set(int index, Object obj)把指定索引位置的元素修改为指定的值,返回修改前的值
D:获取功能
int indexOf(Object o) 返回指定元素在集合中第一次出现的索引
Object get(int index) 获取指定位置的元素
ListIterator listIterator() 列表迭代器
E:截取功能
List subList(int fromIndex, int toIndex) 截取集合
- List容器类有ArrayList、LinkedList等。
- ArrayList:由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。
- LinkedList:由链表实现的List。对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
- 若收集的对象经常会有变动索引的情况,也许考虑链接方式操作的List会比较好,像是随时会有客户端登录或注销的客户端List,使用LinkedList会有比较好的效率。
内容不重复的Set
- Set接口是Collection的子接口,Set接口没有提供额外的方法,但是实现Set接口的容器类中的元素是没有顺序的,而且不可重复。
- Set容器可与数学中的“集合”概念相对应。
- Set容器类有HashSet、TreeSet等。
- HashSet:能快速定位一个元素,存入HashSet的对象必须定义hashCode()。
- TreeSet:保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
- 加入Set的Object必须定义equals()方法以确保对象的唯一性。
支持队列操作的Queue
- Queue接口是Collection的子接口,是一种满足”先进先出“的容器,即放入的顺序和取出的顺序相同。
Queue用来存放等待处理元素 的集合,这种场景一般用于缓冲、并发访问。
除了继承 Collection 接口的一些方法,如add()、remove()、element();Queue 还添加了额外的 添加、删除、查询操作,如offer()、poll()、peek()等。
offer() 在队列后端加入对象,成功返回true
poll() 取出队列前端对象,若列队为空返回null
peek() 取得队列前端对象,但不取出,若队列为空返回null
- LinkedList:虽然名为”List“,但同时也实现了Queue接口
- PriorityQueue:对象会在队列中按照优先级排序(值越小,优先级越高),有最高优先级的将会第一个被取出。
- Deque:双向队列和队列基本相同,但是你可以在任何一端添加或移除元素。
Map接口
- 实现Map接口的类用来存储键值对。
- Map类中存储的键值对通过键来标识,所以键值不能重复。
Map接口中定义的方法:
A:添加功能
Object put(K key ,V value) 当key在集合中不存在时添加元素;当key存在时替换元素
B:判断功能
Boolean containsKey (Object key) 判断指定的键是否在集合中存在
Boolean containsValue(Object value) 判断指定的值是否在集合中存在
Boolean isEmpty() 判断集合是否为空
C:删除功能
void clear() 清除所有键值对数据
D:获取功能
Object get (Object key) 根据键获取值
Set<K> keySet() 所有键的集合
Collection<V>values() 所有值的集合
E:长度功能
int size()
- 同时取得Map的键和值,可以使用entrySet()方法,这回返回一个Set对象,每个元素都是Map.Entry实例,可以调用getKey()取得键,调用getValue()取得值。
- Map接口的实现类有HashMap、TreeMap、Properties等。
- HashMap:特点是键无序。
- TreeMap:特点是键有序,这里的排序必须操作Comparable接口或者在创建TreeMap时指定操作Comparator接口的对象。
- Properties:该类继承自HashTable。一般常用Properties的setProperty()指定字符串类型的键值,getProperty()指定字符串类型的键来取回字符串类型的值,通常称为属性名称与属性值。
如何选择数据结构?
衡量标准:读的效率和改的效率
Array 读快 改慢
Linked 改快 读慢
Hash 两者之间
Collections类
Collection与Collections有什么区别?
- java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。
- java.util.Collections 是一个包装类(工具类/帮助类)。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于Java的Collection框架。
排序操作
Collections提供以下方法对List进行排序操作:
void sort(List list) 按自然排序的升序排序
void reverse(List list) 反转
void shuffle(List list) 随机排序
void sort(List list, Comparator c) 定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j) 交换两个索引位置的元素
void rotate(List list, int distance) 旋转,当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将list的前distance个元素整体移到后面。
查找,替换操作
int binarySearch(List list, Object key) 对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll) 根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c) 根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj) 用元素obj填充list中所有元素
int frequency(Collection c, Object o) 统计元素出现次数
int indexOfSubList(List list, List target) 统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
boolean replaceAll(List list, Object oldVal, Object newVal) 用新元素替换旧元素
Comparable与Comparator
根据什么确定容器中对象的“大小”顺序?
所有可以“排序”的类都实现了java.lang.Comparable接口,Comparable接口有个compareTo(Object obj)方法必须返回大于0、等于0或小于0的数。
返回0表示this == obj
返回正数表示this > obj
返回负数表示this < obj
实现了Compareable接口的类通过实现compareTo方法从而确定该类对象的排序方式。
泛型
在定义集合的时候同时定义集合中对象的类型。
如:ArrayList< String> names = new ArrayList< String>();
声明与建立对象时,可使用角括号告知编译程序,这个对象收集的都会是String,而取回之后也会是String,不再使用括号转换类型。如果实际加入了不是String的对象,编译程序就会检查出这个错误。
实际上,泛型语法有一部分是编译程序蜜糖(一部分是记录在位码中的信息)。
在JDK7后,也可以这样写:
List< String> words = new LinkedList<>();