------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一.泛型
泛型是JDK1.5以后出现的新特性,用于解决安全问题,是个类型安全机制,先看以下的代码<span style="font-size:14px;"> 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);
}</span>
运行结果
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以后出现的泛型机制,解决了这个问题,将类型转化的异常限制在了编译阶段。还是上面那段代码,引入泛型之后,来看看现在的情况:
<span style="font-size:14px;"> ArrayList<String> l = new ArrayList<String>();
l.add("a");
l.add("ab");
l.add("abc");
l.add(4);
Iterator<String> it = l.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}</span>
尝试编译,可以发现,编译器报错
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method add(int, String) in the type ArrayList<String> is not applicable for the arguments (int)
因此程序无法运行
上面加入的泛型ArrayList<String> 意思是在声明的ArrayList类的对象l中,只能加入String类型的数据。如果加入其他类型的数据,编译器就会报错。注释掉l.add(4)后,可以发现,程序正常运行了。我们发现String s = it.next();这里没有类型转化,编译器也没有报错,原因也是泛型的加入,Iterator<String>告诉编译器这个迭代器取的元素也都是String类型,因此不用显式的将it.next()取出的元素转化为String,编译器也认为这里没有问题。
从上面的例子可以看出泛型机制的引入有以下几个好处:
1.将运行时发生的ClassCastException,转移到了编译时期。
方便了程序员解决问题,减少了运行时发生的问题,更为安全。
2.避免了强制类型转化的麻烦。
泛型的格式,通过<>来定义要操作的引用数据类型。
通常使用在集合框架中,在API中见到<>,就要使用泛型
当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
泛型常用方式
先看下面的代码
<span style="font-size:14px;">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();
}
}</span>
运行结果
Exception in thread "main" java.lang.ClassCastException: Worker cannot be cast to Teacher
上面的代码定义了两个类,一个Worker类,一个Teacher,还有一个工具类Tools,可以通过setObject方法设置对象,然后用getObject再取出。
这种工具类的定义方式是泛型机制出现前常用的,工具类为了通用性,操作的都是Object对象,在使用工具类后就像上面的这样,需要进行类型转化。这样在开发的时候也有可能出现类型转化的问题,一旦出错,只能在程序执行阶段才能发现,就行上面这段程序这样,Worker类对象无法转化为Teacher类对象。
使用泛型以后就可以避免这样的问题产生了。
应用泛型以后,上面的代码可以定义成这样:
<span style="font-size:14px;">class Utils<T>
{
private T t;
public T getObject() {
return t;
}
public void setObject(T t) {
this.t = t;
}
}
public static void main(String[] args) {
Utils<Worker> u = new Utils<Worker>();
u.setObject(new Worker());
Worker w = u.getObject();
}</span>
执行结果正常,没有错误
Utils<Worker> 在Utils类对象初始化的时候就把要操作的对象的类型定义好了,首先后面使用的时候不需要类型转化;第二,如果出现Teacher tc = u.getObject();这样的情况,在编译阶段就会提示:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Type mismatch: cannot convert from Worker to Teacher
无法编译通过,避免了程序运行时才暴露出错误。
2.泛型方法
泛型类的定义使得整个对象一旦初始化,就无法再操作其他类型的数据,但是泛型方法的方式可以避免这种情况,看下面的代码:
<span style="font-size:14px;">class Tools
{
public <T> void show(T t){
System.out.println("this is show " + t);
}
public <E> 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));
}</span>
执行结果
this is show haha
this is show 4
this is print haha
this is print 4
方法操作的数据类型由调用的时候动态指定。使得一个类对象可以操作不同类型的数据。
泛型类和泛型方法可以混合使用,并不冲突
<span style="font-size:14px;">class Tools<T>
{
public void show(T t){
System.out.println("this is show " + t);
}
public <E> void print(E e){
System.out.println("this is print " + e);
}
}</span>
这样的方式也是可以的。
注意:静态方法不能使用泛型类上面定义的泛型,因为泛型类的对象初始化的时候会确定类型,但是直接通过类调用静态方法的时候是不能确定类型的。只能通过泛型方法来定义静态函数的泛型。
集合框架(二)
Map
Map是接口 java集合部分的另一个顶级接口Map的常见子类对象
1.Hashtable 底层是哈希表的数据结构,不可以存入null键null值,线程同步
2.HashMap 底层是哈希表的数据结构,可以存入null键null值,线程不同步
3.TreeMap 底层是二叉树。线程不同步,可以用于给map集合中的键进行排序
这个接口的常用方法有一下这些
增
put(K key, V value) 在map中增加键值关联
putAll(Map<? extends K,? extends V> 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 视图
常用方法演示:
<span style="font-size:14px;">//增
Map<String, String> map = new HashMap<String, String>();
map.put("01","zhangsan");
map.put("02","wangwu");
map.put("03","lisi");
System.out.println(map);
Map<String, String> map1 = new HashMap<String, String>();
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);</span>
执行结果
{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集合
<span style="font-size:14px;">Map<String, String> map = new HashMap<String, String>();
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));
}</span>
执行结果
key:01...value:zhangsan
key:02...value:wangwu
key:03...value:lisi
2.通过entrySet取出map集合中所有映射关系的Set集合
<span style="font-size:14px;">Map<String, String> map = new HashMap<String, String>();
map.put("01","zhangsan");
map.put("02","wangwu");
map.put("03","lisi");
Set<Map.Entry<String, String>> me = map.entrySet();
Iterator<Map.Entry<String, String>> it = me.iterator();
while(it.hasNext()){
Entry<String, String> e = it.next();
System.out.println(e.getKey());
System.out.println(e.getValue());
}</span>
执行结果
01
zhangsan
02
wangwu
03
lisi
自定义对象作为map集合的键值
注意:因为map中相同的键值会相互覆盖,为了让自定义的对象存储在map中的时候也保证这个特性,需要在程序中定义什么样的两个对象是相同的,这就需要在自定义对象中覆盖Object的hashCode()方法和equals(Object obj)方法。
示例代码
<span style="font-size:14px;">class Student implements Comparable<Student>
{
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;
}
}</span>
<span style="font-size:14px;">//main方法
Map<Student, String> sm = new HashMap<Student, String>();
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);</span>
执行结果
{zhangsan....5=tianjing, wangwu....7=beijing, lisi....6=beijing}
可以看到,"zhangsan", 5添加了两遍,后者覆盖了前者,因为经过判断,二者的键值是相同的。
如果注释掉hashCode和equals方法:
<span style="font-size:14px;">class Student implements Comparable<Student>
{
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;
}
}</span>
{zhangsan....5=tianjing, lisi....6=beijing, zhangsan....5=beijing, wangwu....7=beijing}
可以看到"zhangsan", 5被添加了两遍,因为程序无法辨别两个Student的对象是否相同。
上面的Student类还实现了Comparable接口,并且覆盖了compareTo方法,让Student类具有了比较性。
<span style="font-size:14px;">Map<Student, String> tm = new TreeMap<Student, String>();
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));
}</span>
执行结果
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
先看下面的示例代码:<span style="font-size:14px;">List<List<String>> l = new ArrayList<List<String>>();
List<String> l1 = new ArrayList<String>();
List<String> l2 = new ArrayList<String>();
l1.add("l101");
l1.add("l102");
l2.add("l201");
l2.add("l202");
l.add(l1);
l.add(l2);
System.out.println(l);</span>
执行结果
[[l101, l102], [l201, l202]]
可以看到List l中有两个元素,l1和l2,而l1和l2也是List,各自有自己包含的元素。
List中嵌套Map
<span style="font-size:14px;">List<Map<String, String>> lm = new ArrayList<Map<String, String>>();
Map<String, String> m1 = new HashMap<String, String>();
Map<String, String> m2 = new HashMap<String, String>();
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);</span>
执行结果
[{01=m1, 02=m1}, {01=m2, 02=m2}]
可以看到List中包含两个元素,m1和m2,而m1和m2是两个Map集合,里面各自包含若干键值对。
Map中嵌套List
<span style="font-size:14px;">Map<String, List<String>> ml = new HashMap<String, List<String>>();
List<String> l1 = new ArrayList<String>();
List<String> l2 = new ArrayList<String>();
l1.add("01");
l1.add("02");
l2.add("01");
l2.add("02");
ml.put("l1",l1);
ml.put("l2",l2);
System.out.println(ml); </span>
执行结果
{l2=[01, 02], l1=[01, 02]}
可以看到Map集合ml中有两个键值关系,key l1对应的值是一个List集合l1,而key l2对应的值是List集合l2。
Map中嵌套List
<span style="font-size:14px;">Map<String, Map<String,String>> m = new HashMap<String, Map<String,String>>();
Map<String, String> m1 = new HashMap<String, String>();
Map<String, String> m2 = new HashMap<String, String>();
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);</span>
执行结果
{m1={01=m1, 02=m1}, m2={01=m2, 02=m2}}
可以看到Map集合中有两个键值对关系,m1和m2,二这两个键对应的值也是Map集合,里面各自包含属于自己的键值对集合。
注意:嵌套可以多层,层数也没有限定,但是不建议对集合进行多层嵌套,会降低代码的可读性和维护性。
集合框架中的常用工具类Collections和Arrays
Collections常用方法max(Collection<? extends T> coll) 返回自然顺序的最大元素
max(Collection<? extends T> coll, Comparator<? super T> comp) 根据比较器返回最大的元素
sort(List<T> list) 按照元素的自然顺序对元素进行排序,元素必须是Comparable的子类
sort(List<T> list, Comparator<? super T> c) 按照给定的比较器对List中的元素进行比较
swap(List<?> list, int i, int j) 交换两个元素的位置
reverse(List<?> list) 反转指定列表中元素的顺序
binarySearch 使用二分法查找指定的对象
fill(List<? super T> list, T obj) 使用指定元素替换指定列表中的所有元素
replaceAll(List<T> list, T oldVal, T newVal) 替换所有list中的oldVal为newVal
sort和reverse演示
<span style="font-size:14px;">List<Integer> l = new ArrayList<Integer>();
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);</span>
执行结果
[3, 1, 2]
[1, 2, 3]
[3, 2, 1]
swap演示
<span style="font-size:14px;">List<Integer> l1 = new ArrayList<Integer>();
l1.add(1);
l1.add(2);
l1.add(3);
System.out.println(l1);
Collections.swap(l1, 1, 2);
System.out.println(l1);</span>
执行结果
[1, 2, 3]
[1, 3, 2]
可以看到l1中的第二个元素和第三个元素被交换顺序了。
fill方法演示
<span style="font-size:14px;">List<Integer> l2 = new ArrayList<Integer>();
l2.add(1);
l2.add(2);
l2.add(3);
System.out.println(l2);
Collections.fill(l2, 0);
System.out.println(l2);</span>
可以看到l2中的元素全部被0替换了。
binarySearch方法演示
<span style="font-size:14px;">List<Integer> l3 = new ArrayList<Integer>();
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));</span>
执行结果
2
-5
第一个输出语句是要查找l3中为3的元素在l3中的位置,返回了3所在位置的索引2(list是从0开始)。第二个输出语句查找一个不存在的元素5,返回的是(-(插入点) - 1),元素5如果插入集合l3中是在位置4,-4-1得出了-5的结果。
注意:binarySearch使用的前提是List必须是有序的。
max方法演示(根据自然顺序)
<span style="font-size:14px;">List<String> l4 = new ArrayList<String>();
l4.add("aaaaa");
l4.add("bbbb");
l4.add("ccc");
System.out.println(Collections.max(l4)); </span>
执行结果
ccc
String类型的自然顺序是字典序,所以ccc最大。
max方法演示(根据自定比较器)
<span style="font-size:14px;">class StrComparator implements Comparator<String>
{
@Override
public int compare(String str1, String str2) {
return new Integer(str1.length()).compareTo(new Integer(str2.length()));
}
}</span>
<span style="font-size:14px;">//main方法
List<String> l4 = new ArrayList<String>();
l4.add("aaaaa");
l4.add("bbbb");
l4.add("ccc");
System.out.println(Collections.max(l4, new StrComparator()));</span>
执行结果
aaaaa
因为自定义的比较器是按照字符串的长度来比较的,所以aaaaa最长,所以最大。
replaceAll演示
<span style="font-size:14px;">List<String> l4 = new ArrayList<String>();
l4.add("aaaaa");
l4.add("bbbb");
l4.add("ccc");
Collections.replaceAll(l4, "ccc", "ddd");
System.out.println(l4); </span>
执行结果
[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<? super T> c) 根据指定比较器产生的顺序对指定对象数组的指定范围进行排序。
toString(Object[] a) 返回指定数组内容的字符串表示形式。
asList演示
<span style="font-size:14px;">Integer[] arr = {2,3,4};
List<Integer> l = Arrays.asList(arr);
System.out.println(l);</span>
执行结果
[2, 3, 4]
注意,由数组转化成的list是不可以增加和删除元素的,否则会报UnsupportedOperationException异常,原因是数组的长度是不可变的。
其他方法和Collections类的方法用法类似,这里就不一一举例了。