18-1,泛型-概述
1,泛型是JDK1.5出现的新技术,新技术的出现是为了解决问题。
2,泛型可以用于明确一个集合中存储什么类型的元素
ArrayList<String> al = new ArrayList<String>();
al.add("abc");//正确
//al.add(3);//编译直接报错
//在迭代器上加上泛型,下面取出来的时候就不用强转了
Iterator<String> it = al.iterator();
while(it.hasNext()) {
//在这里不用强转了,因为从it中取出的肯定是String
String str = it.next();
System.out.println(str);
}
3,好处
(1)将运行时间的问题ClassCastException转到了编译时期。
(2)避免了强制转换的麻烦。
4,泛型什么时候用呢?
当操作的引用数据类型不确定的时候,就用泛型,将要操作的引用数据类型写在<>中即可。其实<>就是一个用于接收具体引用数据类型的参数范围。
在程序中,只要用到了带有<>的类型或者接口,就要明确传入的具体引用数据类型。
也可以同时接受多个数据类型:
class Tool<e1,e2,e3,e4>
Tool<String,Demo,Person,Integer> t = new Tool<String,Demo,Person,Integer>();
18-2,泛型-擦除&补偿
1,泛型解决了编译时期的安全问题,是给编译器使用的技术。
2,运行时,会将泛型去掉,生成的class文件中是不带有泛型的,这个称为泛型的擦除。
为什么要擦除呢?
因为为了兼容运行时的类加载器,因为泛型是JDK1.5的新特性,如果class文件中加上泛型的话,类加载器会不认识,若想要类加载器认识泛型,类加载器也要升级,为了避免升级类加载器的麻烦,泛型将交由编译器处理,编译通过后就会把他删去,使之前的类加载器也能运行它。
3,泛型的补偿:
擦除后,如果对元素进行取出的赋值动作,如:String str =it.next();是否需要加上强转呢?答案是不用了,首先,加上泛型且编译通过就能保证集合中的数据是指定的类型了,所以认为不会出现规定类型以外的元素。其实,在运行时,是自动添加了一步动作,通过用getClass()方法获取元素的类型再对其进行一次转换动作,就不用使用者在强制转换了。
18-3,泛型-在集合中的应用
1,以TreeSet为例,TreeSet具备比较功能,但存入的类型必须实现Comparable接口并实现了里面的compareTo方法才可以;或者定义一个比较器,实现Comparator接口并实现里面的compare方法也可以。定义TreeSet时,如果不加泛型Eclipse等IDE会有黄色波浪线提示,加上泛型,黄色波浪线即可消失。
2,上一章提到的Person类实现Comparable接口时Comparable接口也需要指定泛型,如:Comparable<Person>,指定后,覆写的compareTo方法中的参数就可以不写Object了,直接写Person就可以,方法体中的代码也不用强制向下转型了。Comparator接口也要加上泛型,它里面的compare方法的两个参数也直接接受Person类型就可以了。例如:
class Person implements Comparable<Person> {
...code...
public int compareTo(Person p) {
//Person p = (Person)p;//强转可以省去,因为定义了泛型
int temp = this.age = p.age;
return temp == 0 ? this.name.compareTo(p.name) : temp;
}
...code...
}
18-4,泛型类
1,以前定义工具类的时候这么定义:
public class Tool {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
别的类用这个工具类的时候,可以这样做:
Tool tool = new Tool();
/*
这里因为没有Worker这个类,所以是错误的。
Worker为传入的错误值,但编译不会报错,因为Tool中接收的是Object类型,
下面的语句必须做强转才能匹配类型,因为把Worker向上提升为了Object。
*/
tool.setObject(new Worker());
Student stu = (Student)tool.getObject();
工具类是针对所有类型的,而任何一个类都是Object的子类,所以根据多态的特性,通过将参数定义成Object我们就可以使这个方法接收所有类型的参数,在没有泛型的时候我们就只能这么做。
在JDK1.5之后,使用泛型来接收类中要操作的引用数据类型,这就是泛型类。
泛型类什么时候用呢?
当类中的操作的引用数据类型不确定的时候,就是用泛型来表示。
例如:
public class Tool<Q> {
private Q q;
public Q getObect() {
return q;
}
public void setObject(Q object) {
this.q = object;
}
}
有泛型后,在别的类用到工具类的时候,可以这么做:
Tool<Student> tool = new Tool<Student>();
//tool.setObject(new Worker());//编译报错,因为有了泛型指定的Student,所以别的不能进入集合。
tool.setObject(new Student());
Student stu = tool.getObject();
18-5,泛型方法
1,在18-4中有泛型的Tool类中加入泛型方法,Tool<QQ>
public class Tool<Q> {
private Q q;
public Q getObect() {
return q;
}
public void setObject(Q object) {
this.q = object;
}
/*
如果方法不加泛型会报错,因为Tool明确只能接收QQ类型,
所以不能操作W类型,要想让其中一个方法操作W类型,
必须在返回值类型前加上泛型<W>。
*/
public <W> void show(W str) {
System.out.println("show" + str.toString());
}
/*
另外,print接收的是字符串,那么能不能打印字符串的长度呢?
答案是不能的,因为QQ str = "abc"(这里假设传入abc),将"abc"提升为QQ类型,
QQ类型不知道String类型中的特有方法length(),所以不能打印。但由于QQ也
继承自Object,所以可以使用Object中的方法。
*/
public void print(QQ str) {
System.out.println("print" + str);
}
}
调用的时候:
Tool<String> tool = new Tool<String>();
tool.show(new Integer(4));//因为show加了泛型,所以可以接受Tool定义的String以外的类型。
tool.show("abc");//符合Tool的泛型。
tool.print("haha");//print方法没有加泛型,直接匹配。
2,当方法静态时,不能访问类上定义的泛型,如果静态方法是用泛型,只能将泛型定义在方法上。
如果在Tool<QQ>中定义方法:
//如果此方法上不加泛型,参数Y obj会编译报错,泛型需要对象来明确,
//如Tool<String> tool = new Tool<String>();静态是不需要对象的,所以必须在void前加泛型。
public static <Y> void method(Y obj) {
System.out.println("method" + obj);
}
调用这个方法的时候:
Tool.method("bcd");//bcd
Tool.method(new Integer(9));//9
18-6,泛型接口
1,泛型接口就是将泛型定义在接口上。
//定义泛型时一般不明确具体类型,可以接受任意指定类型的对象,在创建对象的时候明确。
interface Inter<T> {
public void show(T t);
}
//实现接口可以明确要操作的类型,那么在这个类上直接加上指定类型的泛型即可。
class ImterImpl<String> implements Inter<String> {
public void show(String str) {
System.out.println("show:" + str);
}
}
调用show方法时:
InterImpl in = new InterImpl();//这样new对象时就不用加泛型了。
in.show("abc");//这样调用就行
2,在实现接口的类中还是不能明确使用的类型:
class InterImpl<Q> implements Inter<Q> {
public void show(Q q) {
System.out.println("show:" + q);
}
}
在new这个InterImpl类的时候才能明确使用的类型,那么就定义成上述的样子,在new对象的时候再明确其泛型。
InterImpl<Integer> in = new InterImpl<Integer>();
in.show(5);
这样一来,in对象就只能接收Integer类型的对象了。
18-7,泛型的界定-上限
1,有多个集合,要想用Iterator取出多个集合中的数据,这时不要定义多个Iterator,要提高代码的复用性,就要单独定义一个方法来进行迭代。例如有这么两个集合:
ArrayList<String> al = newArrayList<String>();
ArrayList<Integer> al2 = newArrayList<Integer>();
假如这两个集合中已经有了数据,这时要取出里面的数据,定义一个迭代方法:
//这里接收的是Collection,所以可以迭代List,Set等所有集合,但这里也定义了泛型,所以只能接受String了。
public static void printCollection(Collection<String> al) {
Iterator<String> it = al.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
这是要考虑到接收的集合中的元素类型是不确定的,怎样接收不同类型的元素呢?可以用泛型通配符表示。
2,泛型的通配符---?,表示未知类型。
public static void printCollection(Collection<?> al) {
Iterator<?> it = al.iterator();
while(it.hasNext()) {
//由于参数类型不确定,所以直接用it.next()就OK了。
System.out.println(it.next());
}
}
也可以在方法上加上泛型:
public static <T> void printCollection(Collection<T> al)
Iterator<T> it = al.iterator();
while(it.hasNext()) {
T str = it.next();
System.out.println(str);
}
但是这么做有些麻烦。但也有另外的好处,如果这么写:
public static <T> T printCollection(Collection<T> al) {
Iterator<T> it = al.iterator();
T t = it.next();
return t;
}
这样就可以对传入的T返回其对象,并进行操作,不过主要还是用通配符--?。
3,需求:迭代方法中,只能接收Person及其子类。
分析:首先?就不行了,因为不能接受所有对象。
例如Person的子类有Worker,Student,若要迭代这两个而不能迭代Person以外的类(例如String),可以这么写:
//<? extends Person>表示泛型定义是所有继承Person的类型或Person类型
public static void printCollection(Collection<? extends Person> al){
Iterator<? extends Person> it = al.iterator();
while(it.hasNext()) {
Person p = it.next();//已经可以明确接收的是Person或其子类
System.out.println(p.getName() + ":" + p.getAge());
}
}
若想用此方法迭代String类型的对象,则在编译时会直接报错,因为String不是Person的子类,这就是泛型的上限,其实,泛型写<?>是<? extends Object>的缩略。
18-8,下限
1,定义方式:<? super E>表示指定的类型必须是E或E的父类。
例如Student和Worker都是Person的子类,
定义了三个集合:
ArrayList<Person> al1 = new ArrayList<Person>();
ArrayList<Student> al2 = new ArrayList<Student>();
ArrayList<Worker> al3 = new ArrayList<Worker>();
然后定义一个方法:
public static void printCollection(Collection<? super Student> al) {
Iterator<? super Student> it = al.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
这时一个迭代集合元素的方法,泛型类型指的是Student或Student的父类,由于Student继承自Person,Worker也继承自Person,所以这里如果传入Worker是不行的,会报错。这就是泛型的下限。
18-9,选择使用集合的技巧
1,几何元素需要唯一么?
|--需要:用Set
|--需要指定顺序么?
|--需要:TreeSet
|--不需要:HashSet
|--但是想要一个和存储一致的顺序(有序):LinkedHashSet
|--不需要:用List
|--需要频繁增删么?
|--需要:LinkedList
|--不需要:ArrayList
2,如何记住每个容器的结构和所属的体系呢?看名字!
List
|--ArrayList
|--LinkedList
Set
|--HashSet
|--TreeSet
后缀名就是该集合所属的体系。
前缀名就是该集合的数据结构。
看到array:就要想到数组,查询快,有角标。
看到Link:就要想到链表,增删快,想到add、get、remove方法。
看到hash:就要想到哈希表,唯一性,就要想到元素需要覆盖hashCode和equals方法。
看到tree:就要想到二叉树,想到需要排序,两个接口,Comparable,Comparator。
而且通常这些常用的集合容器都是不同步的。
18-10,工具类-Collections排序
1,java.util.Collections
此类完全由在Collection上进行操作或返回Collection的静态方法组成。
2,Collections是集合框架的工作类,里面的方法都是静态的。
static <T extendsComparable<? super T>> voidsort(List<T> list)
根据元素的自然排序,对指定列表进行升序排序,列表中的所有元素都必须实现Comparable接口,列表中的所有元素都必须是相互可以比较的,即类型必须相互匹配才行。
演示排序方法示例:
public static void demo_1() {
List<String> list = new ArrayList<String>();
list.add("abcde");
list.add("cba");
list.add("aa");
list.add("zzz");
list.add("cba");
list.add("nbaa");
System.out.println(list);
//排序
Collections.sort(list);//String实现了Comparable接口,具备可比性
System.out.println(list);
}
若要按照长度排序,可以通过sort(List<T>list,Comparator<? Super T> c)带比较器的sort方法实现,这需要定义一个类,并实现Comparator接口,并实现里面的compare方法。例如:
public class ComparatorByLength implements Comarator<String> {
public int compare(String o1,String o2) {
int temp = o1.length() - o2.length();
return temp == 0 ? o1.compareTo(o2) : temp;
}
}
然后调用方法Collections.sort(list,newComparatorByLength());即可。
18-11,折半&最值
1,static <T> int binarySearch(List<?extends Comparable<? super T>> list, T key)
这个方法是使用二进制搜索法来搜索指定列表,以获得指定对象,在进行此调用之前,必须根据列表元素的自然顺序对列表进行升序排序,其实就是二分查找法。
2,static <T> int binarySearch(List<?extends T> list,T key,Comparator<? super T> c)
与上面的方法的不同之处在于,用指定的比较器对集合中的元素进行升序排序。
3,折半:
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abcde");
list.add("cba");
list.add("aa");
list.add("zzz");
list.add("cba");
list.add("nbaa");
//排序
Collections.sort(list);
int index = Collections.binarySearch(list,"cba");//index是2
}
}
4,最大值
static T max(Collection<? extendsT> coll)
根据元素的自然顺序,返回给定collection的最大元素,collection中的元素必须具备比较功能,该方法是依据Comparable比较的。
static T max(Collection<? extendsT> coll,Comparator<? super T> comp)
根据指定的比较器产生的顺序,返回给定collection的最大值。
例如:
String max = Collections.max(list,newComparatorByLength())
返回最长的字符串。
18-12,逆序&替换
1,static <T> Comparator<T>reverseOrder()
返回一个比较器,它强行反转实现Comparable接口那些对象collection上的自然顺序,他是根据Comparable比较的。
TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(newComparatorByLength()))
这个方法强行反转比较器指定排序后的反转。
2,替换
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abcde");
list.add("cba");
list.add("aa");
//将list集合中的"cba"替换成"nba"
//原理:list.set(indexOf("cba"),"nba")
Collections.replaceAll(list,"cba","nba");
}
}
18-13,其他方法,将非同步集合转成同步集合的方法
1,fill方法,可将集合中的所有的值都重新赋值(重新初始化)
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abcde");
list.add("cba");
list.add("aa");
Collections.fill(list, "cc");
System.out.println(list);//[cc,cc,cc] 将所有元素初始化为cc
}
}
2,Collections.shuffle(list);//将集合中的元素随机重新排列
3,static <T> ArrayList<T>list(Enumeration<T> e)
返回一个数组列表,它按返回顺序包含指定枚举返回的元素,将枚举转换成ArrayList。
4,static <T> Enumeration<T>enumeration(Collection<T> c)
将指定集合转成枚举。
5,static <T> Enumeration<T>synchronizedList(List<T> list)
返回指定列表支持的(线程安全的)列表。
除此之外可以将Collection、Map、Set,等转成线程安全的。
实现原理:
List list = new ArrayList();//是非同步的
list = MyCollections.synList(list);//返回一个同步的list
给非同步的集合加锁:
//保证在添加时不能删除,删除时不能添加
class MyCollections {
public static List synList(List list) {
return new MyList(list);
}
private class MyList implements List {
private List list;
private static final Object lock = new Object();
MyList(List list) {
this.list = list;
}
public boolean add(Object obj) {
synchronized(lock) {
return list.add(obj);
}
}
public boolean remove(Object obj) {
synchronized(lock) {
return list.remove(obj);
}
}
}
}
18-14,Arrays数组工具类方法介绍
1,binarySearch(...)二分查找法,返回索引。
2,equals(...)覆写了Object中的equals,比较内容是否相同,即长度相同且对应位上的元素相同。
3,static void fill(byte[] a,byte val)将val赋给byte[]中的每一个位置。
4,sort(...)将传入的数组排序。
5,toString(),覆写了Object中的toString(),直接打印数组中的元素。
6,toString()的经典实现:
public static String myToString(int[] a) {
int iMax = a.length - 1;
if(iMax == -1) {
return "[]";
}
StringBuilder b = new StringBuilder();
b.append('[');
for(int i=0;;i++) {
b.append(a[i]);
if(i == iMax) {
return b.append(']').toString();
}
b.append(",");
}
}
18-15,Arrays-asList方法
1,static<T> List<T> asList(T... a)
返回一个受指定数组支持的固定大小的列表。
用asList的原因:Arrays中提供的方法也是相当有限的,如果把数组转换成集合操作就方便多了,比如判断一个元素是否在数组中,Arrays中没有这个方法,就需要自己写一个判断方法,但是如果将这个数组转换成List就可以直接用contains判断了。
2,好处:可以使用集合中的方法操作数组中的元素。
注意:数组的长度是固定的,所以对于集合的增删方法是不能使用的,否则会发生UnsupportedOperationException异常,只要不改变集合长度的方法都可以使用。
但是可以使用set方法用指定元素替换指定的元素。
3,如果数组中的元素是对象,那么转成集合时,直接将数组中的元素作为集合中的元素进行存储。如果数组中的数据是基本数据类型,那么会将该数组作为集合中的元素进行存储。
例如:
int[] arr = {31,11,51,61};
List<int[]> list =Arrays.asList(arr);
System.out.prtinln(list);//[[I@C17164
这时list集合的长度是1,打印结果是打印数组对象。
但是如果定义一个Integer类型数组,即非基本数据类型数组:
Integer[] arr = {31,11,51,61};
List<int[]> list =Arrays.asList(arr);
System.out.println(list);//打印[31,11,51,61]
说明,如果存入非基本数据类型数据,打印的是每个对象的toString()。
18-16,Collection-->toArray方法
1,如何将集合转化成数组呢?
使用的是Collection接口中的toArray方法。
2,集合转成数组:可以对集合中的元素操作的方法进行限定,不允许对其进行增删。
3,toArray方法需要传入一个指定类型的数组。
长度该如何定义呢?
如果长度小于集合的size,那么该方法会创建一个同类型并和集合相同size的数组。
如果长度大于集合的size,那么该方法就会使用指定的数组,存储集合的元素,其他位置默认为null。
所以建议,最好将长度指定为集合的size。
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
String[] arr = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(arr));
18-17,JDK1.5新特性-ForEach循环
1,Collection的父接口是Iterable接口,该接口中只有一个iterator迭代器方法,在JDK1.5才出现,因为以后还有可能出现像Collection的顶层父类,都希望他们具备迭代功能,所以抽取出来,提高了扩展性。
2,ForEach循环,增强For循环
格式:for(类型 变量 :Collection集合或数组){...}
传统for和高级for的区别:
(1)传统for可以完成对语句执行很多次,因为可以定义控制循环的增量和条件。
(2)高级for是一种简化形式,它必须有被遍历的目标。该目标要么是数组,要么是Collection单列集合。
对数组的遍历如果仅仅是获取数组中的元素,可以使用高级for。
如果要对数组的角标进行操作建议使用传统for。
可以使用高级for遍历map集合么?
不能直接用,但是可以将map转换成单列的set就可以使用了。
4,实例:
public class Demo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
for(String s : list) {//简化了书写,底层其实是iterator
System.out.println(s);
}
}
}
public class Demo2 {
public static void main(String[] args) {
int[] arr = {3,1,5,7,4};
for(int i : arr) {
System.out.println(i);
}
}
}
遍历Map示例:
public class Demo3 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(3,"zhangsan");
map.put(1,"wangyi");
map.put(5,"wangcai");
map.put(7,"xiaoqiang");
for(Integer key : map.keySet()) {
String value = map.getKey();
System.out.println(key + "::" + value);
}
for(Map.Entry<Integer,String> me : map.entrySet()) {
Integer key = me.getKey();
String value = me.getValue();
System.out.println(key + ":" + value);
}
}
}
18-18,JDK1.5新特性-函数的可变参数
1,其实就是一个数组,但是接收的是数组的元素。自动将这些元素封装成数组。简化了调用者的书写。
注意:可变参数类型,必须定义在参数列表的结尾。
public static newAdd(int... arr) {
int sum = 0;
for(int i=0;i<arr.length;i++) {
sum += arr[i];
}
return sum;
}
int sum = newAdd(5,1,7,4,3,6);//可以传入任意长度的参数
System.out.println(sum);
int sum = newAdd(7,4,6,5,1,8,97,6);
System.out.println(sum);
注意:
public static newAdd(int... arr,int a)
{ ... code ...} //写法错误,传入的参数会全都赋给arr,a会没有值
public static newAdd(int a,int... arr)
{ ... code ...} //写法正确,第一个值传给a,后面的传给arr
18-19,JDK1.5新特新-静态导入
静态导入,其实导入的类中的静态成员。
在import后面加上static,示例如下:
import java.util.ArrayList;
import java.util.Collections;
import static java.util.Collections.*;//导入Collections中的所有的静态成员,可简化书写
import static java.lang.System.*;//导入System中的所有静态成员
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("abc3");
list.add("abc7");
list.add("abc1");
out.println(list);//out是System类中的静态成员,静态导入System后,就不用通过System调用了
sort(list);//Collections中的静态方法
out.println(list);
String max = max(list);//Collections中的静态方法
out.println("max = " + max);
}
}