【集合的由来】
对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定。
就使用集合容器进行存储
【集合框架的构成及分类】
1、集合的顶层
Collection
|--List:有序(存入和取出的顺序一致),元素都有索引(角标),元素可以重复。
|--Set:元素不能重复,无序。
Map
|
|--HashMap
|--TreeMap
2、list的子类
List:list
|--Vector:内部是数组数据结构,是同步的。增删,查询都很慢!
|--ArrayList:内部是数组数据结构,是不同步的。替代了Vector。查询的速度快。
|--LinkedList:内部是链表数据结构,是不同步的。增删元素的速度很快。
3、Set的子类
Set:元素不可以重复,是无序。
Set接口中的方法和Collection一致。
|--HashSet: 内部数据结构是哈希表 ,是不同步的。
如何保证该集合的元素唯一性呢?
是通过对象的hashCode和equals方法来完成对象唯一性的。
如果对象的hashCode值不同,那么不用判断equals方法,就直接存储到哈希表中。
如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true。
如果为true,视为相同元素,不存。如果为false,那么视为不同元素,就进行存储。
记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。
一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法。
建立对象判断是否相同的依据。
* 练习:
* "fdgavcbsacdfs" 获取该字符串中,每一个字母出现的次数。
* 要求打印结果是:a(2)b(1)...;
public static String getCharCount(String str) {
//将字符串变成字符数组
char[] chs = str.toCharArray();
//定义map集合表。
Map<Character,Integer> map = new TreeMap<Character,Integer>();
for (int i = 0; i < chs.length; i++) {
if(!(chs[i]>='a' && chs[i]<='z' || chs[i]>='A' && chs[i]<='Z'))
// if(!(Character.toLowerCase(chs[i])>='a' && Character.toLowerCase(chs[i])<='z'))
continue;
//将数组中的字母作为键去查map表。
Integer value = map.get(chs[i]);
int count = 1;
//判断值是否为null.
if(value!=null){
count = value+1;
}
// count++;
map.put(chs[i], count);
/*
if(value==null){
map.put(chs[i], 1);
}else{
map.put(chs[i], value+1);
}
*/
}
return mapToString(map);
}
private static String mapToString(Map<Character, Integer> map) {
StringBuilder sb = new StringBuilder();
Iterator<Character> it = map.keySet().iterator();
while(it.hasNext()){
Character key = it.next();
Integer value = map.get(key);
sb.append(key+"("+value+")");
}
return sb.toString();
}
|--TreeSet:可以对Set集合中的元素进行排序。是不同步的。
判断元素唯一性的方式:就是根据比较方法的返回结果是否是0,是0,就是相同元素,不存。
TreeSet对元素进行排序的方式一:
让元素自身具备比较功能,元就需要实现Comparable接口。覆盖compareTo方法。
如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。怎么办?
可以使用TreeSet集合第二种排序方式二:
让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。
将该类对象作为参数传递给TreeSet集合的构造函数。
4、Map常用的子类:
|--Hashtable :内部结构是哈希表,是同步的。不允许null作为键,null作为值。
|--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
|--HashMap : 内部结构是哈希表,不是同步的。允许null作为键,null作为值。
|--TreeMap : 内部结构是二叉树,不是同步的。可以对Map集合中的键进行排序。
【集合特点】
1.用于存储对象的容器
2.集合的长度是可变的
3.集合中不可以存储基本数据类型
【集合的共性】
集合容器因为内部的数据结构不同,有多种具体容器
不断向上抽取,就形成了集合框架
框架的顶层Collection的常见方法:
1.添加
boolean add(Object obj);
boolean addAll(Conlection<?> coll);
2.删除
boolean remove(Object obj);
boolean removeAll(Collection coll);//将两个集合中的相同元素从调用removeAll的集合中删除
clear();
3.判断
boolean contains(Object obj)
boolean containsAll(Collection obj);//如果此集合包含指定collection中所有元素,则返回true;
boolean isEmpty();
4.获取
size();
iterator();取出元素的方式:迭代器
5.其他
retainAll();取交集,删除两个集合不相同的元素,和removeAll相反
toArray();将集合转成数组
【迭代器】
相关方法
hashNext();//若果仍有元素可以迭代,则返回true;
next();//首先返回第一个元素,然后他会自动往后走
remove();移除迭代器返回最后一个元素
使用:
Collection coll=new ArrayList();
coll.add("abc");
Iterator it=coll.iterator();
while(it.hasNext){
System.out.println(it.next());
}
这个方式最后it不会被释放,暂用内存
开发中用
for(Iterator it=coll.iterator();it.hasNext;){
System.out.println(it.hasNext());
}
【迭代原理】
该对象必须依赖于具体容器(集合),因为每一个容器的数据结构都不同
所以该迭代器对象是在容器中进行内部实现的
迭代器可以实现迭代过程中完成对元素的增删改查
ListIterator it = list.listIterator();
while(it.hasNext){
Object obj=it.next();
if(obj.equals("")){
it.set();//修改元素
//只有List集合具备这个迭代功能
}
}
【list与set的特点】
Collection是一个接口
常用的两个这个接口下的子体系
List和set连个接口
【List】
有序(存入和取出的顺序一致)元素都有索引(角标),元素可以重复
常用方法:
List list=new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
1.添加元素
void add(Object obj);
2.插入元素
void add(index,element);
void add(index,collection);
3.删除元素
Object remove(index);
4.修改元素
Object set(index,element);
list.set(0,"bcd");//输出list为bcd
5.获取
Object get(index);
int indexOf(Object);
int lastIndex();
List suList(from,to);//包含from,不包含to
list.subList(1,2);//输出abc1
listIterator:列表迭代器,只有list有
在迭代器过程中,不要使用集合操作元素,容易出现异常。
while(it.hasNext()){
Object obj=it.next();
if(obj.equals("abc2")){
list.add("abc2");
}eles{
system.out.println("next:"+obj);
}
}
}
会报ConcurrentModificationException,因为iterator初始化的时候只有3个,
这个时候往集合中添加元素,下一次循环的时候就会产生冲突
解决:
使用Iterator接口的子接口listIterator来完成在迭代中对元素进行更多的操作
方法:
listIterator(int index);//列表迭代器从指定位置开始
add(E e);//将指定的元素加入列表
hasNext()
hasPrevious();//如果以逆向遍历列表,列表迭代器有多个元素,则返回true
next();
nextIndex();//返回next的后续调用所返回元素的索引
previousIndex();//返回对previours的后续调用返回元素的索引
remove();
set(E e);//用指定元素替换next或previous返回的最后一个元素
【list集合的子类】
Vector
内部是数组数据结构。是同步的。几乎不用
ArrayList
内部是数组数据结构,是不同步的。替代了Vector
ArrayList可增长的原理
在内存中在new一个新的数组(这个数组比原来的数组更大),然后把原来的数组复制到这个新数组中
特点:增删的速度不快,查询速度快。查询块是因为对象存在相连的一片连续的空间里面,而LinkeList则不是在连续的空间中
LinkedList 链接列表
结构:链表1记录的是链表2的地址
如:
(链表1 记录98)--(链表2 地址98 记录22)---(链表3 地址22。。。)
删除元素2时,只需让链表1 记录22,链表2就被删除了
内部是链表数据结构,是不同步的。
特点:操作元素的增删元素速度快,查询相对ArrayList慢。
LinkedList也有角标
【LinkeList】
addFirst();添加到头部
getFirst();获取头部
removeFirst();删除头部
模仿堆栈和队列
//先进先出
public class DuiLie{
private LinkedList link;
public (){
link=new LinkedList();
}
public void myAdd(Object obj){
link.addLast(obj);
}
public Object myGet(){
return link.removeFirst();
}
}
如果改成栈 就反过来就可以了
【set】
元素不能重复,无序,Set接口中的方法和Collection一致。
如:HashSet hs=new HashSet();
hs.add("1");
hs.add("2");
hs.add("1");
hs.add("3");
hs.add("4");
set集合的遍历必须使用iterator
无序不重复输出:4,2,1,3
【set的子类】
HashSet
内部数据结构是哈希表 ,是不同步的。
哈希表:
哈希是一种算法,这种算法会算出来很多值,这些值存储起来就是哈希表,
这个表有对应关系。这种算法对数组存储有优化,查找速度很快。
哈希表判断元素是否重复的方法:1.判断两个元素的哈希值是否相同,如果相同再判断两个对象的内容是否相同。
2.判断哈希值相同,其实判断的是对象的hashCode的方法。判断内容相同用的是equals方法
注意:如果哈希值不同,是不需要判断equals的。
如 "ba"和"ba",就需要判断两次
自定义对象如果需要在set中保证唯一性需要重写hashcode方法和equals方法。
如:
class Person {
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
`
}
@Override
public int hashCode() {
// System.out.println(this+".......hashCode");
return name.hashCode()+age*27;//乘以随便一个数保证hashcode的唯一性
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
// System.out.println(this+"....equals....."+obj);
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
HashSet的子类: LinkedHashSet 有序并保证唯一性
treeSet
可以对Set集合中的元素进行排序。是不同步的。
判断元素唯一性的方式:就是根据比较方法的返回结果是否是0,是0,就是相同元素,不存。
TreeSet 存储元素的有序排序的方式是二叉树,元素跟已存在的元素进行比较,大的在右边,小的在左边
TreeSet对元素进行排序的方式一:
让元素自身具备比较功能,元素就需要实现Comparable接口。覆盖compareTo方法。
public class Person /*extends Object*/ implements Comparable {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
// System.out.println(this+".......hashCode");
return name.hashCode()+age*27;
// return 100;
}
@Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(!(obj instanceof Person))
throw new ClassCastException("类型错误");
// System.out.println(this+"....equals....."+obj);
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return name+":"+age;
}
@Override
public int compareTo(Object o) {
Person p = (Person)o;
int temp = this.age-p.age;
return temp==0?this.name.compareTo(p.name):temp;
// int temp = this.name.compareTo(p.name);
// return temp==0?this.age-p.age:temp;
/*
if(this.age>p.age)
return 1;
if(this.age<p.age)
return -1;
else{
return this.name.compareTo(p.name);
}
*/
}
}
如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。怎么办?
可以使用TreeSet集合第二种排序方式二:
让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。
将该类对象作为参数传递给TreeSet集合的构造函数。(这个方式比方式一优先级更高)
public class ComparatorByName implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (Person)o1;
Person p2 = (Person)o2;
int temp = p1.getName().compareTo(p2.getName());
return temp==0?p1.getAge()-p2.getAge(): temp;
}
}
使用:TreeSet ts = new TreeSet(new ComparatorByName ());
特殊用法:如果需要TreeSet有序输出,只需要把compare固定返回1(正序)或者-1(倒序)
字符串按照长度排序,如果长度一样再按照字母排序
public class ComparatorByLength implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Person p1 = (String)o1;
Person p2 = (String)o2;
int temp = p1.length().compareTo(p2.length());
return temp==0?p1.comparaTo(p2): temp;
}
}
只有List有
list.get(i);方法,set是没有的
【Map】
常用方法:
1,添加。
value put(key,value):返回前一个和key关联的值,如果没有返回null.
2,删除。
void clear():清空map集合。
value remove(key):根据指定的key翻出这个键值对。
3,判断。
boolean containsKey(key):
boolean containsValue(value):
boolean isEmpty();
4,获取。
value get(key):通过键获取值,如果没有该键返回null。
当然可以通过返回null,来判断是否包含指定键。
int size(): 获取键值对的个数。
遍历map集合
第一个方法
原理通过keySet获取所有map中所有的键所在的Set集合,通过set的迭代器获取到每一个键,再对每一个键通过Map集合的get方法取其对应的值即可
Set<Object>set=map.ketSet();
Iterator<Object>it=set.iterator();
while(it.hashNex()){
Object key=it.next();
Object value= map.get(key);
}
第二种方法:
先把map转成Set集合
找到另一个方法,entrySet
该方法将键和值的映射关系作为对象存储到了Set集合中,而这个映射关系的类型就是Map.Entry类型
Set<Map.Entry<Object,Object>>entrySet=map.entrySet();
Iterator<Map.Entry<Object,Object>>it =entrySet.iterator();
while(it.hasNext()){
Map.Entry<Object,Object>me=it.next();
Object key = me.getKey();
Object value =me.getValue();
}
【获取所有值】
values();
Collection <Object> values=map.values();
Iterator <Object> it=values.iterator();
while(it.hashNext()){
Object value=it.next();
}
Map常用的子类:
|--Hashtable :内部结构是哈希表,是同步的。不允许null作为键,null作为值。
|--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
|--HashMap : 内部结构是哈希表,不是同步的。允许null作为键,null作为值。
|--TreeMap : 内部结构是二叉树,不是同步的。可以对Map集合中的键进行排序。
【泛型】
1.由来
jdk1.5出现的安全机制。
泛型出现之前:
ArrayList a1= new ArrayList();
a1.add("abc");
a1.add("hhah");
a1.add(4);
Iterator it=a1.iterator();
while(it.hasNext()){
String str=(String)it.next();
}
这个时候就出现了安全隐患,4不能转成String,所以就需要在定义的时候明确类型,
ArrayList<String>a1=new ArrayList<String>();
左边是声明,右边是实体,这样操作的类型就被指定了,这就是泛型。
2.好处:
将运行时期的问题ClassCastException转到落编译时期
避免了强制转换的麻烦
<>:什么时候用?当操作的引用数据类型不确定的时候。就使用<>。将要操作的引用数据类型传入即可.
其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型 。
比如说ArrayList<E>,E接收的数据类型是引用数据类型,而引用数据类型不是接口就是类,这就是为什么不用e。
泛型技术是给编译器使用的技术,用于编译时期。确保了类型的安全。
运行时,会将泛型去掉,生成的class文件中是不带泛型的,这个称为泛型的擦除。
为什么擦除呢?因为为了兼容运行的类加载器。
泛型的补偿:在运行时,通过获取元素的类型进行转换动作。不用使用者在强制转换了。
泛型的通配符:? 未知类型。
泛型的限定:
? extends E: 接收E类型或者E的子类型对象。上限
一般存储对象的时候用。比如 添加元素 addAll.
? super E: 接收E类型或者E的父类型对象。 下限。
一般取出对象的时候用。比如比较器。
我们这一代的程序员在使用已经定义了泛型的JDK API时必须明确传入的数据类型,为什么有时候不传也可以呢,那是为了兼容老版本,但是会有警告。
泛型方法:
定义一个工具类,
public class Tools<T>{
public void show(T t){
System.out.println("show:"+t);
}
}
使用:
Tool<String>tool=new Tool<String>();
上面已经明确了泛型的类型是String,
tool.show(new Integer(4));这样子就会报错
如果想让方法操作的类型不确定,需要把泛型定义在方法上
public <W> void show(W w){
System.out.println("show:"+w);
}
当方法静态时,不能访问类上定义的泛型,如果静态方法使用泛型,只能将泛型定义在方法上
注意格式,不要把泛型放在修饰符前面
public static <W> void show(W w){
System.out.println("show:"+w);
}
泛型接口,将泛型定义在接口上。
interface Inter<T>{
public void show(T t);
}
class InterImp1 implements Inter<String>{
public void show(String str);
}
使用: InterImpl in=new InterImpl();
in.show("abc");
还有一种情况。他的实现类还是不知道具体的类型
class InterImp12<Q> implement Inter<Q>{
Public void show(Q q);
}
那就在使用的时候明确类型
InterImpl in2=new InterImpl();
in2.show(5);
泛型的限定
<?>通配符 可以接受所有 类型
<? extend T> 可以接受T和T的子类,这就是上限,一般存储元素的时候都是用上限,因为这样取出按照上限类型来运算的。不会出现安全隐患
<? super T>可以接收T和T的父类 这就是下限,通常对集合中的元素进行取出操作时,可以使用下限
===========================================================
集合的一些技巧:
需要唯一吗?
需要:Set
需要制定顺序:
需要: TreeSet
不需要:HashSet
但是想要一个和存储一致的顺序(有序):LinkedHashSet
不需要:List
需要频繁增删吗?
需要:LinkedList
不需要:ArrayList
如何记录每一个容器的结构和所属体系呢?
看名字!
List
|--ArrayList
|--LinkedList
Set
|--HashSet
|--TreeSet
后缀名就是该集合所属的体系。
前缀名就是该集合的数据结构。
看到array:就要想到数组,就要想到查询快,有角标.
看到link:就要想到链表,就要想到增删快,就要想要 add get remove+frist last的方法
看到hash:就要想到哈希表,就要想到唯一性,就要想到元素需要覆盖hashcode方法和equals方法。
看到tree:就要想到二叉树,就要想要排序,就要想到两个接口Comparable,Comparator 。
而且通常这些常用的集合容器都是不同步的。
============================================
map练习1:
"fdgavcbsacdfs" 获取该字符串中,每一个字母出现的次数。
public static String getCharCount(String str) {
//将字符串变成字符数组
char[] chs = str.toCharArray();
//定义map集合表。
Map<Character,Integer> map = new TreeMap<Character,Integer>();
for (int i = 0; i < chs.length; i++) {
if(!(chs[i]>='a' && chs[i]<='z' || chs[i]>='A' && chs[i]<='Z'))
// if(!(Character.toLowerCase(chs[i])>='a' && Character.toLowerCase(chs[i])<='z'))
continue;
//将数组中的字母作为键去查map表。
Integer value = map.get(chs[i]);
int count = 1;
//判断值是否为null.
if(value!=null){
count = value+1;
}
// count++;
map.put(chs[i], count);
/*
if(value==null){
map.put(chs[i], 1);
}else{
map.put(chs[i], value+1);
}
*/
}
return mapToString(map);
}
private static String mapToString(Map<Character, Integer> map) {
StringBuilder sb = new StringBuilder();
Iterator<Character> it = map.keySet().iterator();
while(it.hasNext()){
Character key = it.next();
Integer value = map.get(key);
sb.append(key+"("+value+")");
}
return sb.toString();
}
map练习2
查表法
public static String getWeekByMap(String week){
Map<String,String> map = new HashMap<String,String>();
map.put("星期一","Mon");
map.put("星期二","Tus");
map.put("星期三","Wes");
map.put("星期日","Sun");
map.put("星期天","Sun");
return map.get(week);
}
public static String getWeek(int week){
if(week<1 || week>7)
throw new RuntimeException("没有对应的星期,请您重新输入");
String[] weeks = {"","星期一","星期二"};
return weeks[week];
}