您的位置:首页 > 编程语言 > Java开发

java基础学习总结——容器1136

2017-02-06 18:13 323 查看

容器总结



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遍历时
ea7b
只能通过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接口应该不算接口,哪还有一中接口我就暂时先搁置了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java