黑马程序员——JAVA基础拾遗之泛型和集合框架(二)

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一.泛型

泛型是JDK1.5以后出现的新特性,用于解决安全问题,是个类型安全机制,先看以下的代码

        ArrayList l = new ArrayList();
        l.add("a");
        l.add("ab");
        l.add("abc");
        l.add(4);
        Iterator it = l.iterator();
        while(it.hasNext()){
            String s = (String)it.next();
            System.out.println(s);
        }

运行结果
a
ab
abc
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.baobao.GenericTest.main(GenericTest.java:33)

原因很明显是类型转换异常,上面这个错误很好发现。但是当代码的复杂度上升,在大段大段的代码中,缺却难保不发生这样的问题。更槽糕的是这个错误是在运行的时候发生的,这样如果在大型的项目中,程序一旦已经投入使用,到时候发现问题回头修改,代价就太高了。
在JDK1.5以后出现的泛型机制,解决了这个问题,将类型转化的异常限制在了编译阶段。还是上面那段代码,引入泛型之后,来看看现在的情况:


        ArrayList l = new ArrayList();
        l.add("a");
        l.add("ab");
        l.add("abc");
        l.add(4);
        Iterator it = l.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

尝试编译,可以发现,编译器报错
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
The method add(int, String) in the type ArrayList is not applicable for the arguments (int)
因此程序无法运行

上面加入的泛型ArrayList 意思是在声明的ArrayList类的对象l中,只能加入String类型的数据。如果加入其他类型的数据,编译器就会报错。注释掉l.add(4)后,可以发现,程序正常运行了。我们发现String s = it.next();这里没有类型转化,编译器也没有报错,原因也是泛型的加入,Iterator告诉编译器这个迭代器取的元素也都是String类型,因此不用显式的将it.next()取出的元素转化为String,编译器也认为这里没有问题。

从上面的例子可以看出泛型机制的引入有以下几个好处:
1.将运行时发生的ClassCastException,转移到了编译时期。
方便了程序员解决问题,减少了运行时发生的问题,更为安全。
2.避免了强制类型转化的麻烦。

泛型的格式,通过<>来定义要操作的引用数据类型。


在使用java提供的对象时,什么时候使用泛型呢?
通常使用在集合框架中,在API中见到<>,就要使用泛型
当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。


泛型常用方式


先看下面的代码
class Worker
{
}


class Teacher
{
}


class Tools
{
    private Object obj;
    public Object getObject() {
        return obj;
    }
    public void setObject(Object obj) {
        this.obj = obj;
    }
}
public class GenericTest
{
    public static void main(String[] args) {
        Tools t = new Tools();
        t.setObject(new Worker());
        Teacher tc = (Teacher)t.getObject();
    }


}

运行结果
Exception in thread "main" java.lang.ClassCastException: Worker cannot be cast to Teacher

上面的代码定义了两个类,一个Worker类,一个Teacher,还有一个工具类Tools,可以通过setObject方法设置对象,然后用getObject再取出。
这种工具类的定义方式是泛型机制出现前常用的,工具类为了通用性,操作的都是Object对象,在使用工具类后就像上面的这样,需要进行类型转化。这样在开发的时候也有可能出现类型转化的问题,一旦出错,只能在程序执行阶段才能发现,就行上面这段程序这样,Worker类对象无法转化为Teacher类对象。

使用泛型以后就可以避免这样的问题产生了。


1.泛型类
应用泛型以后,上面的代码可以定义成这样:

class Utils
{
    private T t;


    public T getObject() {
        return t;
    }
    public void setObject(T t) {
        this.t = t;
    }
}
public static void main(String[] args) {
	Utils u = new Utils();
	u.setObject(new Worker());
	Worker w = u.getObject();
}

执行结果正常,没有错误

Utils 在Utils类对象初始化的时候就把要操作的对象的类型定义好了,首先后面使用的时候不需要类型转化;第二,如果出现Teacher tc = u.getObject();这样的情况,在编译阶段就会提示:
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
Type mismatch: cannot convert from Worker to Teacher
无法编译通过,避免了程序运行时才暴露出错误。

2.泛型方法
泛型类的定义使得整个对象一旦初始化,就无法再操作其他类型的数据,但是泛型方法的方式可以避免这种情况,看下面的代码:

class Tools
{
    public  void show(T t){
        System.out.println("this is show " + t);
    }
    public  void print(E e){
        System.out.println("this is print " + e);
    }
}


public static void main(String[] args) {
	Tools t = new Tools();
	t.show("haha");
	t.show(new Integer(4));
	t.print("haha");
	t.print(new Integer(4));
}

执行结果
this is show haha
this is show 4
this is print haha
this is print 4

方法操作的数据类型由调用的时候动态指定。使得一个类对象可以操作不同类型的数据。

泛型类和泛型方法可以混合使用,并不冲突

class Tools
{
    public void show(T t){
        System.out.println("this is show " + t);
    }
    public  void print(E e){
        System.out.println("this is print " + e);
    }
}

这样的方式也是可以的。
注意:静态方法不能使用泛型类上面定义的泛型,因为泛型类的对象初始化的时候会确定类型,但是直接通过类调用静态方法的时候是不能确定类型的。只能通过泛型方法来定义静态函数的泛型。


集合框架(二)

Map

Map是接口 java集合部分的另一个顶级接口

Map的常见子类对象
1.Hashtable 底层是哈希表的数据结构,不可以存入null键null值,线程同步
2.HashMap 底层是哈希表的数据结构,可以存入null键null值,线程不同步
3.TreeMap 底层是二叉树。线程不同步,可以用于给map集合中的键进行排序


这个接口的常用方法有一下这些

put(K key, V value) 在map中增加键值关联
putAll(Map m) 将一个map中的键值关联加入另一个map中

clear()  移除所有映射关系
remove(Object key) 移除key对应的键值关系
判断
isEmpty() 判断map是否为空
containsKey(Object key)  是否包含指定键
containsValue(Object value)  是否包含指定value
获取
get(Object key)  根据键获取对应value
size()  返回此映射中的键-值映射关系数
keySet() 返回键值的set集合
values() 返回此映射中包含的值的 Collection 视图
entrySet() 返回此映射中包含的映射关系的 Set 视图



常用方法演示:

//增
   Map map = new HashMap();
   map.put("01","zhangsan");
   map.put("02","wangwu");
   map.put("03","lisi");
   System.out.println(map);
   Map map1 = new HashMap();
   map1.put("04", "chenqi");
   map1.put("05", "yangba");
   map.putAll(map1);
   System.out.println(map);
   //判断
   System.out.println("判断是否为空:"+map.isEmpty());
   System.out.println("判断是否包含指定键02:"+map.containsKey("02"));
   System.out.println("判断是否包含指定值chenqi:"+map.containsValue("chenqi"));
   //获取
   System.out.println("03对应的值是" + map.get("03"));//根据键获取值
   System.out.println(map.size());//获取map集合键值对个数
   System.out.println(map.keySet());//返回键的set集合
   System.out.println(map.values());//返回值的 Collection 视图
   System.out.println(map.entrySet());//返回此映射中包含的映射关系的 Set 视图
   //删
   System.out.println(map.remove("01"));//删除键01对应的键值对,如果成功,返回01对应的值,否则为null
   System.out.println(map);
   map.clear();//清空map中的所有键值对
   System.out.println(map);


执行结果
{01=zhangsan, 02=wangwu, 03=lisi}
{04=chenqi, 05=yangba, 01=zhangsan, 02=wangwu, 03=lisi}
判断是否为空:false
判断是否包含指定键02:true
判断是否包含指定值chenqi:true
03对应的值是lisi
5
[04, 05, 01, 02, 03]
[chenqi, yangba, zhangsan, wangwu, lisi]
[04=chenqi, 05=yangba, 01=zhangsan, 02=wangwu, 03=lisi]
zhangsan
{04=chenqi, 05=yangba, 02=wangwu, 03=lisi}
{}



map的常见遍历方式

1.通过set集合

Map map = new HashMap();
map.put("01","zhangsan");
map.put("02","wangwu");
map.put("03","lisi");
for(String key: map.keySet()){//获取键的set集合
System.out.println("key:"+key+"..."+"value:"+map.get(key));
}

执行结果
key:01...value:zhangsan
key:02...value:wangwu
key:03...value:lisi


2.通过entrySet取出map集合中所有映射关系的Set集合

Map map = new HashMap();
map.put("01","zhangsan");
map.put("02","wangwu");
map.put("03","lisi");
Set> me = map.entrySet();
Iterator> it = me.iterator();
while(it.hasNext()){
    Entry e = it.next();
    System.out.println(e.getKey());
    System.out.println(e.getValue());
}

执行结果
01
zhangsan
02
wangwu
03
lisi

自定义对象作为map集合的键值

注意:因为map中相同的键值会相互覆盖,为了让自定义的对象存储在map中的时候也保证这个特性,需要在程序中定义什么样的两个对象是相同的,这就需要在自定义对象中覆盖Object的hashCode()方法和equals(Object obj)方法。


示例代码
class Student implements Comparable
{
    private String name;
    private int age;
    
    Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode(){
        return name.hashCode() + age;
    }
    @Override
    public boolean equals(Object obj){
        if(!(obj instanceof Student)){
            throw new ClassCastException("类型不匹配");
        }
        Student s = (Student) obj;
        return s.name.equals(name) && s.age == age;
    }
    @Override
    public String toString(){
        return this.name + "...." + this.age;
    }
    @Override
    public int compareTo(Student s){
        int num = new Integer(age).compareTo(new Integer(s.age));
        if(num == 0){
            return this.name.compareTo(s.name);
        }
        return num;
    }
}

//main方法
Map sm = new HashMap();
sm.put(new Student("zhangsan", 5), "beijing");
sm.put(new Student("zhangsan", 5), "tianjing");
sm.put(new Student("lisi", 6), "beijing");
sm.put(new Student("wangwu", 7), "beijing");
System.out.println(sm);

执行结果
{zhangsan....5=tianjing, wangwu....7=beijing, lisi....6=beijing}


可以看到,"zhangsan", 5添加了两遍,后者覆盖了前者,因为经过判断,二者的键值是相同的。

如果注释掉hashCode和equals方法:

class Student implements Comparable
{
    private String name;
    private int age;
    
    Student(String name, int age){
        this.name = name;
        this.age = age;
    }
//    @Override
//    public int hashCode(){
//        return name.hashCode() + age;
//    }
//    @Override
//    public boolean equals(Object obj){
//        if(!(obj instanceof Student)){
//            throw new ClassCastException("类型不匹配");
//        }
//        Student s = (Student) obj;
//        return s.name.equals(name) && s.age == age;
//    }
    @Override
    public String toString(){
        return this.name + "...." + this.age;
    }
    @Override
    public int compareTo(Student s){
    //先按照年龄排序
        int num = new Integer(age).compareTo(new Integer(s.age));
        if(num == 0){
        //年龄相同时按照姓名排序
            return this.name.compareTo(s.name);
        }
        return num;
    }
}


执行结果:

{zhangsan....5=tianjing, lisi....6=beijing, zhangsan....5=beijing, wangwu....7=beijing}

可以看到"zhangsan", 5被添加了两遍,因为程序无法辨别两个Student的对象是否相同。

上面的Student类还实现了Comparable接口,并且覆盖了compareTo方法,让Student类具有了比较性。


Map tm = new TreeMap();
tm.put(new Student("zhangsan", 5), "beijing");
tm.put(new Student("lisi", 5), "beijing");
tm.put(new Student("wangwu", 7), "beijing");
tm.put(new Student("zhaoliu", 8), "beijing");
for(Student stu: tm.keySet()){//获取键的set集合
    System.out.println("stu:" + stu + "..." + "addr:" + tm.get(stu));
}

执行结果
stu:lisi....5...addr:beijing
stu:zhangsan....5...addr:beijing
stu:wangwu....7...addr:beijing
stu:zhaoliu....8...addr:beijing

可以看出TreeMap结合中将Student类按照年龄进行了排序,当年龄相同时,再name进行排序。

注意:Collection接口和Map接口的子类可以添加任何类的对象做为元素,当然也可以添加集合做为元素,因此集合中嵌套集合时可以的,并且是常用的。可以看下面的例子。


List中嵌套List

先看下面的示例代码:
List> l = new ArrayList>();
List l1 = new ArrayList();
List l2 = new ArrayList();
l1.add("l101");
l1.add("l102");
l2.add("l201");
l2.add("l202");
l.add(l1);
l.add(l2);
System.out.println(l);

执行结果
[[l101, l102], [l201, l202]]

可以看到List l中有两个元素,l1和l2,而l1和l2也是List,各自有自己包含的元素。

List中嵌套Map

List> lm = new ArrayList>();
Map m1 = new HashMap();
Map m2 = new HashMap();
m1.put("01", "m1");
m1.put("02", "m1");
m2.put("01", "m2");
m2.put("02", "m2");
lm.add(m1);
lm.add(m2);
System.out.println(lm);

执行结果
[{01=m1, 02=m1}, {01=m2, 02=m2}]

可以看到List中包含两个元素,m1和m2,而m1和m2是两个Map集合,里面各自包含若干键值对。

Map中嵌套List

Map> ml = new HashMap>();
List l1 = new ArrayList();
List l2 = new ArrayList();
l1.add("01");
l1.add("02");
l2.add("01");
l2.add("02");
ml.put("l1",l1);
ml.put("l2",l2);
System.out.println(ml); 


执行结果
{l2=[01, 02], l1=[01, 02]}

可以看到Map集合ml中有两个键值关系,key l1对应的值是一个List集合l1,而key l2对应的值是List集合l2。


Map中嵌套List

Map> m = new HashMap>();
Map m1 = new HashMap();
Map m2 = new HashMap();
m1.put("01", "m1");
m1.put("02", "m1");
m2.put("01", "m2");
m2.put("02", "m2");
m.put("m1", m1);
m.put("m2", m2);
System.out.println(m);

执行结果
{m1={01=m1, 02=m1}, m2={01=m2, 02=m2}}

可以看到Map集合中有两个键值对关系,m1和m2,二这两个键对应的值也是Map集合,里面各自包含属于自己的键值对集合。

注意:嵌套可以多层,层数也没有限定,但是不建议对集合进行多层嵌套,会降低代码的可读性和维护性。


集合框架中的常用工具类Collections和Arrays

Collections常用方法
max(Collection coll) 返回自然顺序的最大元素
max(Collection coll, Comparator comp) 根据比较器返回最大的元素
sort(List list) 按照元素的自然顺序对元素进行排序,元素必须是Comparable的子类
sort(List list, Comparator c) 按照给定的比较器对List中的元素进行比较
swap(List list, int i, int j)  交换两个元素的位置
reverse(List list) 反转指定列表中元素的顺序
binarySearch 使用二分法查找指定的对象
fill(List list, T obj) 使用指定元素替换指定列表中的所有元素
replaceAll(List list, T oldVal, T newVal) 替换所有list中的oldVal为newVal



sort和reverse演示
List l = new ArrayList();
l.add(3);
l.add(1);
l.add(2);
System.out.println(l);
Collections.sort(l);
System.out.println(l);
Collections.reverse(l);
System.out.println(l);

执行结果
[3, 1, 2]
[1, 2, 3]
[3, 2, 1]


swap演示
List l1 = new ArrayList();
l1.add(1);
l1.add(2);
l1.add(3);
System.out.println(l1);
Collections.swap(l1, 1, 2);
System.out.println(l1);

执行结果
[1, 2, 3]
[1, 3, 2]


可以看到l1中的第二个元素和第三个元素被交换顺序了。   



fill方法演示
List l2 = new ArrayList();
l2.add(1);                          
l2.add(2);                          
l2.add(3);                          
System.out.println(l2);             
Collections.fill(l2, 0);            
System.out.println(l2);

可以看到l2中的元素全部被0替换了。


binarySearch方法演示  
List l3 = new ArrayList();
l3.add(1);
l3.add(2);
l3.add(3);
l3.add(4);
System.out.println(Collections.binarySearch(l3, 3));
System.out.println(Collections.binarySearch(l3, 5));


执行结果
2
-5

第一个输出语句是要查找l3中为3的元素在l3中的位置,返回了3所在位置的索引2(list是从0开始)。第二个输出语句查找一个不存在的元素5,返回的是(-(插入点) - 1),元素5如果插入集合l3中是在位置4,-4-1得出了-5的结果。

注意:binarySearch使用的前提是List必须是有序的。

max方法演示(根据自然顺序)
List l4 = new ArrayList();
l4.add("aaaaa");
l4.add("bbbb");
l4.add("ccc");
System.out.println(Collections.max(l4));    

执行结果
ccc

String类型的自然顺序是字典序,所以ccc最大。



max方法演示(根据自定比较器)

class StrComparator implements Comparator
{
    @Override
    public int compare(String str1, String str2) {
        return new Integer(str1.length()).compareTo(new Integer(str2.length()));
    }
}

//main方法
List l4 = new ArrayList();
l4.add("aaaaa");
l4.add("bbbb");
l4.add("ccc");
System.out.println(Collections.max(l4, new StrComparator()));

执行结果
aaaaa

因为自定义的比较器是按照字符串的长度来比较的,所以aaaaa最长,所以最大。



replaceAll演示
List l4 = new ArrayList();
l4.add("aaaaa");
l4.add("bbbb");
l4.add("ccc");
Collections.replaceAll(l4, "ccc", "ddd");
System.out.println(l4);          

执行结果
[aaaaa, bbbb, ddd]

l4中的所有ccc被替换成了ddd。


Arrays常用方法
asList(T... a) 把数组转化为list集合
binarySearch(Object[] a, Object key) 使用二分搜索法来搜索指定数组,以获得指定对象。
equals(Object[] a, Object[] a2) 如果两个指定的 Objects 数组彼此相等,则返回 true。
fill(Object[] a, Object val) 将指定的 Object 引用分配给指定 Object 数组的每个元素。
sort(T[] a, int fromIndex, int toIndex, Comparator c) 根据指定比较器产生的顺序对指定对象数组的指定范围进行排序。
toString(Object[] a) 返回指定数组内容的字符串表示形式。



asList演示
Integer[] arr = {2,3,4};
List l = Arrays.asList(arr);
System.out.println(l);

执行结果
[2, 3, 4]

注意,由数组转化成的list是不可以增加和删除元素的,否则会报UnsupportedOperationException异常,原因是数组的长度是不可变的。

其他方法和Collections类的方法用法类似,这里就不一一举例了。



你可能感兴趣的:(java基础,黑马程序员,泛型,集合,map)