集合(其三)
八.泛型
在JDK1.5中,使用泛型技术来接收类中要操作的引用数据类型,并用于解决数据的安全问题,是一个安全机制。当类中操作的引用数据类型不确定的时候,就可以使用泛型类。
泛型的优点:
① 将运行时期的问题ClassCastException转到了编译时期;
② 避免了强制转换时的麻烦。
泛型的使用:通过<>来定义要操作的引用数据类型,通常在集合框架中比较常见,只要见到<>就要定义泛型。泛型类的使用:当类中要操作的引用数据类型不确定时,可以定义泛型扩展,早期是通过定义Object来完成扩展。当操作的引用数据类型不确定时,就可以使用<泛型>,将要操作的引用数据类型传入<>中即可。<>其实就是一个用于接收具体引用数据类型的参数范围。
泛型技术是编译器使用的技术,用于编译时期,是用来确保类型安全的安全机制。运行时,为了兼容运行的类加载器,编译器会将泛型去掉,生成的class文件中不带泛型,这就是所谓的泛型擦除。与此同时,在运行时,还将通过元素的类型自动进行转换动作,不需要使用者再进行强制转换操作了,这就是所谓的泛型补偿机制。
注意:
① Object类中的equals(Object obj)方法是由Object定义,所有继承它的类都不可以修改该方法中参数的类型,且在进行强制转换时必须进行健壮性判断。
② 泛型<>中不可以写入基本数据类型,只能够写入引用数据类型。(int[]可以写入)
1.自定义泛型类
代码演示:
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;">//由于是自定义泛型类,所以<>中的可以写入任意字母,不过要注意要与类中代码相对应
public class GenericityDemo<C> {
private C q;
public C getObject() {
return q;
}
public void setObject(C object) {
this.q = object;
}
}</span></strong></span>
2.自定义泛型方法
代码演示:
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"> //自定义泛型方法时需要在返回值之前进行标识
public <W> void show(W str){
System.out.println("show : "+str.toString());
}</span></strong></span>
泛型需要对象来明确,但是静态方法不需要对象来调用。当方法为静态时,将不能访问类上定义的泛型。如果要在静态方法上使用泛型,就只能将泛型定义在方法上,例如:(关联上边的自定义泛型类)
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"> public static <Y> void method(Y obj){
System.out.println(obj) ;
}</span></strong></span>
注意:
一旦使用泛型,变量类型不确定,不能使用具体对象的方法。但是能够使用Object类中定义的方法。
3.自定义泛型接口
代码演示:
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-size: 14px;"><span style="font-family:KaiTi_GB2312;">//自定义泛型接口,将泛型定义在接口上。
interface Inter<T>{
public void show(T t);
}
//如果实现它的类的数据类型还是不确定,可以继续使用泛型进行定义
class InterImpl2<Q> implements Inter<Q>{
public void show(Q q){
System.out.println("show :"+q);
}
}
//如果实现他的类的数据类型已经确定,则可以直接将已确定的引用数据类型写入泛型中
class InterImpl implements Inter<String>{
public void show(String str){
System.out.println("show :"+str);
}
</span><span style="font-size:14px;">}</span></span></strong></span>
4.泛型上限
当无法确定泛型的具体类型时,可以使用<?>来表示。
泛型上限的表现形式:
<? extends 父类(上限)> ---- 此定义方式只接收父类或其子类的引用数据类型
一般存储元素是使用泛型的上限,因为这样取出的都是按照上限类型来运算的,就不会出现类型安全隐患。可以参考理解Collection类中的addAll(Collection<? extends E> c)方法,该方法所传入的参数类型即使用了泛型的上限。
5.泛型下限
泛型下限的表现形式:
<? super E> ---- 此定义方式只接受E类型或者是E的父类型的引用数据类型
通常对集合中的元素进行取出操作时,使用泛型的下限。可以参考理解TreeSet集合中的构造方法:TreeSet(Comparator<? super E> comparator),该方法是构造出一个新的空TreeSet集合,并根据指定的比较其进行比较。
注意:
涉及到equals(Object obj)/containsAll()/removeAll()方法时,由于都引用到了Object类中定义的equals(Object obj)方法。因为不可改变Object类中定义的equals(Object obj)方法的参数类型,所以当类型不确定时,可以在参数传递定义<?>来进行类型表示。
九.集合框架工具类
1.Collections
Collections是集合框架中的工具类,该类中的方法都是静态的。
(1)sort(List<T> list)
原理演示:
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"> //所传入的数据类型需要使用到自然排序,所以其泛型要继承Comparable
public static <T extends Comparable<? super T>> void mySort(List<T> list) {
//使用选择排序法实现sort()方法的原理
for (int i=0;i<list.size()-1;i++) {
for (int j=i+1;j<list.size();j++) {
//调用compareTo()方法(返回正数负数或者是零)比较两个值的大小,如果是大于调换两个值的位置
if (list.get(i).compareTo(list.get(j))>0) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
}
}</span></strong></span>
如果不想依赖自然排序,可以通过传入比较器来实现自定义排序:
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;"> //如果不使用到自然排序,可以传入一个比较器来实现自定义排序
public static <T> void mySort(List<T> list,Comparator<? super T> comp) {
//使用选择排序法实现sort()方法的原理
for (int i=0;i<list.size()-1;i++) {
for (int j=i+1;j<list.size();j++) {
//调用比较器中的compareo()方法(返回正数负数或者是零)比较两个值的大小,如果是大于调换两个值的位置
if (comp.compare(list.get(i), list.get(j))>0) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
}
}</span></strong></span>
(2)swap(List i,List j)
以上代码中将i,j两个元素的位置进行交换使用的代码是:
T temp = list.get(i) ; // 定义一个临时变量用于暂存数据
list.set(i,list.get(j)) ;
list.set(j,temp) ;
引入集合框架工具类之后,可以简化为:Collections.swap(i,j) ;
引入集合框架工具类之后,可以简化为:Collections.swap(i,j) ;
(3)折半查找和获取最值
进行折半查找集合必须是有序的(从大到小或者是从小到大),对于无序的集合可以先进行排序之后在进行折半查找。如果找到指定的内容,将会返回指定内容的位置;如果找不到指定内容,则返回该指定内容在集合中的插入点-1.
代码演示:
Collections.binarySearch(list,"所要查找的内容") ;
如果要获取集合中的最大值,可以使用Collections的max()方法来获取。但在使用之前应该先判断所指定的集合是否能够排序,如果可以根据其自然顺序进行排序,则可以直接使用。但是如果指定集合不可以根据自然顺序排序,则在调用max()方法时要将比较其作为参数传入。
代码演示:
Collections.max(list,comparator) ;
(4)集合逆序和元素替换
① reverseOrder()
代码演示:
TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder()) ;
// reverseOrder()方法返回一个比较器,并强行逆转元素原有的自然排序顺序。
② reverseOrder("比较器对象")
代码演示:
TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(new ComparatorByLength()));
// reverseOrder("比较器对象")方法将一个已有的比较器进行顺序逆转。(需要传入一个比较器对象)
③ reverse(List<?> list)
该方法将指定列表集合的元素顺序进行反传
④ replaceAll(List<?> list,old,new)
该方法先调用indexOf(old)找到集合中old值的位置index,然后调用set(index,new)将新值覆盖旧值。(即:set(indexOf(old),new) ;)
⑤ fill(list,"值")
该方法将全部元素转变为所传入的值。
⑥ shuffle(list)
该方法将集合中的所有元素随机安放于任意位置
(5)toArray()
该方法可以将集合转换成数组。作此转换的目的是为了限定操作。(转换之后不允许增删)
关键代码:
(返回值类型:T[]) toArray(T[] a) ; // toArray()方法需要传入一个指定类型的数组
范例:
String[] s = list.toArray(new String[list.size()]) ; //使用list.size()方法指定数组大小
注意在长度定义时,如果长度小于集合的size,那么自动创建同类型同size的数组;如果所定义的长度大于集合的size,那么该方法使用指定的数组存储集合元素,其他多余位置默认为null。
2.同步非同步的集合
由于常用集合线程都是非同步的,所以再多线程时,要给非同步的集合加锁使其线程同步。
范例代码演示: (该范例为常见加锁方法,可以在以后开发中使用。注意:注释包含知识点和注意点)
<span style="font-family:KaiTi_GB2312;"><strong><span style="font-family:KaiTi_GB2312;">class MyCollections {
public static List synList(List list){
return new MtList(list) ;
}
//通过内部类实现为集合加锁
private class MyList implements List{
private List list ;
//定义一个Object对象的锁
private static final Object lock = new Object() ;
MyList(List list){
this.list = list ;
}
//将List对象原有的add()方法进行同步包装
public boolean add(Object obj){
synchronized(lock){
return list.add(obj) ;
}
}
//将List对象原有的remove()方法进行同步包装
public boolean remove(Object obj){
synchronized(lock){
return list.remove(obj) ;
}
}
//继续实现List接口中的抽象方法
//…………
//…………
//…………
//…………
}
}</span></strong></span>
以上代码是一下代码的实现原理:
(返回值类型:List) synchronizedList(List<?> list) ;
//此代码的实现过程即为以上代码,通过此构造方法,可以返回指定集合列表支持的同步集合列表(线程安全)。其中synchronizedList还可以换成是synchronizedMap和synchronizedSet……
3.Arrays工具类
与Collections一样,Arrays也是是集合框架中的工具类,该类中的方法也都是静态的。
(1)(返回值类型:List) asList(T...a[])
该方法将数组转换成集合。使用此方法是因为数组能使用的方法有限,利用此方法可以用使用集合中的方法操作数组中的元素。但是要注意,由于数组的长度固定,转换成集合之后不可以使用增删方法。
如果数组中的元素是对象,那么在转成集合的时候,直接将数组中的元素作为集合中的元素进行集合存储;但是如果数组中的元素是基本数据类型数值,那么会将该数组作为集合中的元素进行存储(即存储数组的地址)。
(2)sort()
该方法可以对数组中的元素进行排序
(3)……(详细参见API文档)
4.JDK1.5新特性
(1)foreach语句
代码演示:
for(类型 变量 : Collection集合/数组){}
范例:(使用高级for遍历Map集合)
可将Map转成单列Set集合,再利用高级for进行遍历
for(Integer key : map.keySet()){
String value = map.get(key) ;
System.out.println(……) ;
}
或者是:
for(Map.Entry<Integer,String> me : map.entrySet()){
Integer key = me.getKey() ;
String value = me.getValue() ;
System.out.println(……) ;
}
传统for循环与高级for循环的区别:
① 传统for循环可以完成对语句的多次执行,并且可以控制循环的条件以及循环的次数;
② 高级for循环必须有被便利的目标,该目标要么是数组要么是Collection单列集合;
③ 如果需要对数组的角标进行操作,需要使用传统for循环。
(2)函数可变参数
关键代码演示:
public static int newAdd(int...arr){} // 其实可变参数是数组的简写
函数可变参数其实是一个数组,但是接受的是数组的元素。它自动将这些元素封装成数组,极大地简化了使用者的书写。但是要注意的是:可变参数的类型必须定义在参数列表的结尾。