什么是集合框架?
集合可以看作是一种容器,用来存储对象信息。所有集合类都位于java.util包下,但支持多线程的集合类位于java.util.concurrent包下。
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List、Set、Queue、Map四种接口体系,(注意:Map不是Collection的子接口)。
- 其中List代表了有序可重复集合,可直接根据元素的索引来访问;Set代表无序不可重复集合,只能根据元素本身来访问;Queue是队列集合;Map代表的是存储key-value对的集合,可根据元素的key来访问value。
- 上图中淡绿色背景覆盖的是集合体系中常用的实现类,分别是ArrayList、LinkedList、ArrayQueue、HashSet、TreeSet、HashMap、TreeMap等实现类。
List集合
List集合代表一个有序、可重复集合,集合中每个元素都有其对应的顺序索引。List集合默认按照元素的添加顺序设置元素的索引,可以通过索引(类似数组的下标)来访问指定位置的集合元素。
ArrayList
ArrayList是一个动态数组,也是我们最常用的集合,是List类的典型实现。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
如下演示ArrayList的用法:
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
//1.生成ArrayList对象
ArrayList list = new ArrayList();
//2.向集合中添加元素
list.add("aabb");
list.add("abc");
list.add(1,"aaa"); //在下标为1的位置插入元素aaa,元素abc向后移一位
list.add("abc");
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
//遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//增强for循环遍历
for(Object obj:list){
System.out.println(obj);
}
int index = list.indexOf("abc"); //indexOf()查找元素的下标
System.out.println(index); //2
int lastIndex = list.lastIndexOf("abc"); //lastIndex()元素最后一个下标
System.out.println(lastIndex); //3
list.remove(0); //移除下标为0的元素,后面的元素依次向前移动
for(Object obj : list ){
System.out.println(obj);
}
System.out.println(list.contains("aaa")); //集合中是否包含aaa
list.set(0,"zhangsan"); //把下标为0的元素改成zhangsan
for(Object obj : list ){
System.out.println(obj);
}
}
}
LinkedList
LinkedList是List接口的另一个实现,除了可以根据索引访问集合元素外,LinkedList还实现了Deque接口,可以当作双端队列来使用,也就是说,既可以当作“栈”使用,又可以当作队列使用。
LinkedList的实现机制与ArrayList的实现机制完全不同,ArrayLiat内部以数组的形式保存集合的元素,所以随机访问集合元素有较好的性能;LinkedList内部以链表的形式保存集合中的元素,所以随机访问集合中的元素性能较差,但在插入删除元素时有较好的性能。
Map集合
Map接口采用键值对Map<K,V>的存储方式,保存具有映射关系的数据,因此,Map集合里保存两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value,key和value可以是任意引用类型的数据。key值不允许重复,可以为null。如果添加key-value对时Map中已经有重复的key,则新添加的value会覆盖该key原来对应的value。常用实现类有HashMap、LinkedHashMap、TreeMap等。
HashMap与Hashtable
HashMap与Hashtable是Map接口的两个典型实现,它们之间的关系完全类似于ArrayList与Vertor。HashTable是一个古老的Map实现类,它提供的方法比较繁琐,目前基本不用了,HashMap与Hashtable主要存在以下两个典型区别:
-
HashMap是线程不安全,HashTable是线程安全的。
-
HashMap可以使用null值最为key或value;Hashtable不允许使用null值作为key和value,如果把null放进HashTable中,将会发生空指针异常。
为了成功的在HashMap和Hashtable中存储和获取对象,用作key的对象必须实现hashCode()方法和equals()方法。
public class HashMapDemo {
public static void main(String[] args) {
HashMap map= new HashMap();
map.put("zhangsna","1345485454564545");
map.put("lisi","454545455445");
map.put("fanchenda", "7898956567");
String str=(String) map.get("fanchenda");
System.out.println(str);
//遍历所有的键
for (Object key:map.keySet()) {
System.out.println(key);
}
//遍历所有的值
for (Object vaule:map.values()) {
System.out.println(vaule);
}
//集合的大小
System.out.println(map.size()); //大小 输出3
System.out.println(map.containsKey("lisi")); //containsKey 包含
for (Object obj:map.entrySet()) { //键值对形式
System.out.println(obj);
}
迭代器Iterator
所有集合接口和类都没有提供相应的遍历方法,而是把遍历交给迭代器Iterator完成。Iterator为集合而生,专门实现集合的遍历。它隐藏了各种集合实现类的内部细节,提供了遍历集合的一编程接口。
Collection接口的iterator()方法返回一个Iterator,然后通过Iterator接口的两个方法即可方便地实现遍历。
- boolean hasNext():判断是否存在另一个可访问的元素
- Object next():返回要访问的下一个元素
在使用迭代器的时候,一定不能改变集合的长度(增加或者删除),每次迭代器都会判断底层的记忆长度是否与实际长度相等,如果不相等,则会出现一个并发修改异常,ConcarrentModifiCationException,可以在集合中修改元素,但不能改变长度。
import java.util.HashMap;
import java.util.Iterator;
public class HashMapDemo {
public static void main(String[] args) {
HashMap map= new HashMap();
map.put("zhangsna","1345485454564545");
map.put("lisi","454545455445");
map.put("fanchenda", "7898956567");
//迭代方式
Iterator it=map.keySet().iterator(); //迭代器
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println();
Iterator ut=map.values().iterator();
while (ut.hasNext()) {
System.out.println(ut.next());
}
}
}
泛型集合
所谓泛型就是允许在定义类、接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定。增加了泛型支持后的集合,完全可以记住集合
中元素的类型,并可以在编译时检查集合中元素的类型。即解决一些安全问题,同时还可以让代码变得更加简洁。
创建一个只存放字符串的对象,代码如下:
import java.util.ArrayList;
import java.util.List;
public class GenericDemo {
public static void main(String[] args) {
//创建一个只能保存字符串的ArrayList集合
List<String> strList=new ArrayList<String>();
//如果存放其他类型的对象时会出现编译错误
strList.add("chaofn");
System.out.println(strList);
}
}
使用泛型的好处:
1、将运行时期出现的ClassCastExcpetion , 转移到了编译时期。方便于程序员解决问题,让运行时期问题减少。
2、避免了强制转换的麻烦。
如下代码可以解释这一点:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class StringDemo {
String name;
public StringDemo(String name){
this.name=name;
}
}
public class GenericDemo {
public static void main(String[] args) {
//创建一个只能保存字符串的ArrayList集合
List<String> ls=new ArrayList<String>();
//如果存放其他类型的对象时会出现编译错误
ls.add(new StringDemo("chaofn01"));
ls.add(new StringDemo("chaofn02"));
ls.add(new StringDemo("chaofn03"));
ls.add(new StringDemo("chaofn04"));
ls.add(1000);
MyIterator(ls);
}
public static void MyIterator(List ls){
Iterator it=ls.iterator();
while(it.hasNext()){
StringDemo s=(StringDemo) it.next();
System.out.println(s.name);
}
}
}
运行结果:
chaofn01
chaofn02
Exception in thread"main"java.lang.ClassCastException:java.lang.Integercannot be cast tocom.csu.test1.StringDemo at com.csu.test1.GenericDemo.MyIterator(GenericDemo.java:34)at com.csu.test1.GenericDemo.main(GenericDemo.java:27) chaofn03 chaofn04
在调用MyIterator(List ls) 方法时会发生ClassCastException 异常。而且在编译时是不会有任何提示,只有运行时会出现,所以使的程序存在安全隐患。
如果使用泛型则会在编译时提示错误,而且在遍历时不需要强制转换。如:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class StringDemo {
String name;
public StringDemo(String name){
this.name=name;
}
}
public class GenericDemo {
public static void main(String[] args) {
//创建一个只能保存字符串的ArrayList集合
List<StringDemo> ls=new ArrayList<StringDemo>();
//如果存放其他类型的对象时会出现编译错误
ls.add(new StringDemo("chaofn01"));
ls.add(new StringDemo("chaofn02"));
ls.add(new StringDemo("chaofn03"));
ls.add(new StringDemo("chaofn04"));
//下面一行代码在编译时会出错
//ls.add(1000);
MyIterator(ls);
}
public static void MyIterator(List<StringDemo> ls){
Iterator<StringDemo> it=ls.iterator();
while(it.hasNext()){
//不需要强制转化成StringDemo
StringDemo s= it.next();
System.out.println(s.name);
}
}
}
泛型方法
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
-
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的)。
-
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
-
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
-
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
实例:
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
运行结果:
整型数组元素为:
1 2 3 4 5
双精度型数组元素为:
1.1 2.2 3.3 4.4
字符型数组元素为:
H E L L O