黑马程序员--集合框架体系
来源:互联网 发布:发货单打印软件 编辑:程序博客网 时间:2024/05/17 11:07
集合框架体系
----------------------------------------------------------------------------------------------------------------------
对象多了就需要存储,常见的存储方式有两种:集合类和数组;
数组和集合都是存储对象的容器,他们有什么不同?
1、 就他们的存储容量来讲:数组虽然可以存储对象,但是它在刚开始初始化时就定义的长度,所以容量是
固定的,一旦存储的对象数量超过了其容量,就会发生数组角标越界异常;而集合类的容量时可变化的!
2、就他们的存储类型来看:数组除了可以存储对象,还可以存储基本数据类型,但是容器中存储的对象或者
数据必须是同一类型的;而集合类他只可以存储对象,但是它可以存储不同类型的对象!
集合框架的各个组分:
Collection知识体系
----------------------------------------------------------------------------------------------------------------------
上图中仅仅列出部分常见的集合体系中的容器;
从图中我们可以看见Collection接口下面的有许多的实现子类,那么为什么会产生这么多的子类呢?
因为每个子类他们内部都有存储数据的特有方式---数据结构,所以产生了不同的子类!
集合中实际存储的不是具体的对象实体,而是对象的引用(地址);
---------------------------------------
集合框架的共性方法:
1、增加元素:add(element);----增加一个元素;
2、删除元素:remove(element)----删除一个元素;
clear( )----删除容器中所有的元素,清空集合!
al1.remove( al2 )----删除al1集合中和al2集合相同的元素;
al1.retainAll( al2 )----仅保留al1集合中和al2集合相同的元素(取交集);如
果没有相同元素,那么al1集合就变成空集合;
3、判断元素:al.contains( “java03”)----判断集合中是否包含java03这个元素;
al.isEmpty( )----判断al集合是否是个空集合;
4、获取集合的属性信息:
al.size( )----获取al集合的容量!
------------------------
迭代器:
迭代器其实就是集合取出里边元素的方式;
迭代器的操作格式:
第一步:获取迭代器----Iterator it =al.iterator( );
第二步:通过循环将集合中的元素一一取出:
while(it.hasNext()){ //判断指针的后面是否还有元素,如果有则返回true;
System.out.println( it.next( )); //通过next()获取指针后面的元素;
}
老外一般通过for循环将这两步合成一步来做:
for(Iteratorit = al.iterator( );it.hasNext( ); ){
System.out.println( it.next( )); //通过next()获取指针后面的元素;
} //这样做有一个好处:it这个对象会在for循环结束后释放,节约内存;
--------------------------------------------------
List集合的共性方法:
Collection
|------List: 元素是有序的,可重复,因为该集合体系有角标!
|------Set: 元素是无需的,不可重复的;
List集合中特有的方法:凡事可以操作角标的方法都是该体系特有的方法:
增加元素:add(index,element);
Add(index,collection);
删除元素:remove(index);
修改元素:set(index,element);
查找元素:get(index );
subList(fromIndex,toIndex)-----包含头不包含尾
---------------------------------
列表迭代器:
我们知道在集合框架中已经定义了迭代器,那么为什么还要在List集合中单独再定义一
个列表迭代器呢?
因为在传统的迭代器中可以进行的操作只有hasNext( )、remove( )和next( )三种方法。也
就是说在迭代的时候,除了则三种操作以外,是不可以通过对象来对集合中的元素进行添加,
修改等操作的,否则会发生并发修改异常,所以在迭代时只能使用迭代器的方法来操作元素,
可是传统的迭代器Iterator中方法是有限的,于是在其子类接口就进行了增强,因此List体
系中就产生了一个列表迭代器来弥补传统迭代器的不足;
那么在List集合体系中如何获取列表迭代器呢?
我们可以通过List集合对象调用体系中的listIterator( )成员方法来方法获取列表迭代器;
listIterator除了具有传统迭代器所具备的功能以外,还可以对集合中的元素在迭代的同时进
行增加、修改。此外他还有一个比较特殊的功能:反向迭代集合中的元素,这主要是通过:
hasPrevious( )---查看指针前面是否还有元素; previous( )---取出指针前面的那一个元素;
这两个方法来完成的,这就是传统迭代器迭代的反向运行;
-------------------------------
List集合的具体对象的特点:
List---|---ArrayList:底层数据结构是数组结构(JDK1.2出现)
|---LinkedList:底层使用的是链表结构(JDK1.2出现)
|---Vector:底层是由的数据结构是数组结构(JDK1.0出现)
由于Vector是线程同步的,虽然安全但是效率太低,所以在JDK1.2中被ArrayList取代。
ArrayList特点:查询速度快,但是增删慢-------线程不同步(虽然不安全,但是效率高);
LinkedList特点:增删速度快,但是查询速度慢-------线程不同步。
可变长度数组:
ArrayList:默认长度是10,当容量不够时,自动以50%延长;
Vector:默认长度是10,当容量不够时,自动以100%延长;
------------------------------
Vector的枚举------Vector的特有元素取出方式!
查看API文档,我们可以发现Collection集合体系是在JDK1.2时才建立的,而Vector可是
开国元勋,他在JDK1.0就已经有了,所以它里面的有些方法是早就存在的,比如他自己特
有的迭代方式-----Enumeration(枚举)。
代码演示:
class VectorDemo{
Vector v = new Vector( );
v.add(“java01” );
v.add(“java02” );
v.add(“java03” );
Enumeration en = v.elements( ); //获取枚举
while(en.hasMoreElements()){ //判断指针后面是否还有元素;
System.out.println(“en.nextElements”); //获取指针后面的元素;
}
}
上面演示代码中红色部分就是Vector集合取出集合中元素的特有迭代方式;我们可以发
现:其实它的形式和Iterator很相似,只不过书写起来比较麻烦!
-----------------------------------------------------
LinkedList集合:
特有方法:addFirst( )、addLast( )、getFirst()、getLast( )、removeFirst( )、removeLast( )
这些方法都是在特殊的位置对元素进行操作。需要特别注意的是:remove( )不仅删除的元素,同时它的
返回值是;元素类型,也就是说在删除的同时获取到了元素;
如果集合中没有元素,那么使用get( )或者remove( )方法,会抛出异常!但是在JDK6.0中开始,出现
了pollFist( ) ---获取并移除集合的第一个元素;如果集合为空,则返回null;pollLst()---获取并移除集合
的最后一个元素;如果集合为空,则返回null。
下面列举JDK6.0中相关的替代方法;
原方法 ------------------------替代方法
addFirst( ) -------------------- offerFirst()
addLast( ) -------------------- offerLast( )
getFirst( ) -------------------- peekFirst( )
getLast( ) -------------------- peekLast( )
removeFirst( ) ---------------- pollFirst( )
removeLast( ) ---------------- pollLast( )
LinkedList练习:使用LinkedList模拟一个堆栈或者队列数据结构
堆栈:先进后出;
队列:先进先出;
代码演示:
class Queue{private LinkedList link;Queue(){ link = new LinkedList(); } public void myAdd(Object obj){ link.addFirst(obj); //将添加的数据往堆栈的下面添加; } public Object myGet( ){ return link.removeLast(); //取出堆栈上面的数据; } public boolean isNull(){ return link.isEmpty( ); }}//通过LinkedList集合特有的添加和取出数据的方法达到让数据先进后出-----模拟堆栈!class LinkedListDemo{public static void main(String[ ] args){ Queue q = new Queue( ); q.myAdd(“java01”); q.myAdd(“java02”); q.myAdd(“java03”); q.myAdd(“java04”); while(!q.isNull()){ System.out.println(q.myGet()); } }}
-------------------------------------------------------------------
ArrayList练习1:除去ArrayList集合中重复的元素
思路:新建一个容器,将原来容器中的元素迭代取出,然后通过一个筛选过滤装置,将不符合要求的元素
滤去,这样就可以得到一个没有重复元素的新的ArrayList集合;
在这里可以通过contain(obj)方法来达到过滤的作用:
演示代码:
class ArrayListTest1{public static void main(String[] args ){ ArrayList al = new ArrayList(); al .add(“java01”); al .add(“java02”); al .add(“java01”); al .add(“java03”); al .add(“java02”); } public static ArrayList singleElement(ArrayList al){ ArrayList newAl = new ArrayList( ); //定义一个新的容器来装不重复元素 Iterator it = al.iterator( ); while(it.hasNext( )){ Object obj = it.next( ); if(!newAl.contains(obj)){ //通过contains()方法来过滤重复元素! newAl.add(obj); } } }}
-----------------------------------------------------------
ArrayList集合练习2:
/*将自定义的对象作为元素存到ArrayList集合中去,并除去重复元素;例如存人作为对象,同姓名同年龄
为同一个人,视作重复元素*/
分析:
本题和上一题目是很类似的,只不过上一题中判断元素重复与否是由电脑定义的条件来判断的,而
本题中判断元素是否重复是由我们来定义;
那么我们如何来定义元素是否重复呢?
对于List集合判断元素是否重复实际上用到的是对象的equals( ),所以我们在定义对象时只需要复写其中的
equals( )方法,那么集合使用contains( )方法时,就可以根据返回值是true还是false来判断元素是否重复!
代码演示:
class Person{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){if(!(obj instanceOf Person)) return false;Person p = (Person)obj;return this .name.equals(p.name)&&this.age==p.age;//名字和年龄相同为同一个人}}class ArrayListTest2{public static void sop(Object obj){System.out.println(obj);}public static void main(String[] args){ArrayList al = new ArrayList( ) ;al.add(new Person(“lisi01”,30));al.add(new Person(“lisi02”,25));al.add(new Person(“lisi01”,19));al.add(new Person(“lisi03”,23));al.add(new Person(“lisi02”,25)); //此为一个重复元素,但是仍然存储成功了,因为ArrayList中有角标;al.add(new Person(“lisi04”,31));al = singleElement( al ); //将重复元素去除,得到一个新集合}public static ArrayList singleElement(ArrayList al){ArrayList newAl = new ArrayList( ); //定义一个新的容器来装不重复元素Iterator it = al.iterator( );while(it.hasNext( )){Object obj = it.next( );if(!newAl.contains(obj)){ //通过contains()方法来过滤重复元素!实际 //上该方法调用了对象身上的equals( )! newAl.add(obj);}}}}
说明一点:其实不仅仅是此处的contains()方法在底层调用了对象身上的equals( ),还有很多方法在被调用时,
也用到了对象身上的equals(),比如remove( )!
----------------------------------------------
Set集合体系:
Set集合的特点:元素是无序的(存入和取出的顺序不一致)而且元素是不可以重复的!
它的两个常见实现类:
|---HashSet:底层数据结构是哈希表;
|---TreeSet:底层数据结构是二叉树;
HashSet存储自定义对象:为了保证元素的唯一性,HashSet通过元素身上的hashCode()
和equals( )来判断元素是否重复;
集合会先自动调用元素的hashCode( )查看他们的哈希地址值是否相同,如果不同说明两个元素不是同
一个;如果相同,还会继续通过equals()来判断内容是否相同,如果相同,就认为两个元素是同一个对象,
如果不同,就认为两个元素不是同一个对象;
其实HashSet的判断和删除的依据就是hashCode()和equals( )方法!
这里的hashCode()的作用的是提高了集合的存取效率,假如一个HashSet集合中有一万的元素,如果
没有hashCode(),那么每次存入时,都需要先看已经存在的这一万个元素中有没有已经存在要存入的元素,
如果有,就不存入,这样多次的判断,很消耗时间,所以使用hashCode()方法,可以将这一万个元素根据
自己身上的成员算出一个hash值,然后分在不同的hash域中,每次根据元素的hash值到相应的hash域中
去查找,可以减少判断次数,提高存取效率!
TreeSet集合:它的数据结构是二叉树(又叫红黑树)
该集合的特点是:可以对集合中的元素进行排序:
默认的排序规则是ASCII码表,小的在前面,大的在后面!
除了默认的排序规则以外,我们还可以通过以下两种方式自定义排序规则!
方式一:让元素对象实现Comparable接口并复写其中的compareTo( )!
代码演示:往TreeSet集合中存储自定义的对象,想按照学士的年龄来进行排序:
class Student implements Comparable{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 compareTo(Object obj){ //复写Comparable接口中的compareTo()if(!(obj instanceOf Student)){throw new RuntimeException();}Student s = (Student)obj;if(this.age >s.age){ //比较首要条件!return 1;}if(this.age == s.age){return this.name.compareTo(s.name); //比较次要条件!}if(this.age<s.age){return -1;}}} //这种方式是将比较方式定义在了元素的身上!
方式二:将比较方式定义在集合上面,让集合具备比较性!也就是在集合的构造方法中传入一个比较器!
这样可以解决掉元素本身不具备比较性,或者元素所具备的比较性不是我们想要的情况!也就是
说这种定义的比较方式的权限要大于元素自身的比较性。
当两种比较方式同时存在时,以第二种放方式为准!
代码演示:class MyCompare implements Comparator{ //定义了一个比较器!public int compare(Object obj1 , Object obj2){Student s1 = (Student )obj1;Student s2 = (Student )obj2;int num = s1.getName().compareTo(s2.getName()); //主要比较条件if(num == 0){return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge())) //次要比较条件!}return num ;}}class Student implements{ //该对象不具备比较性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;}}class TreeSetDemo2 {public static void main(String[] args) {TreeSet ts = new TreeSet(new MyComparator); //定义一个TreeSet集合,并向集合传入一个比较器ts.add(new Student(“lisi02”,22)); ts.add(new Student(“lisi007”,20));ts.add(new Student(“lisi09”,19));ts.add(new Student(“lisi06”,18));ts.add(new Student(“lisi06”,18));ts.add(new Student(“lisi07”,29));//在存入元素时,集合会自动的调用比较器,来判断元素是否已经存在了!Iterator it = ts.iterator();while(it.hasNext()){Student stu = (Student)it.next();System.out.println(stu.getName()+"..."+stu.getAge());}}}
泛型知识体系
---------------------------------------------------------------------------------------------------------------------
初级篇(毕老师主讲):
泛型是JDK1.5以后才出现的新特性,用于解决集合的安全问题,是一种安全机制:它可以将运行时期
出现的ClassCastException转移到编译时期就发现,方便程序员解决问题,减少运行时期的问题,
同时它还可以避免强制类型转换的麻烦!
通过前面对于集合的学习我们知道了集合这种容器可以添加的元素可以是各种类型的,就像《阿甘正传》
里面所说的:“人生就像一盒巧克力,你永远不知道会尝到哪种滋味”.因为在美国巧克力通常有十二块或二十四
块,每个都有不同的包装和口味形状和颜色,以前没有标志。只能拆开放在嘴里,品尝了之后才知道个中滋味
以前我们所使用的没有泛型的集合就像装着各式各样巧克力的盒子。而现在使用泛型,就相当于给巧克力盒子
上面贴了一个标签:告诉我们里面装的是清一色某某口味的巧克力而且必须是清一色某某口味巧克力!
代码演示:
class GenericDemo{public static void main(){ArrayList<String> al = new ArrayList<String>(); //使用泛型,限定了ArrayList集合中只能装String类型的元素!al.add(“abc01”);al.add(“abc05”);al.add(“abc07”);al.add(“abc02”);Iterator<String> it = al.iterator(); //限定了迭代器中的数据类型,避免的强转while(it.hasNext( )){String s = it.next( ); //不需要强制转换,取出的数据类型很明确!Systerm.out.println(s);}}}
泛型使用:
泛型格式:通常使用“< 引用数据类型 >”来定义要操作的引用数据类型!
在这里用红色下划线标明是引用数据类型,就是着重强调:泛型里面的说类型必须是引用数据类型,
千万不可以是基本数据类型!
泛型在集合框架中很常见:只要见到< >就要定义泛型!
---泛型类:
当类中要操作的引用数据类型不明确时,早起我们通过Object来完成,现在我们通过定义泛型来完成扩展!
那么现在通过定义泛型来完成扩展有什么好处呢?
我们先看下面这个例子:
代码演示:
ha
class Tool{ //早期我们使用Object来完成类型扩展private Object obj;public void setObject(Object obj){this.obj = obj;}public Object getObject(){return obj;}}class Utils<QQ>{ //现在我们使用泛型来完成类型的扩展!private QQ q;public void setObject(QQ q){this.q = q;}public QQ getObject(){return q;}}class Test{public static void main(String[] args){Tool t = new Tool();t.setObject(new Student());Student s1 = (Student)t.getObject(); //使用强制类型转换来得到Student对象Utils<Student> u = new Utils<Student>();u.setObject(new Student());Student s2 = u.getObject(); //不需要使用强制类型转化!}}
从这里我们可以看出来,对于泛型类它可以接受各种引用数据类型,一旦在对泛型类初始化时指定了传入
的类型,那么在整个类中所有通过泛型类型变量T代替的类型就是初始化时指定的类型,这样一来我们在使用
类型时就可以清楚明白的知道数据类型,可以避免强转,减少类型转换异常的发生!
--------------------------------
泛型方法:为了让不同的方法可以操作不同的类型,而且类型还不确定,那么就可以将泛型定义在方法上;
格式为:权限修饰符 <T> 返回值 函数名(T t){}
泛型方法定义的泛型在整个方法中有效!
--------------------------------
静态泛型方法:
泛型类和泛型方法是可以共存的,即:可以在泛型类中定义泛型方法,二者不矛盾!
特殊之处:静态方法是不可以访问类上定义的泛型!
因为静态只能访问静态,但是类上泛型是在对象建立以后才明确的。
如果静态方法访问的数据类型不能确定,可以单独定义一个泛型方法!
class Demo<T>{
public void show(T t){ //他可不是一个泛型方法!
System.out.println(“show”+t);}
public static <W> void println( W w){ //单独定义一个泛型方法!
System.out.println(“print”+t);
}
}
要注意静态泛型方法中泛型类型定义的具体位置,不要写错了,都则编译器会报错。
--------------------------------
泛型接口:
interface Inter<T>{ //定义了一个泛型接口
void show(T t);
}
/*
class InterImpl implements Inter<String> //形式一:子类实现时直接传入了类型
{
public voidshow(String t)
{
System.out.println("show :"+t);
}
}
*/
class InterImpl<T> implements Inter<T>{ //形式二:子类在传入时没有制定类型
publicvoid show(T t)
{
System.out.println("show:"+t);
}
}
class GenericDemo5 {
public static voidmain(String[] args) {
//InterImpl i = new InterImpl();
//i.show("haha"); //由于形势一中指定了泛型类型,所以只能传入String类型
InterImpl<Integer> i = newInterImpl<Integer>();
//在对子类对象初始化时传入泛型类型为Interger;
i.show(4); //所以这里传入的数据类型只能是Iteger.
}
}
从这里我们明白了见到泛型的内容,在使用时需要往里面传入参数!
------------------------------
泛型限定:
通配符:? 代表任何类型的符号:T
二者的区别在于:使用T可以作为一个具体类型来接收一个对象,而使用通配符则不行!
同时有一点必须明确: 使用泛型将不可以使用具体对象的特有方法,比如length( ),因为万一你传进来一个
Integer对象,然后调用Length( ),那是执行不了的,所以为了提高安全性,不可以在泛型中使用特有方法!
----泛型提高了程序数据类型的扩展性,但是不可以使用特有的方法!
? extends E:可以接收E类型及其子类型,向上限定;
? super E: 可以接受E类型及其父类型,向下限定;
---------------------------------------------------
高级篇(张老师主讲)
泛型是JDK1.5中的新特性,而且被公认是这诸多新特性中最难理解的!
前面我们学泛型初级篇时,说过使用泛型可以避免强制类型转换,因为他在一开始就确定了数据类型,那
么装进去的对象必须是那种类型,而取出来的对象必然也是那种类型,我们在使用时,类型很明确。而如果不
使用泛型,那么会出现这样的情况:接受的数据类型是Object类型,我们实际上传进去了一个String类型的对
象,而我们取出来时却将其强制转换为Integer类型,当然会发生类型转换异常---ClassCastException。
张老师有这样一句话来说明强制类型转换:强制类型转换就是JVM在编译时告诉他,我知道我要得到的
数据类型就是我要强转的类型,所以你就不要不要管了,出了错我负责!
这样有可能会发生明明那个数字不是整数,你偏偏把他转换成整数,存在安全隐患!
------------------
泛型的内部原理及更深应用:
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,
编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响。也就是泛型只在编译时期
有效,等编译以后在字节码中不会存在泛型类型,这叫做去类型化!因此可以通过反射来获取集合的字节码,
然后再通过add()方法向集合中添加元素时,是不受之前定义的泛型类型的限制的,是可以向集合中添加
其他类型的元素的。
代码演示:
//定义了一个只能添加String类型的集合
ArrayList< String > al1 = new ArrayList< String >();
ArrayList< Integer> al2 = new ArrayList< Integer >();
System.out.println(al1.getClass()== al2.getClass()); //结果为true,说明的确去类型化了
al2.getClass().getMethod(“add”,Object).invoke(al2, “abc”) //通过反射向al2集合中添加
//一个String类型的元素,注意al2本来定义的只能添加Integr类型
System.out.println(al2.get(0)); //输出结果为abc----说明添加成功
--------------------------------
泛型的专用术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
--------------------------------
参数化类型与原始类型的兼容性:---------承前启后
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
Collection<String> c = new Vector();
这是因为新的集合要能容纳老的集合,用jdk1.5的新集合来接受一个jdk1.5之前的方
法返回来的不带参数化的集合!------承前
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
Collection c = new Vector<String>();
因为用原来的方法接受一个集合参数,新的参数化类型也要能传进去----启后
----------------------------------
参数化类型不考虑类型参数的继承关系:
情况一:Vector<String> v = newVector<Object>(); //错误!
情况二:Vector<Object> v = newVector<String>(); //也错误!
我们可以这样理解:Vector<String>是一个清一色的炮兵团,而Vector<Object>是一个独立团,
里边有步兵营,炮兵营,装甲营等等!对于第一种情况:我要一个炮兵团,以满足强大的火力需求,
而他却给我了一个独立团,肯定满足不了需求,所以我就不愿意了!第二种情况:我要一个独立团,
以获得均衡的作战能力,他却给我了一个清一色的炮兵团,这也让我无法完成需求,所以我还是不愿意!
对于下面这段代码会报错吗?
Vector v1 = new Vector<String>(); //不会报错,因为参数化类型与原始类型的兼容性
Vector<Object> v = v1; //也不会报错,因为编译器是一行一行的编译,在本行中他认//为v1是原始类型,
//而不是String类型,所以编译会通过
----------------------------------
在创建数组实例时,数组元素不能使用参数化类型:
Vector<Integer>[] vectorList = new Vector<Integer>[10]; //编译不会通过!
这是为什么呢?请看下面这段SUN公司的代码:
List<String>[ ] lsa = new List<String>[10]; //这是不允许的,假设它可以通过编译器!
Object o = lsa;
Object[ ] oa = (Object[ ]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;
String s = lsa[1].get(0); //将会发生类型转化异常!
分析:从上面的代码我们可以看出:即使定义一个数组,并且里面的元素是参数化类型的,
但是我们可以通过一系列的代码向数组中传入不符合要求的其他类型的元素(并未用到反射去添加),从而
导致出现了安全隐患,为了避免这种安全隐患,所以编译器直接规定数组元素不可以使用参数化类型!
---------------------------------
泛型通配符的扩展应用:
通配符:? 它的作用是代表具体传进来的对象身上的类型;
Collection<Object> col1 = new Collection<String>( ); //编译会报错!
Collection< ? > col2 = new Collection<String>(); //编译会通过!
在此使用通配符?,那么?代表的具体类型就是String!
虽然?可以表示成任意引用数据类型,但是它只可以代表一次,所以Collection<?>是一个类型虽然不明确
但是里边元素是清一色相同元素的集合;而Collection<Object>是一个类型不明确,同时里边元素类型是各式
各样的集合,所以他不可以接受一个清一色String类型元素的集合,只要他接受了,编译器就报错!-----------
------Object和?的区别!
总结:使用通配符可以引用各种参数类型,通配符定义的变量主要用作引用;但是需要注意一点:
使用通配符只能调用与参数化无关的方法,不可以调用与参数化有关的方法。举个例子:我们调用传进来来的
对象身上特有的方法:比如length( ),如果我们传进来的对象是具有length( )方法的对象,比如String的实例
对象,那么是没有问题的,但是如果我们传进来一个Integer对象,那么调用length( ),岂不是把Integer对象
为难住了?所以为了避免这种安全隐患,不允许在使用通配符时调用与参数化有关的方法!
泛型限定:
向上限定:? extendsE:可以接收E类型及其子类型
向下限定:?super E : 可以接受E类型及其父类型
我们来分析下面这个代码是否有问题:
Class<String> x = Class.forName(“java.lang.String”); //编译器会报错!
因为等号后面运行的返回值是“?”,而将?传给“String”是不可以的!而反过来:
Class<?> y;
Class<String> x;
y = x; //这是可以的!
----------------------------------------------------------
自定义泛型及其使用:
a. 类型推断:
<T> T add(T x,T y){ //自定义了一个泛型方法;
return null
} --------------伪代码
number n = 对象.add(5,3.5); //在这里传入的参数类型分别是Integer和Float(自
//动装箱功能),然后推断的结果类型是number
b. 泛型中传入的参数化类型必须是应用数据类型:
先看这样一个例子:交换数组中的两个元素的位置的泛型方法语法定义如下:
static <E> void swap(E[] a, int i, int j) { //定义一个静态泛型方法;
Et = a[i];
a[i]= a[j];
a[j]= t;
} --------------伪代码
类名.swap(new int[]{1,2,4,6,3,2},1,2); //此时程序会报错,因为int是基本数据类
//型,他是不可以作为泛型的实际参数类型的
c. 除了在应用泛型时会使用extends限定符,在定义泛型时也可以使用泛型限定符:
如:public <A extendsAnotation> A getAnotation(Class(A) annotationClass){}
本来A可以是任意类型的,但是加上了extends Anotation以后,就限定了A是Anotation的子类!
除此而外还可以通过&符号来为泛型指定多个边界:
<V extends Serializable &Cloneable> void method(V v){ }
d. 可以用类型变量来表示异常----参数化的异常:
private static <T extends Exception> sayHello() throw T{
try{
代码;
}catch(Exception e){
throw (T)e;
}
}
需要说明的一点是:这个参数化的异常不能放在catch块的捕捉代码中!
e. 在泛型中可以同时定义多个类型参数,并且中间需要用逗号隔开;
比如map中的<K,V>的情况!
-----------------------------------------
自定义泛型类的应用:
dao:date(数据) access(访问)object(对象)
对于自定义泛型类需要注意两点:
一是泛型参数的实际参数化类型必须是应用数据类型;
二是在泛型类中静态泛型方法是不可以访问类级别的类型参数!
Map集合体系:
----------------------------------------------------------------------------------------------------------------------
Map集合的常见子类:
|---HashTable:底层数据结构是哈希表,不可以存入null键和null值;(线程同步)
|---HashMap:底层数据结构是哈希表,可以存入null键和null值;(线程不同步)
|---TreeMap:底层数据结构是二叉树结构,给键排序!(线程不同步)
发现Set集合和Map集合很像,其实Set集合在底层就是调用Map集合的方法!
Map集合的基本特点:
该集合存储键值对,是一对一对的往里边存,而且要保证键的唯一性;
Map集合的常用方法:
1、 添加:put(k,v)-----返回的是键对应的原来的value!
putAll(Map<? extends K,? extends V> m)
2、 删除:containsKey():判断是否包含某个键
remove(Object key) :将键对应的值从集合中删除,返回值是值,
如果值不存在,这返回null;
clear() :移除Map集合中的所有映射关系;
3、 判断: containsValue():判断是否包含某个键
isEmpty()
4、 获取:get(Object key):返回指定键说映射的值;
size( ):获取映射关系数
values( ):返回包含值的Collection集合!
entrySet( ):返回此映射中包含的映射关系的 Set 视图
keySet( ):返回此映射中包含的键的Set 视图。
Map集合中元素的两种取出方式:
方式一:通过keySet( )取出:将Map集合中所有的键存入Set集合,因为Set集合具备
迭代器,所以可以取出所有的键,然后再通过map集合的get()方法将值取出。
演示代码:
Set<String>keySet = map.keySet();
//只取出map集合的键那一列组成一个Set集合
Iterator<String> it =keySet.iterator(); //得到Set集合的迭代器;
While(it.hasNext()){
Stringkey = it.next(); //取出键
Stringvalue = map.get(key); //通过已经获得的键获取相应的值
System.out.println(“key:”+key+“value:”+value);
}
方式二:将map集合中的一对键值对作为一个整体,这个整体叫做“entry”,然后将这些
entry存储到一个Set集合中(此时集合中的元素的数据类型是Map.entry),再然
后通过Set集合的迭代器取出一个个的entry,通过entry上的getKey()和
getValue()将键和值取出!
代码演示:
Set<Map.Entry<String,String>> entrySet = Map.entrySet( );
//获取由键和值整体组成的元素(entry)的Set集合
Iterator< Map.Entry<String,String>> it = entrySet.iterator(); //获取迭代器
while(it.hasNext()){
Map.Entry<String,String> me = it.next(); //获取entry
String key = me.getKey(); //通过entry获取key
String value = me.getValue(); //通过entry获取value
}
说明一下:这里的Entry他其实也是一个接口,他是Map集合中的内部接口;
并且内部接口必须被static关键字修饰!
这两种取出方式实际上都是:先将map集合转换成Set集合,然后通过Set集合的
迭代器将元素取出!
集合框架工具类---Collections
---------------------------------------------------------------------------------------------------------------------
该类是一个对集合框架中一些没有的功能进行加强和补充的一个工具类,里面的方法
全部是静态方法!
比如:我们知道,List集合是不具备对里面的元素进行排序的,但是通过这个集合框架工具类便可以完成
这个需求,即通过Collections.sort( )便可以给集合中的元素按照自然顺序排序,此外还可以通过向该方法
中传入比较器,来完成按照我们希望的方式排序!
Collections工具类常见方法:
排序:
sort(List<T> list) :给list集合按照元素的自然顺序排序;
sort(List<T>list, Comparator<? super T> c):给list集合按照传入的比较器顺序进行排序;
获取最值:
max(Collection<? extends T> coll)
根据元素的自然顺序,返回给定 collection 的最大元素。
max(Collection<? extends T> coll,Comparator<? super T> comp)
根据指定比较器产生的顺序,返回给定collection 的最大元素。
min(Collection<? extendsT> coll)
根据元素的自然顺序 返回给定 collection 的最小元素。
min(Collection<? extendsT> coll, Comparator<? super T> comp)
根据指定比较器产生的顺序,返回给定 collection 的最小元素。
二分查找---binarySeacher:
进行二分查找必须保证元素具备可比较性,从而使得元素是有序排列!
int index = Collections.binarySearch(list,“aaa”); //不存在时返回负数!
如果a存在,这返回他在list集合中的角标,如果不存在则返回:-(插入点)-1;
此处插入点的意思是;若这个元素存在,他所应该存在的位置,称此位置为插入点!
替换:
Collections.fill(list,“pp”):将集合中所有的元素全部替换为“pp”;
Collections.fill(list,“aaa”,“pp”):将集合中所有的“aaa”元素全部替换为“pp”;
反转:
Collections.reverse(list):将集合中所有的元素的顺序进行大翻转,头变尾,尾变头;
Collections.reverseOrder( 比较器对象 ):将比较器定义的比较规则进行翻转!
交换:
swap(List<?> list,int 1,int2) :将集合中的指定位置元素位置互换;
洗牌:
shuffle(List<?> list) shuffle(List<?> list, Randomrnd)
随机将集合中的元素进行排序!
同步:通过静态的synchronizedXxx()方法将集合由不同步集合变成同步集合!
操作数组的工具类---Arrays
---------------------------------------------------------------------------------------------------------------------
该工具类里边的方法也全部是静态方法
常用方法:
toString(数组对象):将数组内容以字符串的形式表现出来,括在[ ]中;
asList( ):把数组变成List集合
这样的好处:是可以使用集合的思想和方法来操作数组中的元素;
需要注意:
(1)不可以使用集合的增删方法,因为数组的长度是固定的!
(2)如果数组中的元素都是对象,那么在变成集合时,数组中的元素就直接变成了集合中的元素;如果
数组中的元素是基本数据类型,那么编译器会将数组整体作为集合中的一个元素存在。
既然有了将数组变成集合的方法,那么有没方法可以将集合变成数组呢?答案是肯定的!
我们可以通过Collection中toArray(new数组[长度])方法将一个集合变成一个数组!这样可以限制我们对集合
中元素的增删操作!但是我们知道数组的长度是不可以变化的,已经初始化就确定的!所以如果我们为了能
满足数组可以装下集合中所有的元素,需要定义一个多大容量的数组呢?当然是一个大小和集合容量刚刚一
致的数组最合理!我们可以通过new 数组[集合.size()]来做到这一点。
实际上即使我们创建的来容纳集合元素的数组和集合的容量不一致也没有关系:
当指定类型的数组长度小于了集合的size,那么方法内部会创建一个长度刚刚为集合.size的数组,当当指定类
型的数组长度大于了集合的size,那么数组就可以装的下所有的集合中的元素,那么就不会再创建新的数组了!
------------------------
增强for循环------ JDK1.5新特性!
格式为: for(数据类型 变量名 :被遍历的集合或者数组){}
通过增强for循环对集合进行遍历,只能获取元素,而不可以对集合中的元素进行操作,比如删除元素;
而是由迭代器对集合中的元素进行迭代不仅可以获取元素,还可以删除元素,甚至是由列表迭代器ListIterator
可以对元素进行增删改查!-----所以说for循环虽然简化了代码,但是它具有局限性!
此外高级for循环与传统的for循环相比较,他还有一个局限:那就是高级for循环必须有一个遍历目标,如
果你让他打印一百次hello world ,他就无法完成!
我们建议在遍历数组时还是使用传统for循环,因为它可以定义角标!
-------------------------------------------------
可变参数---- JDK1.5新特性!
可变参数实际上就是之前学习的数组的简写形式,不用每一次自己手动创建数组对象,只要将要操作的元
素作为参数传递,他就会自动将参数封装成数组;
格式:public static void show(int…arr){} //红色代码就是可变参数
在使用可变参数时要注意,可变参数一定要放在参数列表的最后边!
-----------------------------------------------------
静态导入---staticImport
在使用集合框架的工具类时,我们发现里边的方法全部是静态的,每次调用都要用类名调用太麻烦了,为了方
便书写,在导入包是,使用静态导入,可以简化书写代码,不用每次都写类名,直接使用静态方法就可以了!
如何静态导入呢?
和导入很类似,只需要在导入区域写上:import static 工具类 就可以了!
不过要注意两点:
1、当导入的类名重名时,要指明具体的包;
2、当方法重名时,要指明方法所属的类或者对象!
- 黑马程序员--集合框架体系
- 黑马程序员-集合体系
- 黑马程序员 集合体系
- 黑马程序员——集合框架1:体系框架
- 黑马程序员--集合框架:List、Set和Map体系
- 黑马程序员-集合体系汇总
- 黑马程序员_9_集合体系
- 黑马程序员:集合框架
- 黑马程序员-集合框架
- 黑马程序员-集合框架
- 黑马程序员:集合框架
- 黑马程序员-----集合框架
- 黑马程序员: 集合框架
- 黑马程序员-集合框架
- 黑马程序员--集合框架
- 黑马程序员- 集合框架
- 黑马程序员-集合框架
- 黑马程序员--------集合框架
- android下载网络图片并缓存
- 自己动手写操作系统 第八章 :进程间通信 IPC
- java设计模式之装饰者模式Decorator
- [黑马程序员]TreeMap练习
- Android开发把项目打包成apk
- 黑马程序员--集合框架体系
- 黑马程序员-阻塞队列
- struts2 json 输出日期格式不正确
- Android Gallery3D源码分析
- 字典树Trie
- Android安装包制作签名Android安装包制作签名
- linux下memcached安装和配置
- Eclipse中使用代码折叠插件
- C/C++ 结构体字节对齐详解