容器总结
1:一个图
2:一个类collection
在 Java2中,有一套设计优良的接口和类组成了Java集合框架Collection,使程序员操作成批的数据或对象元素极为方便。这些接口和类有很多对抽象数据类型操作的API,而这是我们常用的且在数据结构中熟知的。例如Map,Set,List等。并且Java用面向对象的设计对这些数据结构和算法进行了封装,这就极大的减化了程序员编程时的负担。程序员也可以以这个集合框架为基础,定义更高级别的数据抽象,比如栈、队列和线程安全的集合等,从而满足自己的需要。
Java2的集合框架,抽其核心,主要有三种:List、Set和Map。如下图所示:
需要注意的是,这里的 Collection、List、Set和Map都是接口(Interface),不是具体的类实现。 List lst = new ArrayList(); 这是我们平常经常使用的创建一个新的List的语句,在这里, List是接口,ArrayList才是具体的类。
常用集合类的继承结构如下:
Collection<--List<--Vector
Collection<--List<--ArrayList
Collection<--List<--LinkedList
Collection<--Set<--HashSet
Collection<--Set<--HashSet<--LinkedHashSet
Collection<--Set<--SortedSet<--TreeSet
Map<--SortedMap<--TreeMap
Map<--HashMap
List:
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下 >标)来访问List中的元素,这类似于Java的数组。
Vector:
基于数组(Array)的List,其实就是封装了数组所不具备的一些功能方便我们使用,所以它难易避免数组的限制,同时性能也不可能超越数组。所以,在可能的情况下,我们要多运用数组。另外很重要的一点就是Vector是线程同步的(sychronized)的,这也是Vector和ArrayList 的一个的重要区别。
ArrayList:
同Vector一样是一个基于数组上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector好一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。
LinkedList:
LinkedList不同于前面两种List,它不是基于数组的,所以不受数组性能的限制。
它每一个节点(Node)都包含两方面的内容:
1.节点本身的数据(data);
2.下一个节点的信息(nextNode)。
所以当对LinkedList做添加,删除动作的时候就不用像基于数组的ArrayList一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了,这是LinkedList的优势。
List总结:
所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ]
所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]
所有的List中可以有null元素,例如[ tom,null,1 ]
基于Array的List(Vector,ArrayList)适合查询,而LinkedList 适合添加,删除操作
Set:
Set是一种不包含重复的元素的无序Collection。
HashSet:
虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是在 HashMap的基础上来实现的,这个就是Set和List的根本区别。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。看看 HashSet的add(Object obj)方法的实现就可以一目了然了。
public boolean add(Object obj) {
return map.put(obj, PRESENT) == null;
}
这个也是为什么在Set中不能像在List中一样有重复的项的根本原因,因为HashMap的key是不能有重复的。
LinkedHashSet:
HashSet的一个子类,一个链表。
TreeSet:
SortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。
Set总结:
Set实现的基础是Map(HashMap)
Set中的元素是不能重复的,如果使用add(Object obj)方法添加已经存在的对象,则会覆盖前面的对象
Map:
Map 是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个 Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求,你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)。
Map有两种比较常用的实现:HashMap和TreeMap。
HashMap也用到了哈希码的算法,以便快速查找一个键,
TreeMap则是对键按序存放,因此它便有一些扩展的方法,比如firstKey(),lastKey()等,你还可以从TreeMap中指定一个范围以取得其子Map。
键和值的关联很简单,用put(Object key,Object value)方法即可将一个键与一个值对象相关联。用get(Object key)可得到与此key对象所对应的值对象。
3.1:3个知识点- for和lterator
1.Iterator
Java提供一个专门的迭代器«interface»Iterator,我们可以对某个序列实现该interface,来提供标准的Java迭代器。Iterator接口实现后的功能是“使用”一个迭代器.
文档定义:
Package java.util;
public interface Iterator<E> {
boolean hasNext();//判断是否存在下一个对象元素
E next();
void remove();
}
PS:在lterator遍历时只能通过remove删除,因为在遍历时,相当上了线程锁,只能lterator进行操作。
2.Iterable
Java中还提供了一个Iterable接口,Iterable接口实现后的功能是“返回”一个迭代器,我们常用的实现了该接口的子接口有: Collection, Deque, List, Queue, Set 等.该接口的iterator()方法返回一个标准的Iterator实现。实现这个接口允许对象成为 For each 语句的目标。就可以通过For each语法遍历你的底层序列。
Iterable接口包含一个能够产生Iterator的iterator()方法,并且Iterable接口被foreach用来在序列中移动。因此如果创建了任何实现Iterable接口的类,都可以将它用于foreach语句中。
文档定义:
Package java.lang;
import java.util.Iterator;
public interface Iterable<T> {
Iterator<T> iterator();
}
使用Iterator的简单例子
import java.util.*;
public class TestIterator {
public static void main(String[] args) {
List list=new ArrayList();
Map map=new HashMap();
//初始化list和map的数据
for(int i=0;i<10;i++){
list.add(new String("list"+i) );
map.put(i, new String("map"+i));
}
Iterator iterList= list.iterator();//List接口实现了Iterable接口
//循环list
while(iterList.hasNext()){
String strList=(String)iterList.next();
System.out.println(strList.toString());
}
Iterator iterMap=map.entrySet().iterator();
//循环map
while(iterMap.hasNext()){
Map.Entry strMap=(Map.Entry)iterMap.next();
System.out.println(strMap.getValue());
}
}
}
4.foreach和Iterator的关系
for each是jdk5.0新增加的一个循环结构,可以用来处理集合中的每个元素而不用考虑集合定下标。
格式如下
1 for(variable:collection){ statement; }
定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(块)。collection必须是一个数组或者是一个实现了lterable接口的类对象。
上面的例子使用泛型和forEach的写法:
import java.util.*;
public class TestIterator {
public static void main(String[] args) {
List<String> list=new ArrayList<String> ();
for(int i=0;i<10;i++){
list.add(new String("list"+i) );
}
for(String str:list){
System.out.println(str);
}
}
使用for循环时,在循环内使用list.remove()会导致错误,可以使用如下方法:
for(int i = 0; i < list.size();i++){
if(true){
list.remove(list.get(i));
--i;//remove的同时下标跟着减
}
}
可以看出,使用for each循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值。
forEach不是关键字,关键字还是for,语句是由iterator实现的,他们最大的不同之处就在于remove()方法上。
一般调用删除和添加方法都是具体集合的方法,例如:
List list = new ArrayList(); list.add(…); list.remove(…);
但是,如果在循环的过程中调用集合的remove()方法,就会导致循环出错,因为循环过程中list.size()的大小变化了,就导致了错误。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。
forEach就是为了让用iterator循环访问的形式简单,写起来更方便。当然功能不太全,所以但如有删除操作,还是要用它原来的形式。
4 使用for循环与使用迭代器iterator的对比
效率上的各有有事
采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快
采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快
从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合.
而使用 Iterator 的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样.
3.1:3个知识点- generic泛型
正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。
简单去理解就是之前object现在是特指某一种类型。
3.1:3个知识点- Auto-boxing/unboxing 打包与解包
Autoboxing和unboxing又名拆箱和装箱,简单一点讲,就是从primitive转换到wrapper class,例如int类型到Integer类型就是装箱,而Integer类型到int类型则是拆箱。当然,这里的装箱和拆箱都是auto的,是JVM在工作的内容,事实上不用我们手写,然而也有手写的对应方式,如下所示:
int i=10;
Integer a=new Integer(i);//装箱的操作
int j=a.intValue();//拆箱的操作
上面是手动的,在Java5.0之后已经在JVM中有了自动的装箱和拆箱的转换,如下所示:
int i=10;
Integer b=i;//自动的装箱
int k=b;//自动的拆箱
装箱和拆箱就是这么简单,下面可以看一下自增是怎么一个过程,这是一个很有意思的事情,递减也是一样。
Integer d=new Integer(10);
d++;//这条语句使得d先拆箱,然后进行++操作,而后对结果再装箱
面的这条语句,使得Java保证了wrapper class也可以是正常使用通用的操作符,但这绝对不是C++中的运算符重载。你还可以试着分析三元表达式和条件表达式中的装箱拆箱过程。
这里需要注意的一点就是装箱和拆箱在Method Overload时候的问题,例如下面:
public void doSomething(double num);
public void doSomething(Integer num);
在一个类中有这么两个函数,那么对于整数int k,如果进行doSomething(k)的调用,会调用哪个呢?
在Java1.4中,显然是调用double类型的函数,然而,现在有了自动的拆箱和装箱之后会调用哪个呢?还double类型的函数,这样就保证了以前的代码在现在的版本中也可以正确的运行。
注意,Java5中,Method的解析是三个pass的过程:
A.编译器会试着不用任何的boxing和unboxing,或者启用vararg来定位正确的method。这会找到根据Java1.4的规则而会调用的任何method。
B.如果第一个pass失败了,编译器会再度尝试解析method,但这次会允许boxing和unboxing转换。具有vararg的method不在这次pass的考虑中。
C.如果第二个pass也失败了,编译器会做最后一次的尝试,允许boxing和unboxing,且同时也考虑到vararg method。
4:六个接口
set list map lterator foreach comparable foreachjdk5.0新增加的一个循环结构 也是包装了lterator接口应该不算接口,哪还有一中接口我就暂时先搁置了。