——- android培训、java培训、期待与您交流! ———-
一、集合框架
集合框架:又称集合类。可在API文档中查看java.util了解更多。
为什么会出现集合类?
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。数据存储用对象,对象存储用集合。
数组和集合类同为容器,有何不同?
数组虽然也可以存储对象,但长度是固定的;集合长度是可变的。数组中可以存储基本数据类型,集合只能存储对象。
集合的特点:
1.只用于存储对象的容器。
2.集合的长度是可变的。
3.集合可以存储不同类型的对象,不可以存储基本数据类型值。
集合容器因为内部的数据结构不同,有多种具体容器。不断的向上抽取,就形成了集合框架。
集合框架的构成及分类:
二、Collection接口
Collection接口常见的方法:
1.增加:boolean add(Object obj)//添加元素
boolean addAll(Collection coll)//将集合中的元素全部添加进该集合
2.删除:boolean remove(Object obj)//删除元素
void clear()//清空集合
3.判断:boolean contains(Object obj)判断是否包含obj元素
boolean containsAll(Collection coll)//判断是否包含该集合
boolean isEmpty()//判断集合中是否有元素。
4.获取:int size()//获取集合长度
Iterator iterator()//迭代器
5.转换:boolean retainAll(Collection coll)集合与集合取交集
Object toArray()//将集合转成数组Collection接口包含了两个子接口:
|---List:元素是有序的,元素可以重复,因为该集合体系有索引。
|---Set:元素是无序的,元素不可以重复。
List特有方法:凡是可以操作角标的方法都是该体系中的特有方法。
如:增:add(index,element)//在index角标位置上插入元素
addAll(index,collection)//在index角标位置上添加集合
删:remove(index)//移除角标index的元素
改:set(index,element)//修改角标index的元素
查:get(index)//根据角标index取元素
subList(from,to)//从角标from到to取元素
ListIterator()//List特有的迭代器
示例:
import java.util.*;
class ListDemo
{
public static void main(String[] args)
{
List li=new ArrayList();
li.add("java01");//增加
li.add("java02");
li.add("java03");
li.add("java09");
sop("增",li);
li.remove(1);//删除
sop("删",li);
li.set(2,"java08");//修改
sop("改",li);
System.out.println("查\t"+li.get(2));//查看
List li1=li.subList(0,li.size());
sop("查",li1);
}
public static void sop(String str,List li)
{
Iterator it=li.iterator();//迭代器
System.out.print(str+"\t");
while(it.hasNext())
{
System.out.print(it.next()+" ");
}
System.out.println();
}
}运行结果为:
注:这里使用的Iteratord迭代器是对所有的Collection容器进行元素取出的公共接口。对象依赖于具体容器,而每一个容器的数据结构都不同,迭代器对象是在容器中进行内部实现的,也就是说iterator方法在每个容器中的实现方式是不同的。对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,也就是iterator方法。
在List集合中有特有的迭代器——ListIterator,是Iterator的子接口。
在迭代时,不可以使用集合对象的方法操作集合中的元素。因为会发生异常。所以在迭代时只能用迭代器的方法操作元素,可是Iterator方法是有限的。只能对元素进行判断、取出、删除的操作。
如果想对元素进行其他操作(如:添加、修改等),就需要使用其子接口ListIterator。
示例:
import java.util.*;
class ListDemo
{
public static void main(String[] args)
{
ArrayList al=new ArrayList();
al.add("java01");//增加
al.add("java02");
al.add("java03");
al.add("java09");
ListIterator li;
for(li=al.listIterator();li.hasNext();)//往后查找元素
{
if(li.next().equals("java03"))
{
li.set("java10");//修改
}
}
while(li.hasPrevious())//往前查找元素
{
System.out.println(li.previous());
}
}
}运行结果为:
List中常见的子类对象:
|---ArrayList:底层的数据结构使用的是数组结构。特点:查询速度快,但是删除稍慢,线程不同步。
|---LinkedList:底层使用的是链表数据结构。特点:增删速度很快,查询稍慢。
|---Vector:底层使用的是数组数据结构。特点:线程同步,被ArrayList替代了。
示例:
import java.util.*;
class ListDemo
{
public static void main(String[] args)
{
Vector v=new Vector();
v.add("java01");//增加
v.add("java02");
v.add("java03");
v.add("java09");
Enumeration en=v.elements();
while(en.hasMoreElements())
{
System.out.println(en.nextElement());
}
}
}运行结果为:
注:枚举(Enumeration)是Vector特有的取出方式。在这里可以发现枚举和迭代器很像,其实枚举和迭代器是一样的。因为枚举的名称过长,所以被迭代器所取代了。
LinkedList的特有方法:
1.增加:addFirst()
addLast()
2.获取:获取元素,但不删除元素。如果集合中没有元素,会出现NoSuchElementException
getFirst()
getLast()
3.删除:获取元素,并删除元素。如果集合中没有元素,会出现NoSuchElementException
removaFirst()
removeLast()
在JDK1.6以后,出现了替代方法。
1.增加:和上述增加方法没什么不同
offFirst()
offLast()
2.获取:获取元素,但是不删除。如果集合中没有元素,会返回null。
peekFirst()
peekLast()
3.删除:获取元素,并删除元素。如果集合中没有元素,会返回null。
pollFirst()
pollLast()
示例:用LinkedList模拟堆栈(先进后出),队列(先进先出)
import java.util.*;
class DuiLie
{
private LinkedList ls;
DuiLie()
{
ls=new LinkedList();
}
public void set(Object obj)
{
ls.addFirst(obj);
}
public Object get()
{
return ls.removeLast();//return ls.removeFirst();
}
public boolean isNull()
{
return ls.size()>0;
}
}
class LinkedList174
{
public static void main(String[] args)
{
DuiLie dl=new DuiLie();
dl.set("java1");
dl.set("java2");
dl.set("java3");
dl.set("java4");
while(dl.isNull())
{
System.out.println(dl.get());
}
}
}运行结果为:
练习:自定义人对象作为元素存储到ArrayList集合中,并去除重复元素,如同姓名同年龄,视为同一个人。
import java.util.*;
class Person//继承自Object
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public boolean equals(Object obj)//覆盖Object中的equals方法,equals在底层自动调用的
{
System.out.println(this.name+"::::::"+this.age);
if(!(obj instanceof Person))
return false;
Person p=(Person)obj;
return this.name.equals(p.name)&&this.age==p.age;//这里的equals是String的方法
}
}
class ArrayList175
{
public static void main(String[] args)
{
ArrayList al=new ArrayList();
al.add(new Person("zhangsan",24));
al.add(new Person("lisi",49));
al.add(new Person("zhangsan",24));
al.add(new Person("wangwu",27));
al.add(new Person("zhangsan",24));
al.remove(new Person("wangwu",27));//remove的底层原理就是equals方法
ArrayList arr=delSame(al);
Iterator it=arr.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
System.out.println(p.getName()+";;;"+p.getAge());
}
}
public static ArrayList delSame(ArrayList al)
{
ArrayList arr=new ArrayList();//临时容器
Iterator li=al.iterator();//iterator()在collection接口中
while(li.hasNext())
{
Object obj=li.next();
//System.out.println(obj);
if(!arr.contains(obj))//contains的底层原理就是equals。当boolean contains(Object o);
//o==null ? e==null : o.equals(e),当不为空时会自动调用equals
arr.add(obj);
}
return arr;
}
}运行结果为:
二、Set子接口
Set:元素是无序的(插入和取出的顺序不一定一致),元素不可以重复。
Set集合的功能和Collection是一致的。Set中常见的子类对象:
|----HashSet:底层数据结构是哈希表。线程不同步。
HashSet是如何保证元素的唯一性?
是通过元素的两个方法:hashCode和equals来完成的。如果元素的hashCode值相同,继续判断元素的equals方法是否为true。如果元素的hashCode值不相同,则不会调用equals方法。
示例:自定义人对象作为元素存储到HashSet集合中,并去除重复元素,如同姓名同年龄,视为同一个人。
import java.util.*;
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public int hashCode()//复写父类Object的hashCode方法
{
return name.hashCode()+age*12;
}
public boolean equals(Object obj)//复写父类Object的equals方法
{
if(!(obj instanceof Person))//判断对象是否为Person类型
throw new RuntimeException("类型错误");
Person p=(Person)obj;
return this.name.equals(p.name)&&this.age==p.age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
class HashSetDemo178
{
public static void main(String[] args)
{
HashSet hs=new HashSet();
hs.add(new Person("zhangsan",24));
hs.add(new Person("lisi",49));
hs.add(new Person("zhangsan",24));
hs.add(new Person("wangwu",27));
hs.add(new Person("zhangsan",24));
for(Iterator it=hs.iterator();it.hasNext();)
{
Person p=(Person)it.next();
System.out.println("Name="+p.getName()+"\tAge="+p.getAge());
}
}
}运行结果为:
注意:对于判断元素是否存在,以及删除、添加等操作。依赖的方法是hashCode和equals方法。
|----TreeSet:可以对Set集合中的元素进行排序。默认按照字母的自然排序。底层数据结构是二叉树。保证元素唯一性的依据:compareTo方法return 0。
TreeSet排序的第一种方式:让元素自身具备比较性,元素需要实现Compareable接口,覆盖compareTo方法,这种方式也称为元素的自然顺序,或者叫做默认顺序。
示例:按年龄排序 Comparable比较对象与对象之间的关系
import java.util.*;
class Person implements Comparable
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public int compareTo(Object obj)//复写
{
//System.out.println(this.name+"::::::"+this.age);
if(!(obj instanceof Person))
throw new RuntimeException("这不是Person对象");
Person p=(Person)obj;
int num=new Integer(this.age).compareTo(new Integer(p.age));
if(num==0)
return this.name.compareTo(p.name);
return num;
//this.name.equals(p.name)&&this.age==p.age;//这里的equals是String的方法
}
}
class TreeSet1505
{
public static void main(String[] args)
{
TreeSet ts=new TreeSet();//
ts.add(new Person("zhangsan",24));
ts.add(new Person("zisi",49));
ts.add(new Person("lisi",49));
ts.add(new Person("zhangsan",24));
ts.add(new Person("wangwu",27));
ts.add(new Person("zhangsan",24));
for(Iterator it=ts.iterator();it.hasNext();)
{
Person p =(Person)it.next();
System.out.println(p.getName()+"......"+p.getAge());
}
}
}运行结果为:
TreeSet排序的第二种方式:当元素自身不具备比较性时,或者具备的比较性不是所需要的。这时就需要让集合自身具备比较性。在集合初始化时,就有了比较方式——构造方法,即定义一个比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。比较器构造方式:定义一个类,实现Comparator接口,覆盖compare方法。
示例:按字符串长度排序
import java.util.*;
class A implements Comparable
{
public int compareTo(Object obj)
{
return 0;
}
}
class B implements Comparator
{
public int compare(Object obj1,Object obj2)
{
String s1=(String)obj1;
String s2=(String)obj2;
//int num= s1.length()-s2.length();
int num= new Integer(s1.length()).compareTo(new Integer(s2.length()));
if(num==0)//长度相等时,次要条件
return s1.compareTo(s2);
return num;
}
}
class TreeSet1505
{
public static void main(String[] args)
{
TreeSet ts=new TreeSet(new B());//比较器对象作为参数传递给TreeSet集合的构造函数。
ts.add("asjjjsddd");
ts.add("zasdd");
ts.add("vasdddd");
ts.add("sd");
ts.add("asd");
ts.add("dasdd");
for(Iterator it=ts.iterator();it.hasNext();)
{
Object o=it.next();
System.out.println(o);
}
}
}
<span style="font-size:14px;">
</span>运行结果为:
注:当两种排序都存在时,以比较器为主。
三、泛型
在以上示例中发现运行结果中会报的安全问题。JDK1.5版本后出现了新特性——泛型,用于解决这类安全问题,是一个类型安全机制。
好处:
1.将运行时期出现的问题ClassCastException,转移到了编译时期。方便于程序员解决问题。让运行时期问题减少、安全。
2.避免了强制转换的麻烦。如在实现某一个接口时,指定传入接口方法的实参的类型的话,在复写该接口方法时就可以直接使用指定类型,而不需要强制转换。
格式:
通过< >来定义要操作的引用数据类型。
在使用Java提供的对象时,什么时候写泛型呢?
通常在集合框架中常见,只要见到<>就定义泛型,其实<>就是来接收类型的。当使用集合时,将集合中需要存储的数据类型作为参数传递到<>即可。注:只有引用类型才能作为泛型方法的实际参数。
泛型类:当类中要操作的引用数据类型不确定的时候,可定义泛型类。早期定义Object来完成扩展,现在定义泛型来完成扩展。
示例:自定义泛型类
class FanXing<QQ>//泛型类
{
private QQ q;
public void setObject(QQ q)
{
this.q=q;
}
public QQ getObject()
{
return q;
}
}
class Worker
{
private String name;
private int age;
Worker(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
class FanXingDemo1507
{
public static void main(String[] args)
{
FanXing<Worker> fx=new FanXing<Worker>();
fx.setObject(new Worker("zhangsan",12));
Worker wo=fx.getObject();
System.out.println("Name="+wo.getName()+"\tAge="+wo.getAge());
}
}
<span style="font-size:14px;">
</span>运行结果为:
泛型方法:泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类对象明确要操作的具体数据类型后,所有要操作的类型就已经固定了。即泛型类中明确好具体类型后,整个泛型类中的所以方法都是这个类型。
为了让不同方法可以操作不同类型,而且类型不确定,那么可将泛型定义在方法上。
如:class Demo
{
public <T> void show(T t) {}
public <E> void print(E t){}
}
注:静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
如:class Demo<T>//类型参数通常用单个大写字母表示
{
public static<T> void show(T t) {}
}
class Demo
{
public static<E,F> void print(){} //在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开
}
泛型定义在接口上
interface Inter<T>
{
void show(T t);
}
class InterImp<T> implements Inter<T>
{
public void show(T t)
{
System.out.println(t);
}
}
class FanXingDemo1507
{
public static void main(String[] args)
{
Inter<Integer> i=new InterImp<Integer>();
i.show(4);
}
}运行结果为:
泛型限定:
1)?:通配符,也可以理解为占位符。
2)?extends E:可接收E类型或E类型的子类型;称之为上限。
如:ArrayList<? extends Number>x = new ArrayList<Integer>();
3)?super E:可接收E类型或E类型的父类型;称之为下限。
如:ArrayList<? super Integer>x = new ArrayList<Number>();
注:除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}。
四、Map集合
定义:该集合存储的是键值对,且要保证键的唯一性。
常见方法:
1.添加:
put(K key,V value):添加元素,如果出现添加时,相同的键,那么后添加的值会覆盖原有键对应值,并put方法会返回被覆盖的值。
putAll(Map <? extends K,? extends V> m):添加一个集合。
2.删除:
clear():清空
remove(Object key):删除指定键值对
3.判断:
containsValue(Object value):判断值是否存在
containsKey(Object key):判断键是否存在
isEmpty():判断是否为空
4.获取:
get(Object key):通过键获取对应的值,可通过返回值判断键是否存在,如null为不存在。
size():获取集合的长度
Collection<V> value():获取Map集合中所有的值,返回一个Collection集合。
Set<Map.Entry<K,V>> entrySet():将Map中的映射关系存入到Set集合中,而这个关系的数据类型就是Map.Entry
Set<K> keySet():将Map中的所有的键存入到Set集合中,因为Set具备迭代器,所以可以用迭代的方式取出所有的键,再根据get方法,获取每一个键对应的值。
Map常用的子类对象:
|---Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。JDK1.0,效率低。
|---HashMap:底层是哈希表数据结构。允许使用null键null值,该集合是不同步的。JDK1.2,效率高。
|---TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。
注:Set底层就是使用了Map集合。
Map集合取出元素的原理:将Map集合转成Set集合,再通过迭代器取出。
方式一:将Map中的映射关系存入到Set集合中,再通过迭代器取出。使用Set<Map.Entry<K,V>> entrySet()。
示例:
/*需求:
每个学生都有对应的归属地。学生student,地址String
学生属性:姓名,年龄
如姓名和年龄相同视为同一个学生,保证学生的唯一性
思路:
1描述学生
2定义map容器,将学生作为键,地址作为值,存入
3获取map集合中的元素
*/
import java.util.*;
class Student
{
private String name;
private int age;
Student(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public int hashCode()
{
//System.out.println("ff:"+(this.name.hashCode()+age*27)+"::"+(name.hashCode()));
return this.name.hashCode()+age*27;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
//return false;
throw new ClassCastException("类型错误");
Student s=(Student)obj;
//System.out.println("dd:"+s.name);
return this.name.equals(s.name) && (this.age==s.age);
}
}
class MapTest1519
{
public static void main(String[] args)
{
Map<Student,String> mp=new HashMap<Student,String>();
mp.put(new Student("zhangsan",39),"nanjing");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");
mp.put(new Student("zhangsan",39),"shanghai");
mp.put(new Student("wangwu",39),"shenzhen");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");
Set<Map.Entry<Student,String>> ss=mp.entrySet();
Iterator<Map.Entry<Student,String>> it=ss.iterator();
while(it.hasNext())
{
Map.Entry<Student,String> me=it.next();
Student st=me.getKey();
String str=me.getValue();
System.out.println(st.getName()+"::"+st.getAge()+"::::"+str);
}
}
}运行结果为:
方式二:将Map中的所有的键存入到Set集合中,再通过迭代器取出。使用 Set<K> keySet()。
示例:如上示例中的Student类不变,只需改变entrySet的使用。如下:
class MapTest1519
{
public static void main(String[] args)
{
Map<Student,String> mp=new HashMap<Student,String>();
mp.put(new Student("zhangsan",39),"nanjing");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");
mp.put(new Student("zhangsan",39),"shanghai");
mp.put(new Student("wangwu",39),"shenzhen");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");
Set<Student> s=mp.keySet();
Iterator<Student> it=s.iterator();
while(it.hasNext())
{
Student key=it.next();
String value=mp.get(key);
System.out.println(key.getName()+"::"+key.getAge()+"::::"+value);
}
}
}运行结果相同。
注:当量数据之间存在着映射关系的时候,就应该想到使用Map集合。
练习:需求:"sdfgzxcvasdfxcvdf"获取该字符串中字母出现的次数。结果为:a(1)c(2).....
/*需求:"sdfgzxcvasdfxcvdf"获取该字符串中字母出现的次数。
结果为:a(1)c(2).....
思路:1.每一个字母对应一个数,有映射关系,可用map集合
2.将字符串转换成字符数组,因为要对每一个字母进行操作
3.定义一个map集合,因为打印结果的字母顺序,可用treemap集合
4.遍历字符数组,将每个字母作为键去查map集合
如果返回null,将该字母和1存入到map集合中
如果返回不为null,说明该字母已经在map中,并有对应次数
那么就获取该次数并进行自增,然后将该字符和自增后的次数存到map集合中,覆盖原有键对应的值
5.将map集合中的数据变成指定的字符串形式打印
*/
import java.util.*;
class TreeMap1521
{
public static void main(String[] args)
{
//String str="sdfgzxcvasdfxcvdf";
String str="adfzxfxx";
String ss=charCount(str);
System.out.println(ss);
}
public static String charCount(String str)
{
char[] ch=str.toCharArray();
Map<Character,Integer> tm=new TreeMap<Character,Integer>();
for(int i=0;i<ch.length;i++)
{
Integer value=tm.get(ch[i]);//通过键来查找,判断集合中是否包含该键的值
if(value==null)
{
tm.put(ch[i],1);
}
else
{
value+=1;
tm.put(ch[i],value);
}
}
//System.out.println(tm);
StringBuilder sb=new StringBuilder();//用容器来装键值
Set<Character> s=tm.keySet();
Iterator<Character> it=s.iterator();
while(it.hasNext())
{
Character c=it.next();
Integer in=tm.get(c);
sb.append(c+"("+in+")");
//System.out.println(c+"::"+in);
}
//return null;
return sb.toString();
}
}运行结果为:
扩展:以上所讲的是一对一的映射关系。但是在现实生活中有很多是一对多的映射关系,我们可以通过嵌套的形式将多个映射定义到一个大的集合中,再将大的集合分级处理,形成一个体系。
如:一个学校有很多班,一个班中有很多学生。可以定义为:
HashMap<String,HashMap<String,String>> czbk=new HashMap<String,HashMap<String,String>>();//学校
HashMap<String,String> yureban=new HashMap<String,String>();//班级
HashMap<String,String> jiuyeban=new HashMap<String,String>();//班级
czbk.put("yureban",yureban);
czbk.put("jiuyueban",jiuyeban);yureban.put("01","zhangsan");
jiuyeban.put("01","wangwu");